github.com/dim4egster/coreth@v0.10.2/plugin/evm/import_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 "errors" 8 "fmt" 9 "math/big" 10 11 "github.com/dim4egster/coreth/core/state" 12 "github.com/dim4egster/coreth/params" 13 14 "github.com/dim4egster/qmallgo/chains/atomic" 15 "github.com/dim4egster/qmallgo/ids" 16 "github.com/dim4egster/qmallgo/snow" 17 "github.com/dim4egster/qmallgo/utils/crypto" 18 "github.com/dim4egster/qmallgo/utils/math" 19 "github.com/dim4egster/qmallgo/vms/components/avax" 20 "github.com/dim4egster/qmallgo/vms/components/verify" 21 "github.com/dim4egster/qmallgo/vms/secp256k1fx" 22 "github.com/ethereum/go-ethereum/common" 23 "github.com/ethereum/go-ethereum/log" 24 ) 25 26 var ( 27 _ UnsignedAtomicTx = &UnsignedImportTx{} 28 _ secp256k1fx.UnsignedTx = &UnsignedImportTx{} 29 errImportNonAVAXInputBanff = errors.New("import input cannot contain non-AVAX in Banff") 30 errImportNonAVAXOutputBanff = errors.New("import output cannot contain non-AVAX in Banff") 31 ) 32 33 // UnsignedImportTx is an unsigned ImportTx 34 type UnsignedImportTx struct { 35 avax.Metadata 36 // ID of the network on which this tx was issued 37 NetworkID uint32 `serialize:"true" json:"networkID"` 38 // ID of this blockchain. 39 BlockchainID ids.ID `serialize:"true" json:"blockchainID"` 40 // Which chain to consume the funds from 41 SourceChain ids.ID `serialize:"true" json:"sourceChain"` 42 // Inputs that consume UTXOs produced on the chain 43 ImportedInputs []*avax.TransferableInput `serialize:"true" json:"importedInputs"` 44 // Outputs 45 Outs []EVMOutput `serialize:"true" json:"outputs"` 46 } 47 48 // InputUTXOs returns the UTXOIDs of the imported funds 49 func (utx *UnsignedImportTx) InputUTXOs() ids.Set { 50 set := ids.NewSet(len(utx.ImportedInputs)) 51 for _, in := range utx.ImportedInputs { 52 set.Add(in.InputID()) 53 } 54 return set 55 } 56 57 // Verify this transaction is well-formed 58 func (utx *UnsignedImportTx) Verify( 59 ctx *snow.Context, 60 rules params.Rules, 61 ) error { 62 switch { 63 case utx == nil: 64 return errNilTx 65 case len(utx.ImportedInputs) == 0: 66 return errNoImportInputs 67 case utx.NetworkID != ctx.NetworkID: 68 return errWrongNetworkID 69 case ctx.ChainID != utx.BlockchainID: 70 return errWrongBlockchainID 71 case rules.IsApricotPhase3 && len(utx.Outs) == 0: 72 return errNoEVMOutputs 73 } 74 75 // Make sure that the tx has a valid peer chain ID 76 if rules.IsApricotPhase5 { 77 // Note that SameSubnet verifies that [tx.SourceChain] isn't this 78 // chain's ID 79 if err := verify.SameSubnet(ctx, utx.SourceChain); err != nil { 80 return errWrongChainID 81 } 82 } else { 83 if utx.SourceChain != ctx.XChainID { 84 return errWrongChainID 85 } 86 } 87 88 for _, out := range utx.Outs { 89 if err := out.Verify(); err != nil { 90 return fmt.Errorf("EVM Output failed verification: %w", err) 91 } 92 if rules.IsBanff && out.AssetID != ctx.AVAXAssetID { 93 return errImportNonAVAXOutputBanff 94 } 95 } 96 97 for _, in := range utx.ImportedInputs { 98 if err := in.Verify(); err != nil { 99 return fmt.Errorf("atomic input failed verification: %w", err) 100 } 101 if rules.IsBanff && in.AssetID() != ctx.AVAXAssetID { 102 return errImportNonAVAXInputBanff 103 } 104 } 105 if !avax.IsSortedAndUniqueTransferableInputs(utx.ImportedInputs) { 106 return errInputsNotSortedUnique 107 } 108 109 if rules.IsApricotPhase2 { 110 if !IsSortedAndUniqueEVMOutputs(utx.Outs) { 111 return errOutputsNotSortedUnique 112 } 113 } else if rules.IsApricotPhase1 { 114 if !IsSortedEVMOutputs(utx.Outs) { 115 return errOutputsNotSorted 116 } 117 } 118 119 return nil 120 } 121 122 func (utx *UnsignedImportTx) GasUsed(fixedFee bool) (uint64, error) { 123 var ( 124 cost = calcBytesCost(len(utx.Bytes())) 125 err error 126 ) 127 for _, in := range utx.ImportedInputs { 128 inCost, err := in.In.Cost() 129 if err != nil { 130 return 0, err 131 } 132 cost, err = math.Add64(cost, inCost) 133 if err != nil { 134 return 0, err 135 } 136 } 137 if fixedFee { 138 cost, err = math.Add64(cost, params.AtomicTxBaseCost) 139 if err != nil { 140 return 0, err 141 } 142 } 143 return cost, nil 144 } 145 146 // Amount of [assetID] burned by this transaction 147 func (utx *UnsignedImportTx) Burned(assetID ids.ID) (uint64, error) { 148 var ( 149 spent uint64 150 input uint64 151 err error 152 ) 153 for _, out := range utx.Outs { 154 if out.AssetID == assetID { 155 spent, err = math.Add64(spent, out.Amount) 156 if err != nil { 157 return 0, err 158 } 159 } 160 } 161 for _, in := range utx.ImportedInputs { 162 if in.AssetID() == assetID { 163 input, err = math.Add64(input, in.Input().Amount()) 164 if err != nil { 165 return 0, err 166 } 167 } 168 } 169 170 return math.Sub64(input, spent) 171 } 172 173 // SemanticVerify this transaction is valid. 174 func (utx *UnsignedImportTx) SemanticVerify( 175 vm *VM, 176 stx *Tx, 177 parent *Block, 178 baseFee *big.Int, 179 rules params.Rules, 180 ) error { 181 if err := utx.Verify(vm.ctx, rules); err != nil { 182 return err 183 } 184 185 // Check the transaction consumes and produces the right amounts 186 fc := avax.NewFlowChecker() 187 switch { 188 // Apply dynamic fees to import transactions as of Apricot Phase 3 189 case rules.IsApricotPhase3: 190 gasUsed, err := stx.GasUsed(rules.IsApricotPhase5) 191 if err != nil { 192 return err 193 } 194 txFee, err := calculateDynamicFee(gasUsed, baseFee) 195 if err != nil { 196 return err 197 } 198 fc.Produce(vm.ctx.AVAXAssetID, txFee) 199 200 // Apply fees to import transactions as of Apricot Phase 2 201 case rules.IsApricotPhase2: 202 fc.Produce(vm.ctx.AVAXAssetID, params.AvalancheAtomicTxFee) 203 } 204 for _, out := range utx.Outs { 205 fc.Produce(out.AssetID, out.Amount) 206 } 207 for _, in := range utx.ImportedInputs { 208 fc.Consume(in.AssetID(), in.Input().Amount()) 209 } 210 211 if err := fc.Verify(); err != nil { 212 return fmt.Errorf("import tx flow check failed due to: %w", err) 213 } 214 215 if len(stx.Creds) != len(utx.ImportedInputs) { 216 return fmt.Errorf("import tx contained mismatched number of inputs/credentials (%d vs. %d)", len(utx.ImportedInputs), len(stx.Creds)) 217 } 218 219 if !vm.bootstrapped { 220 // Allow for force committing during bootstrapping 221 return nil 222 } 223 224 utxoIDs := make([][]byte, len(utx.ImportedInputs)) 225 for i, in := range utx.ImportedInputs { 226 inputID := in.UTXOID.InputID() 227 utxoIDs[i] = inputID[:] 228 } 229 // allUTXOBytes is guaranteed to be the same length as utxoIDs 230 allUTXOBytes, err := vm.ctx.SharedMemory.Get(utx.SourceChain, utxoIDs) 231 if err != nil { 232 return fmt.Errorf("failed to fetch import UTXOs from %s due to: %w", utx.SourceChain, err) 233 } 234 235 for i, in := range utx.ImportedInputs { 236 utxoBytes := allUTXOBytes[i] 237 238 utxo := &avax.UTXO{} 239 if _, err := vm.codec.Unmarshal(utxoBytes, utxo); err != nil { 240 return fmt.Errorf("failed to unmarshal UTXO: %w", err) 241 } 242 243 cred := stx.Creds[i] 244 245 utxoAssetID := utxo.AssetID() 246 inAssetID := in.AssetID() 247 if utxoAssetID != inAssetID { 248 return errAssetIDMismatch 249 } 250 251 if err := vm.fx.VerifyTransfer(utx, in.In, cred, utxo.Out); err != nil { 252 return fmt.Errorf("import tx transfer failed verification: %w", err) 253 } 254 } 255 256 return vm.conflicts(utx.InputUTXOs(), parent) 257 } 258 259 // AtomicOps returns imported inputs spent on this transaction 260 // We spend imported UTXOs here rather than in semanticVerify because 261 // we don't want to remove an imported UTXO in semanticVerify 262 // only to have the transaction not be Accepted. This would be inconsistent. 263 // Recall that imported UTXOs are not kept in a versionDB. 264 func (utx *UnsignedImportTx) AtomicOps() (ids.ID, *atomic.Requests, error) { 265 utxoIDs := make([][]byte, len(utx.ImportedInputs)) 266 for i, in := range utx.ImportedInputs { 267 inputID := in.InputID() 268 utxoIDs[i] = inputID[:] 269 } 270 return utx.SourceChain, &atomic.Requests{RemoveRequests: utxoIDs}, nil 271 } 272 273 // newImportTx returns a new ImportTx 274 func (vm *VM) newImportTx( 275 chainID ids.ID, // chain to import from 276 to common.Address, // Address of recipient 277 baseFee *big.Int, // fee to use post-AP3 278 keys []*crypto.PrivateKeySECP256K1R, // Keys to import the funds 279 ) (*Tx, error) { 280 kc := secp256k1fx.NewKeychain() 281 for _, key := range keys { 282 kc.Add(key) 283 } 284 285 atomicUTXOs, _, _, err := vm.GetAtomicUTXOs(chainID, kc.Addresses(), ids.ShortEmpty, ids.Empty, -1) 286 if err != nil { 287 return nil, fmt.Errorf("problem retrieving atomic UTXOs: %w", err) 288 } 289 290 return vm.newImportTxWithUTXOs(chainID, to, baseFee, kc, atomicUTXOs) 291 } 292 293 // newImportTx returns a new ImportTx 294 func (vm *VM) newImportTxWithUTXOs( 295 chainID ids.ID, // chain to import from 296 to common.Address, // Address of recipient 297 baseFee *big.Int, // fee to use post-AP3 298 kc *secp256k1fx.Keychain, // Keychain to use for signing the atomic UTXOs 299 atomicUTXOs []*avax.UTXO, // UTXOs to spend 300 ) (*Tx, error) { 301 importedInputs := []*avax.TransferableInput{} 302 signers := [][]*crypto.PrivateKeySECP256K1R{} 303 304 importedAmount := make(map[ids.ID]uint64) 305 now := vm.clock.Unix() 306 for _, utxo := range atomicUTXOs { 307 inputIntf, utxoSigners, err := kc.Spend(utxo.Out, now) 308 if err != nil { 309 continue 310 } 311 input, ok := inputIntf.(avax.TransferableIn) 312 if !ok { 313 continue 314 } 315 aid := utxo.AssetID() 316 importedAmount[aid], err = math.Add64(importedAmount[aid], input.Amount()) 317 if err != nil { 318 return nil, err 319 } 320 importedInputs = append(importedInputs, &avax.TransferableInput{ 321 UTXOID: utxo.UTXOID, 322 Asset: utxo.Asset, 323 In: input, 324 }) 325 signers = append(signers, utxoSigners) 326 } 327 avax.SortTransferableInputsWithSigners(importedInputs, signers) 328 importedAVAXAmount := importedAmount[vm.ctx.AVAXAssetID] 329 330 outs := make([]EVMOutput, 0, len(importedAmount)) 331 // This will create unique outputs (in the context of sorting) 332 // since each output will have a unique assetID 333 for assetID, amount := range importedAmount { 334 // Skip the AVAX amount since it is included separately to account for 335 // the fee 336 if assetID == vm.ctx.AVAXAssetID || amount == 0 { 337 continue 338 } 339 outs = append(outs, EVMOutput{ 340 Address: to, 341 Amount: amount, 342 AssetID: assetID, 343 }) 344 } 345 346 rules := vm.currentRules() 347 348 var ( 349 txFeeWithoutChange uint64 350 txFeeWithChange uint64 351 ) 352 switch { 353 case rules.IsApricotPhase3: 354 if baseFee == nil { 355 return nil, errNilBaseFeeApricotPhase3 356 } 357 utx := &UnsignedImportTx{ 358 NetworkID: vm.ctx.NetworkID, 359 BlockchainID: vm.ctx.ChainID, 360 Outs: outs, 361 ImportedInputs: importedInputs, 362 SourceChain: chainID, 363 } 364 tx := &Tx{UnsignedAtomicTx: utx} 365 if err := tx.Sign(vm.codec, nil); err != nil { 366 return nil, err 367 } 368 369 gasUsedWithoutChange, err := tx.GasUsed(rules.IsApricotPhase5) 370 if err != nil { 371 return nil, err 372 } 373 gasUsedWithChange := gasUsedWithoutChange + EVMOutputGas 374 375 txFeeWithoutChange, err = calculateDynamicFee(gasUsedWithoutChange, baseFee) 376 if err != nil { 377 return nil, err 378 } 379 txFeeWithChange, err = calculateDynamicFee(gasUsedWithChange, baseFee) 380 if err != nil { 381 return nil, err 382 } 383 case rules.IsApricotPhase2: 384 txFeeWithoutChange = params.AvalancheAtomicTxFee 385 txFeeWithChange = params.AvalancheAtomicTxFee 386 } 387 388 // AVAX output 389 if importedAVAXAmount < txFeeWithoutChange { // imported amount goes toward paying tx fee 390 return nil, errInsufficientFundsForFee 391 } 392 393 if importedAVAXAmount > txFeeWithChange { 394 outs = append(outs, EVMOutput{ 395 Address: to, 396 Amount: importedAVAXAmount - txFeeWithChange, 397 AssetID: vm.ctx.AVAXAssetID, 398 }) 399 } 400 401 // If no outputs are produced, return an error. 402 // Note: this can happen if there is exactly enough AVAX to pay the 403 // transaction fee, but no other funds to be imported. 404 if len(outs) == 0 { 405 return nil, errNoEVMOutputs 406 } 407 408 SortEVMOutputs(outs) 409 410 // Create the transaction 411 utx := &UnsignedImportTx{ 412 NetworkID: vm.ctx.NetworkID, 413 BlockchainID: vm.ctx.ChainID, 414 Outs: outs, 415 ImportedInputs: importedInputs, 416 SourceChain: chainID, 417 } 418 tx := &Tx{UnsignedAtomicTx: utx} 419 if err := tx.Sign(vm.codec, signers); err != nil { 420 return nil, err 421 } 422 return tx, utx.Verify(vm.ctx, vm.currentRules()) 423 } 424 425 // EVMStateTransfer performs the state transfer to increase the balances of 426 // accounts accordingly with the imported EVMOutputs 427 func (utx *UnsignedImportTx) EVMStateTransfer(ctx *snow.Context, state *state.StateDB) error { 428 for _, to := range utx.Outs { 429 if to.AssetID == ctx.AVAXAssetID { 430 log.Debug("crosschain", "src", utx.SourceChain, "addr", to.Address, "amount", to.Amount, "assetID", "AVAX") 431 // If the asset is AVAX, convert the input amount in nAVAX to gWei by 432 // multiplying by the x2c rate. 433 amount := new(big.Int).Mul( 434 new(big.Int).SetUint64(to.Amount), x2cRate) 435 state.AddBalance(to.Address, amount) 436 } else { 437 log.Debug("crosschain", "src", utx.SourceChain, "addr", to.Address, "amount", to.Amount, "assetID", to.AssetID) 438 amount := new(big.Int).SetUint64(to.Amount) 439 state.AddBalanceMultiCoin(to.Address, common.Hash(to.AssetID), amount) 440 } 441 } 442 return nil 443 }