github.com/dim4egster/coreth@v0.10.2/plugin/evm/tx.go (about) 1 // (c) 2019-2020, Ava Labs, Inc. All rights reserved. 2 // See the file LICENSE for licensing terms. 3 4 package evm 5 6 import ( 7 "bytes" 8 "errors" 9 "fmt" 10 "math/big" 11 "sort" 12 13 "github.com/ethereum/go-ethereum/common" 14 15 "github.com/dim4egster/coreth/core/state" 16 "github.com/dim4egster/coreth/params" 17 18 "github.com/dim4egster/qmallgo/chains/atomic" 19 "github.com/dim4egster/qmallgo/codec" 20 "github.com/dim4egster/qmallgo/ids" 21 "github.com/dim4egster/qmallgo/snow" 22 "github.com/dim4egster/qmallgo/utils" 23 "github.com/dim4egster/qmallgo/utils/crypto" 24 "github.com/dim4egster/qmallgo/utils/hashing" 25 "github.com/dim4egster/qmallgo/utils/wrappers" 26 "github.com/dim4egster/qmallgo/vms/components/verify" 27 "github.com/dim4egster/qmallgo/vms/secp256k1fx" 28 ) 29 30 var ( 31 errWrongBlockchainID = errors.New("wrong blockchain ID provided") 32 errWrongNetworkID = errors.New("tx was issued with a different network ID") 33 errNilTx = errors.New("tx is nil") 34 errNoValueOutput = errors.New("output has no value") 35 errNoValueInput = errors.New("input has no value") 36 errNilOutput = errors.New("nil output") 37 errNilInput = errors.New("nil input") 38 errEmptyAssetID = errors.New("empty asset ID is not valid") 39 errNilBaseFee = errors.New("cannot calculate dynamic fee with nil baseFee") 40 errFeeOverflow = errors.New("overflow occurred while calculating the fee") 41 ) 42 43 // Constants for calculating the gas consumed by atomic transactions 44 var ( 45 TxBytesGas uint64 = 1 46 EVMOutputGas uint64 = (common.AddressLength + wrappers.LongLen + hashing.HashLen) * TxBytesGas 47 EVMInputGas uint64 = (common.AddressLength+wrappers.LongLen+hashing.HashLen+wrappers.LongLen)*TxBytesGas + secp256k1fx.CostPerSignature 48 ) 49 50 // EVMOutput defines an output that is added to the EVM state created by import transactions 51 type EVMOutput struct { 52 Address common.Address `serialize:"true" json:"address"` 53 Amount uint64 `serialize:"true" json:"amount"` 54 AssetID ids.ID `serialize:"true" json:"assetID"` 55 } 56 57 // EVMInput defines an input created from the EVM state to fund export transactions 58 type EVMInput struct { 59 Address common.Address `serialize:"true" json:"address"` 60 Amount uint64 `serialize:"true" json:"amount"` 61 AssetID ids.ID `serialize:"true" json:"assetID"` 62 Nonce uint64 `serialize:"true" json:"nonce"` 63 } 64 65 // Verify ... 66 func (out *EVMOutput) Verify() error { 67 switch { 68 case out == nil: 69 return errNilOutput 70 case out.Amount == 0: 71 return errNoValueOutput 72 case out.AssetID == ids.Empty: 73 return errEmptyAssetID 74 } 75 return nil 76 } 77 78 // Verify ... 79 func (in *EVMInput) Verify() error { 80 switch { 81 case in == nil: 82 return errNilInput 83 case in.Amount == 0: 84 return errNoValueInput 85 case in.AssetID == ids.Empty: 86 return errEmptyAssetID 87 } 88 return nil 89 } 90 91 // UnsignedTx is an unsigned transaction 92 type UnsignedTx interface { 93 Initialize(unsignedBytes, signedBytes []byte) 94 ID() ids.ID 95 GasUsed(fixedFee bool) (uint64, error) 96 Burned(assetID ids.ID) (uint64, error) 97 Bytes() []byte 98 SignedBytes() []byte 99 } 100 101 // UnsignedAtomicTx is an unsigned operation that can be atomically accepted 102 type UnsignedAtomicTx interface { 103 UnsignedTx 104 105 // InputUTXOs returns the UTXOs this tx consumes 106 InputUTXOs() ids.Set 107 // Verify attempts to verify that the transaction is well formed 108 Verify(ctx *snow.Context, rules params.Rules) error 109 // Attempts to verify this transaction with the provided state. 110 SemanticVerify(vm *VM, stx *Tx, parent *Block, baseFee *big.Int, rules params.Rules) error 111 // AtomicOps returns the blockchainID and set of atomic requests that 112 // must be applied to shared memory for this transaction to be accepted. 113 // The set of atomic requests must be returned in a consistent order. 114 AtomicOps() (ids.ID, *atomic.Requests, error) 115 116 EVMStateTransfer(ctx *snow.Context, state *state.StateDB) error 117 } 118 119 // Tx is a signed transaction 120 type Tx struct { 121 // The body of this transaction 122 UnsignedAtomicTx `serialize:"true" json:"unsignedTx"` 123 124 // The credentials of this transaction 125 Creds []verify.Verifiable `serialize:"true" json:"credentials"` 126 } 127 128 // Sign this transaction with the provided signers 129 func (tx *Tx) Sign(c codec.Manager, signers [][]*crypto.PrivateKeySECP256K1R) error { 130 unsignedBytes, err := c.Marshal(codecVersion, &tx.UnsignedAtomicTx) 131 if err != nil { 132 return fmt.Errorf("couldn't marshal UnsignedAtomicTx: %w", err) 133 } 134 135 // Attach credentials 136 hash := hashing.ComputeHash256(unsignedBytes) 137 for _, keys := range signers { 138 cred := &secp256k1fx.Credential{ 139 Sigs: make([][crypto.SECP256K1RSigLen]byte, len(keys)), 140 } 141 for i, key := range keys { 142 sig, err := key.SignHash(hash) // Sign hash 143 if err != nil { 144 return fmt.Errorf("problem generating credential: %w", err) 145 } 146 copy(cred.Sigs[i][:], sig) 147 } 148 tx.Creds = append(tx.Creds, cred) // Attach credential 149 } 150 151 signedBytes, err := c.Marshal(codecVersion, tx) 152 if err != nil { 153 return fmt.Errorf("couldn't marshal Tx: %w", err) 154 } 155 tx.Initialize(unsignedBytes, signedBytes) 156 return nil 157 } 158 159 // BlockFeeContribution calculates how much AVAX towards the block fee contribution was paid 160 // for via this transaction denominated in [avaxAssetID] with [baseFee] used to calculate the 161 // cost of this transaction. This function also returns the [gasUsed] by the 162 // transaction for inclusion in the [baseFee] algorithm. 163 func (tx *Tx) BlockFeeContribution(fixedFee bool, avaxAssetID ids.ID, baseFee *big.Int) (*big.Int, *big.Int, error) { 164 if baseFee == nil { 165 return nil, nil, errNilBaseFee 166 } 167 if baseFee.Cmp(common.Big0) <= 0 { 168 return nil, nil, fmt.Errorf("cannot calculate tip with base fee %d <= 0", baseFee) 169 } 170 gasUsed, err := tx.GasUsed(fixedFee) 171 if err != nil { 172 return nil, nil, err 173 } 174 txFee, err := calculateDynamicFee(gasUsed, baseFee) 175 if err != nil { 176 return nil, nil, err 177 } 178 burned, err := tx.Burned(avaxAssetID) 179 if err != nil { 180 return nil, nil, err 181 } 182 if txFee > burned { 183 return nil, nil, fmt.Errorf("insufficient AVAX burned (%d) to cover import tx fee (%d)", burned, txFee) 184 } 185 excessBurned := burned - txFee 186 187 // Calculate the amount of AVAX that has been burned above the required fee denominated 188 // in C-Chain native 18 decimal places 189 blockFeeContribution := new(big.Int).Mul(new(big.Int).SetUint64(excessBurned), x2cRate) 190 return blockFeeContribution, new(big.Int).SetUint64(gasUsed), nil 191 } 192 193 // innerSortInputsAndSigners implements sort.Interface for EVMInput 194 type innerSortInputsAndSigners struct { 195 inputs []EVMInput 196 signers [][]*crypto.PrivateKeySECP256K1R 197 } 198 199 func (ins *innerSortInputsAndSigners) Less(i, j int) bool { 200 addrComp := bytes.Compare(ins.inputs[i].Address.Bytes(), ins.inputs[j].Address.Bytes()) 201 if addrComp != 0 { 202 return addrComp < 0 203 } 204 return bytes.Compare(ins.inputs[i].AssetID[:], ins.inputs[j].AssetID[:]) < 0 205 } 206 207 func (ins *innerSortInputsAndSigners) Len() int { return len(ins.inputs) } 208 209 func (ins *innerSortInputsAndSigners) Swap(i, j int) { 210 ins.inputs[j], ins.inputs[i] = ins.inputs[i], ins.inputs[j] 211 ins.signers[j], ins.signers[i] = ins.signers[i], ins.signers[j] 212 } 213 214 // SortEVMInputsAndSigners sorts the list of EVMInputs based on the addresses and assetIDs 215 func SortEVMInputsAndSigners(inputs []EVMInput, signers [][]*crypto.PrivateKeySECP256K1R) { 216 sort.Sort(&innerSortInputsAndSigners{inputs: inputs, signers: signers}) 217 } 218 219 // IsSortedAndUniqueEVMInputs returns true if the EVM Inputs are sorted and unique 220 // based on the account addresses 221 func IsSortedAndUniqueEVMInputs(inputs []EVMInput) bool { 222 return utils.IsSortedAndUnique(&innerSortInputsAndSigners{inputs: inputs}) 223 } 224 225 // innerSortEVMOutputs implements sort.Interface for EVMOutput 226 type innerSortEVMOutputs struct { 227 outputs []EVMOutput 228 } 229 230 func (outs *innerSortEVMOutputs) Less(i, j int) bool { 231 addrComp := bytes.Compare(outs.outputs[i].Address.Bytes(), outs.outputs[j].Address.Bytes()) 232 if addrComp != 0 { 233 return addrComp < 0 234 } 235 return bytes.Compare(outs.outputs[i].AssetID[:], outs.outputs[j].AssetID[:]) < 0 236 } 237 238 func (outs *innerSortEVMOutputs) Len() int { return len(outs.outputs) } 239 240 func (outs *innerSortEVMOutputs) Swap(i, j int) { 241 outs.outputs[j], outs.outputs[i] = outs.outputs[i], outs.outputs[j] 242 } 243 244 // SortEVMOutputs sorts the list of EVMOutputs based on the addresses and assetIDs 245 // of the outputs 246 func SortEVMOutputs(outputs []EVMOutput) { 247 sort.Sort(&innerSortEVMOutputs{outputs: outputs}) 248 } 249 250 // IsSortedEVMOutputs returns true if the EVMOutputs are sorted 251 // based on the account addresses and assetIDs 252 func IsSortedEVMOutputs(outputs []EVMOutput) bool { 253 return sort.IsSorted(&innerSortEVMOutputs{outputs: outputs}) 254 } 255 256 // IsSortedAndUniqueEVMOutputs returns true if the EVMOutputs are sorted 257 // and unique based on the account addresses and assetIDs 258 func IsSortedAndUniqueEVMOutputs(outputs []EVMOutput) bool { 259 return utils.IsSortedAndUnique(&innerSortEVMOutputs{outputs: outputs}) 260 } 261 262 // calculates the amount of AVAX that must be burned by an atomic transaction 263 // that consumes [cost] at [baseFee]. 264 func calculateDynamicFee(cost uint64, baseFee *big.Int) (uint64, error) { 265 if baseFee == nil { 266 return 0, errNilBaseFee 267 } 268 bigCost := new(big.Int).SetUint64(cost) 269 fee := new(big.Int).Mul(bigCost, baseFee) 270 feeToRoundUp := new(big.Int).Add(fee, x2cRateMinus1) 271 feeInNAVAX := new(big.Int).Div(feeToRoundUp, x2cRate) 272 if !feeInNAVAX.IsUint64() { 273 // the fee is more than can fit in a uint64 274 return 0, errFeeOverflow 275 } 276 return feeInNAVAX.Uint64(), nil 277 } 278 279 func calcBytesCost(len int) uint64 { 280 return uint64(len) * TxBytesGas 281 } 282 283 // mergeAtomicOps merges atomic requests represented by [txs] 284 // to the [output] map, depending on whether [chainID] is present in the map. 285 func mergeAtomicOps(txs []*Tx) (map[ids.ID]*atomic.Requests, error) { 286 if len(txs) > 1 { 287 // txs should be stored in order of txID to ensure consistency 288 // with txs initialized from the txID index. 289 copyTxs := make([]*Tx, len(txs)) 290 copy(copyTxs, txs) 291 sort.Slice(copyTxs, func(i, j int) bool { return copyTxs[i].ID().Hex() < copyTxs[j].ID().Hex() }) 292 txs = copyTxs 293 } 294 output := make(map[ids.ID]*atomic.Requests) 295 for _, tx := range txs { 296 chainID, txRequests, err := tx.UnsignedAtomicTx.AtomicOps() 297 if err != nil { 298 return nil, err 299 } 300 mergeAtomicOpsToMap(output, chainID, txRequests) 301 } 302 return output, nil 303 } 304 305 // mergeAtomicOps merges atomic ops for [chainID] represented by [requests] 306 // to the [output] map provided. 307 func mergeAtomicOpsToMap(output map[ids.ID]*atomic.Requests, chainID ids.ID, requests *atomic.Requests) { 308 if request, exists := output[chainID]; exists { 309 request.PutRequests = append(request.PutRequests, requests.PutRequests...) 310 request.RemoveRequests = append(request.RemoveRequests, requests.RemoveRequests...) 311 } else { 312 output[chainID] = requests 313 } 314 }