From bc26c83012c4d72ed0c62eeb72988685f67f8659 Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Wed, 11 Dec 2024 16:57:04 +0300 Subject: [PATCH 1/4] new atomic pkg --- plugin/evm/admin.go | 7 +- plugin/evm/{ => atomic}/codec.go | 15 +- plugin/evm/{ => atomic}/export_tx.go | 239 +++++++++--- plugin/evm/{ => atomic}/import_tx.go | 165 +++++---- plugin/evm/{ => atomic}/metadata.go | 2 +- plugin/evm/{ => atomic}/status.go | 6 +- plugin/evm/{ => atomic}/tx.go | 119 +++--- plugin/evm/atomic/utils.go | 32 ++ plugin/evm/atomic_backend.go | 51 ++- plugin/evm/atomic_state.go | 13 +- plugin/evm/atomic_trie.go | 11 +- plugin/evm/atomic_trie_test.go | 53 +-- plugin/evm/atomic_tx_repository.go | 41 +- plugin/evm/atomic_tx_repository_test.go | 25 +- plugin/evm/block.go | 11 +- plugin/evm/{ => client}/client.go | 91 ++++- .../evm/{ => client}/client_interface_test.go | 2 +- plugin/evm/export_tx_test.go | 343 +++++++++-------- plugin/evm/formatting.go | 21 -- plugin/evm/gossip.go | 9 +- plugin/evm/gossip_test.go | 11 +- plugin/evm/handler.go | 7 +- plugin/evm/import_tx_test.go | 249 ++++++------- plugin/evm/mempool.go | 57 +-- plugin/evm/mempool_atomic_gossiping_test.go | 3 +- plugin/evm/mempool_test.go | 5 +- plugin/evm/service.go | 104 +----- plugin/evm/syncervm_test.go | 17 +- plugin/evm/test_tx.go | 44 +-- plugin/evm/tx_gossip_test.go | 27 +- plugin/evm/tx_heap.go | 17 +- plugin/evm/tx_heap_test.go | 13 +- plugin/evm/tx_test.go | 57 +-- plugin/evm/user.go | 7 +- plugin/evm/vm.go | 349 +++++------------- plugin/evm/vm_test.go | 99 ++--- 36 files changed, 1236 insertions(+), 1086 deletions(-) rename plugin/evm/{ => atomic}/codec.go (91%) rename plugin/evm/{ => atomic}/export_tx.go (58%) rename plugin/evm/{ => atomic}/import_tx.go (70%) rename plugin/evm/{ => atomic}/metadata.go (98%) rename plugin/evm/{ => atomic}/status.go (95%) rename plugin/evm/{ => atomic}/tx.go (75%) create mode 100644 plugin/evm/atomic/utils.go rename plugin/evm/{ => client}/client.go (78%) rename plugin/evm/{ => client}/client_interface_test.go (97%) diff --git a/plugin/evm/admin.go b/plugin/evm/admin.go index fd8d7f8d6e..e90be473a7 100644 --- a/plugin/evm/admin.go +++ b/plugin/evm/admin.go @@ -9,6 +9,7 @@ import ( "github.com/ava-labs/avalanchego/api" "github.com/ava-labs/avalanchego/utils/profiler" + "github.com/ava-labs/coreth/plugin/evm/client" "github.com/ethereum/go-ethereum/log" ) @@ -65,11 +66,7 @@ func (p *Admin) LockProfile(_ *http.Request, _ *struct{}, _ *api.EmptyReply) err return p.profiler.LockProfile() } -type SetLogLevelArgs struct { - Level string `json:"level"` -} - -func (p *Admin) SetLogLevel(_ *http.Request, args *SetLogLevelArgs, reply *api.EmptyReply) error { +func (p *Admin) SetLogLevel(_ *http.Request, args *client.SetLogLevelArgs, reply *api.EmptyReply) error { log.Info("EVM: SetLogLevel called", "logLevel", args.Level) p.vm.ctx.Lock.Lock() diff --git a/plugin/evm/codec.go b/plugin/evm/atomic/codec.go similarity index 91% rename from plugin/evm/codec.go rename to plugin/evm/atomic/codec.go index e4c38761e3..3376eeb049 100644 --- a/plugin/evm/codec.go +++ b/plugin/evm/atomic/codec.go @@ -1,9 +1,10 @@ // (c) 2019-2021, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. -package evm +package atomic import ( + "errors" "fmt" "github.com/ava-labs/avalanchego/codec" @@ -12,8 +13,14 @@ import ( "github.com/ava-labs/avalanchego/vms/secp256k1fx" ) -// Codec does serialization and deserialization -var Codec codec.Manager +const CodecVersion = uint16(0) + +var ( + // Codec does serialization and deserialization + Codec codec.Manager + + errMissingAtomicTxs = errors.New("cannot build a block with non-empty extra data and zero atomic transactions") +) func init() { Codec = codec.NewDefaultManager() @@ -35,7 +42,7 @@ func init() { lc.RegisterType(&secp256k1fx.Credential{}), lc.RegisterType(&secp256k1fx.Input{}), lc.RegisterType(&secp256k1fx.OutputOwners{}), - Codec.RegisterCodec(codecVersion, lc), + Codec.RegisterCodec(CodecVersion, lc), ) if errs.Errored() { panic(errs.Err) diff --git a/plugin/evm/export_tx.go b/plugin/evm/atomic/export_tx.go similarity index 58% rename from plugin/evm/export_tx.go rename to plugin/evm/atomic/export_tx.go index a187007046..26307cface 100644 --- a/plugin/evm/export_tx.go +++ b/plugin/evm/atomic/export_tx.go @@ -1,7 +1,7 @@ // (c) 2019-2020, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. -package evm +package atomic import ( "context" @@ -9,7 +9,6 @@ import ( "fmt" "math/big" - "github.com/ava-labs/coreth/core/state" "github.com/ava-labs/coreth/params" "github.com/holiman/uint256" @@ -30,10 +29,15 @@ import ( ) var ( - _ UnsignedAtomicTx = &UnsignedExportTx{} - _ secp256k1fx.UnsignedTx = &UnsignedExportTx{} - errExportNonAVAXInputBanff = errors.New("export input cannot contain non-AVAX in Banff") - errExportNonAVAXOutputBanff = errors.New("export output cannot contain non-AVAX in Banff") + _ UnsignedAtomicTx = &UnsignedExportTx{} + _ secp256k1fx.UnsignedTx = &UnsignedExportTx{} + ErrExportNonAVAXInputBanff = errors.New("export input cannot contain non-AVAX in Banff") + ErrExportNonAVAXOutputBanff = errors.New("export output cannot contain non-AVAX in Banff") + ErrNoExportOutputs = errors.New("tx has no export outputs") + errPublicKeySignatureMismatch = errors.New("signature doesn't match public key") + errOverflowExport = errors.New("overflow when computing export amount + txFee") + errInsufficientFunds = errors.New("insufficient funds") + errInvalidNonce = errors.New("invalid nonce") ) // UnsignedExportTx is an unsigned ExportTx @@ -73,13 +77,13 @@ func (utx *UnsignedExportTx) Verify( ) error { switch { case utx == nil: - return errNilTx + return ErrNilTx case len(utx.ExportedOutputs) == 0: - return errNoExportOutputs + return ErrNoExportOutputs case utx.NetworkID != ctx.NetworkID: - return errWrongNetworkID + return ErrWrongNetworkID case ctx.ChainID != utx.BlockchainID: - return errWrongBlockchainID + return ErrWrongChainID } // Make sure that the tx has a valid peer chain ID @@ -87,11 +91,11 @@ func (utx *UnsignedExportTx) Verify( // Note that SameSubnet verifies that [tx.DestinationChain] isn't this // chain's ID if err := verify.SameSubnet(context.TODO(), ctx, utx.DestinationChain); err != nil { - return errWrongChainID + return ErrWrongChainID } } else { if utx.DestinationChain != ctx.XChainID { - return errWrongChainID + return ErrWrongChainID } } @@ -100,7 +104,7 @@ func (utx *UnsignedExportTx) Verify( return err } if rules.IsBanff && in.AssetID != ctx.AVAXAssetID { - return errExportNonAVAXInputBanff + return ErrExportNonAVAXInputBanff } } @@ -110,17 +114,17 @@ func (utx *UnsignedExportTx) Verify( } assetID := out.AssetID() if assetID != ctx.AVAXAssetID && utx.DestinationChain == constants.PlatformChainID { - return errWrongChainID + return ErrWrongChainID } if rules.IsBanff && assetID != ctx.AVAXAssetID { - return errExportNonAVAXOutputBanff + return ErrExportNonAVAXOutputBanff } } if !avax.IsSortedTransferableOutputs(utx.ExportedOutputs, Codec) { - return errOutputsNotSorted + return ErrOutputsNotSorted } if rules.IsApricotPhase1 && !utils.IsSortedAndUnique(utx.Ins) { - return errInputsNotSortedUnique + return ErrInputsNotSortedUnique } return nil @@ -176,13 +180,14 @@ func (utx *UnsignedExportTx) Burned(assetID ids.ID) (uint64, error) { // SemanticVerify this transaction is valid. func (utx *UnsignedExportTx) SemanticVerify( - vm *VM, + backend *Backend, stx *Tx, - _ *Block, + parent AtomicBlockContext, baseFee *big.Int, - rules params.Rules, ) error { - if err := utx.Verify(vm.ctx, rules); err != nil { + ctx := backend.Ctx + rules := backend.Rules + if err := utx.Verify(ctx, rules); err != nil { return err } @@ -199,10 +204,10 @@ func (utx *UnsignedExportTx) SemanticVerify( if err != nil { return err } - fc.Produce(vm.ctx.AVAXAssetID, txFee) + fc.Produce(ctx.AVAXAssetID, txFee) // Apply fees to export transactions before Apricot Phase 3 default: - fc.Produce(vm.ctx.AVAXAssetID, params.AvalancheAtomicTxFee) + fc.Produce(ctx.AVAXAssetID, params.AvalancheAtomicTxFee) } for _, out := range utx.ExportedOutputs { fc.Produce(out.AssetID(), out.Output().Amount()) @@ -231,7 +236,7 @@ func (utx *UnsignedExportTx) SemanticVerify( if len(cred.Sigs) != 1 { return fmt.Errorf("expected one signature for EVM Input Credential, but found: %d", len(cred.Sigs)) } - pubKey, err := vm.secpCache.RecoverPublicKey(utx.Bytes(), cred.Sigs[0][:]) + pubKey, err := backend.SecpCache.RecoverPublicKey(utx.Bytes(), cred.Sigs[0][:]) if err != nil { return err } @@ -258,7 +263,7 @@ func (utx *UnsignedExportTx) AtomicOps() (ids.ID, *atomic.Requests, error) { Out: out.Out, } - utxoBytes, err := Codec.Marshal(codecVersion, utxo) + utxoBytes, err := Codec.Marshal(CodecVersion, utxo) if err != nil { return ids.ID{}, nil, err } @@ -277,8 +282,11 @@ func (utx *UnsignedExportTx) AtomicOps() (ids.ID, *atomic.Requests, error) { return utx.DestinationChain, &atomic.Requests{PutRequests: elems}, nil } -// newExportTx returns a new ExportTx -func (vm *VM) newExportTx( +// NewExportTx returns a new ExportTx +func NewExportTx( + ctx *snow.Context, + rules params.Rules, + state StateDB, assetID ids.ID, // AssetID of the tokens to export amount uint64, // Amount of tokens to export chainID ids.ID, // Chain to send the UTXOs to @@ -306,8 +314,8 @@ func (vm *VM) newExportTx( ) // consume non-AVAX - if assetID != vm.ctx.AVAXAssetID { - ins, signers, err = vm.GetSpendableFunds(keys, assetID, amount) + if assetID != ctx.AVAXAssetID { + ins, signers, err = GetSpendableFunds(ctx, state, keys, assetID, amount) if err != nil { return nil, fmt.Errorf("couldn't generate tx inputs/signers: %w", err) } @@ -315,18 +323,17 @@ func (vm *VM) newExportTx( avaxNeeded = amount } - rules := vm.currentRules() switch { case rules.IsApricotPhase3: utx := &UnsignedExportTx{ - NetworkID: vm.ctx.NetworkID, - BlockchainID: vm.ctx.ChainID, + NetworkID: ctx.NetworkID, + BlockchainID: ctx.ChainID, DestinationChain: chainID, Ins: ins, ExportedOutputs: outs, } tx := &Tx{UnsignedAtomicTx: utx} - if err := tx.Sign(vm.codec, nil); err != nil { + if err := tx.Sign(Codec, nil); err != nil { return nil, err } @@ -336,14 +343,14 @@ func (vm *VM) newExportTx( return nil, err } - avaxIns, avaxSigners, err = vm.GetSpendableAVAXWithFee(keys, avaxNeeded, cost, baseFee) + avaxIns, avaxSigners, err = GetSpendableAVAXWithFee(ctx, state, keys, avaxNeeded, cost, baseFee) default: var newAvaxNeeded uint64 newAvaxNeeded, err = math.Add64(avaxNeeded, params.AvalancheAtomicTxFee) if err != nil { return nil, errOverflowExport } - avaxIns, avaxSigners, err = vm.GetSpendableFunds(keys, vm.ctx.AVAXAssetID, newAvaxNeeded) + avaxIns, avaxSigners, err = GetSpendableFunds(ctx, state, keys, ctx.AVAXAssetID, newAvaxNeeded) } if err != nil { return nil, fmt.Errorf("couldn't generate tx inputs/signers: %w", err) @@ -351,26 +358,26 @@ func (vm *VM) newExportTx( ins = append(ins, avaxIns...) signers = append(signers, avaxSigners...) - avax.SortTransferableOutputs(outs, vm.codec) + avax.SortTransferableOutputs(outs, Codec) SortEVMInputsAndSigners(ins, signers) // Create the transaction utx := &UnsignedExportTx{ - NetworkID: vm.ctx.NetworkID, - BlockchainID: vm.ctx.ChainID, + NetworkID: ctx.NetworkID, + BlockchainID: ctx.ChainID, DestinationChain: chainID, Ins: ins, ExportedOutputs: outs, } tx := &Tx{UnsignedAtomicTx: utx} - if err := tx.Sign(vm.codec, signers); err != nil { + if err := tx.Sign(Codec, signers); err != nil { return nil, err } - return tx, utx.Verify(vm.ctx, vm.currentRules()) + return tx, utx.Verify(ctx, rules) } // EVMStateTransfer executes the state update from the atomic export transaction -func (utx *UnsignedExportTx) EVMStateTransfer(ctx *snow.Context, state *state.StateDB) error { +func (utx *UnsignedExportTx) EVMStateTransfer(ctx *snow.Context, state StateDB) error { addrs := map[[20]byte]uint64{} for _, from := range utx.Ins { if from.AssetID == ctx.AVAXAssetID { @@ -379,7 +386,7 @@ func (utx *UnsignedExportTx) EVMStateTransfer(ctx *snow.Context, state *state.St // denomination before export. amount := new(uint256.Int).Mul( uint256.NewInt(from.Amount), - uint256.NewInt(x2cRate.Uint64()), + uint256.NewInt(X2CRate.Uint64()), ) if state.GetBalance(from.Address).Cmp(amount) < 0 { return errInsufficientFunds @@ -403,3 +410,151 @@ func (utx *UnsignedExportTx) EVMStateTransfer(ctx *snow.Context, state *state.St } return nil } + +// GetSpendableFunds returns a list of EVMInputs and keys (in corresponding +// order) to total [amount] of [assetID] owned by [keys]. +// Note: we return [][]*secp256k1.PrivateKey even though each input +// corresponds to a single key, so that the signers can be passed in to +// [tx.Sign] which supports multiple keys on a single input. +func GetSpendableFunds( + ctx *snow.Context, + state StateDB, + keys []*secp256k1.PrivateKey, + assetID ids.ID, + amount uint64, +) ([]EVMInput, [][]*secp256k1.PrivateKey, error) { + inputs := []EVMInput{} + signers := [][]*secp256k1.PrivateKey{} + // Note: we assume that each key in [keys] is unique, so that iterating over + // the keys will not produce duplicated nonces in the returned EVMInput slice. + for _, key := range keys { + if amount == 0 { + break + } + addr := GetEthAddress(key) + var balance uint64 + if assetID == ctx.AVAXAssetID { + // If the asset is AVAX, we divide by the x2cRate to convert back to the correct + // denomination of AVAX that can be exported. + balance = new(uint256.Int).Div(state.GetBalance(addr), X2CRate).Uint64() + } else { + balance = state.GetBalanceMultiCoin(addr, common.Hash(assetID)).Uint64() + } + if balance == 0 { + continue + } + if amount < balance { + balance = amount + } + nonce := state.GetNonce(addr) + + inputs = append(inputs, EVMInput{ + Address: addr, + Amount: balance, + AssetID: assetID, + Nonce: nonce, + }) + signers = append(signers, []*secp256k1.PrivateKey{key}) + amount -= balance + } + + if amount > 0 { + return nil, nil, errInsufficientFunds + } + + return inputs, signers, nil +} + +// GetSpendableAVAXWithFee returns a list of EVMInputs and keys (in corresponding +// order) to total [amount] + [fee] of [AVAX] owned by [keys]. +// This function accounts for the added cost of the additional inputs needed to +// create the transaction and makes sure to skip any keys with a balance that is +// insufficient to cover the additional fee. +// Note: we return [][]*secp256k1.PrivateKey even though each input +// corresponds to a single key, so that the signers can be passed in to +// [tx.Sign] which supports multiple keys on a single input. +func GetSpendableAVAXWithFee( + ctx *snow.Context, + state StateDB, + keys []*secp256k1.PrivateKey, + amount uint64, + cost uint64, + baseFee *big.Int, +) ([]EVMInput, [][]*secp256k1.PrivateKey, error) { + initialFee, err := CalculateDynamicFee(cost, baseFee) + if err != nil { + return nil, nil, err + } + + newAmount, err := math.Add64(amount, initialFee) + if err != nil { + return nil, nil, err + } + amount = newAmount + + inputs := []EVMInput{} + signers := [][]*secp256k1.PrivateKey{} + // Note: we assume that each key in [keys] is unique, so that iterating over + // the keys will not produce duplicated nonces in the returned EVMInput slice. + for _, key := range keys { + if amount == 0 { + break + } + + prevFee, err := CalculateDynamicFee(cost, baseFee) + if err != nil { + return nil, nil, err + } + + newCost := cost + EVMInputGas + newFee, err := CalculateDynamicFee(newCost, baseFee) + if err != nil { + return nil, nil, err + } + + additionalFee := newFee - prevFee + + addr := GetEthAddress(key) + // Since the asset is AVAX, we divide by the x2cRate to convert back to + // the correct denomination of AVAX that can be exported. + balance := new(uint256.Int).Div(state.GetBalance(addr), X2CRate).Uint64() + // If the balance for [addr] is insufficient to cover the additional cost + // of adding an input to the transaction, skip adding the input altogether + if balance <= additionalFee { + continue + } + + // Update the cost for the next iteration + cost = newCost + + newAmount, err := math.Add64(amount, additionalFee) + if err != nil { + return nil, nil, err + } + amount = newAmount + + // Use the entire [balance] as an input, but if the required [amount] + // is less than the balance, update the [inputAmount] to spend the + // minimum amount to finish the transaction. + inputAmount := balance + if amount < balance { + inputAmount = amount + } + nonce := state.GetNonce(addr) + + inputs = append(inputs, EVMInput{ + Address: addr, + Amount: inputAmount, + AssetID: ctx.AVAXAssetID, + Nonce: nonce, + }) + signers = append(signers, []*secp256k1.PrivateKey{key}) + amount -= inputAmount + } + + if amount > 0 { + return nil, nil, errInsufficientFunds + } + + return inputs, signers, nil +} diff --git a/plugin/evm/import_tx.go b/plugin/evm/atomic/import_tx.go similarity index 70% rename from plugin/evm/import_tx.go rename to plugin/evm/atomic/import_tx.go index b447a717ee..0d4d367d4e 100644 --- a/plugin/evm/import_tx.go +++ b/plugin/evm/atomic/import_tx.go @@ -1,7 +1,7 @@ // (c) 2019-2020, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. -package evm +package atomic import ( "context" @@ -10,7 +10,6 @@ import ( "math/big" "slices" - "github.com/ava-labs/coreth/core/state" "github.com/ava-labs/coreth/params" "github.com/holiman/uint256" @@ -21,6 +20,7 @@ import ( "github.com/ava-labs/avalanchego/utils/crypto/secp256k1" "github.com/ava-labs/avalanchego/utils/math" "github.com/ava-labs/avalanchego/utils/set" + "github.com/ava-labs/avalanchego/utils/timer/mockable" "github.com/ava-labs/avalanchego/vms/components/avax" "github.com/ava-labs/avalanchego/vms/components/verify" "github.com/ava-labs/avalanchego/vms/secp256k1fx" @@ -31,8 +31,19 @@ import ( var ( _ UnsignedAtomicTx = &UnsignedImportTx{} _ secp256k1fx.UnsignedTx = &UnsignedImportTx{} - errImportNonAVAXInputBanff = errors.New("import input cannot contain non-AVAX in Banff") - errImportNonAVAXOutputBanff = errors.New("import output cannot contain non-AVAX in Banff") + ErrImportNonAVAXInputBanff = errors.New("import input cannot contain non-AVAX in Banff") + ErrImportNonAVAXOutputBanff = errors.New("import output cannot contain non-AVAX in Banff") + ErrNoImportInputs = errors.New("tx has no imported inputs") + ErrConflictingAtomicInputs = errors.New("invalid block due to conflicting atomic inputs") + ErrWrongChainID = errors.New("tx has wrong chain ID") + ErrNoEVMOutputs = errors.New("tx has no EVM outputs") + ErrInputsNotSortedUnique = errors.New("inputs not sorted and unique") + ErrOutputsNotSortedUnique = errors.New("outputs not sorted and unique") + ErrOutputsNotSorted = errors.New("tx outputs not sorted") + ErrAssetIDMismatch = errors.New("asset IDs in the input don't match the utxo") + errNilBaseFeeApricotPhase3 = errors.New("nil base fee is invalid after apricotPhase3") + errInsufficientFundsForFee = errors.New("insufficient AVAX funds to pay transaction fee") + errRejectedParent = errors.New("rejected parent") ) // UnsignedImportTx is an unsigned ImportTx @@ -66,15 +77,15 @@ func (utx *UnsignedImportTx) Verify( ) error { switch { case utx == nil: - return errNilTx + return ErrNilTx case len(utx.ImportedInputs) == 0: - return errNoImportInputs + return ErrNoImportInputs case utx.NetworkID != ctx.NetworkID: - return errWrongNetworkID + return ErrWrongNetworkID case ctx.ChainID != utx.BlockchainID: - return errWrongBlockchainID + return ErrWrongChainID case rules.IsApricotPhase3 && len(utx.Outs) == 0: - return errNoEVMOutputs + return ErrNoEVMOutputs } // Make sure that the tx has a valid peer chain ID @@ -82,11 +93,11 @@ func (utx *UnsignedImportTx) Verify( // Note that SameSubnet verifies that [tx.SourceChain] isn't this // chain's ID if err := verify.SameSubnet(context.TODO(), ctx, utx.SourceChain); err != nil { - return errWrongChainID + return ErrWrongChainID } } else { if utx.SourceChain != ctx.XChainID { - return errWrongChainID + return ErrWrongChainID } } @@ -95,7 +106,7 @@ func (utx *UnsignedImportTx) Verify( return fmt.Errorf("EVM Output failed verification: %w", err) } if rules.IsBanff && out.AssetID != ctx.AVAXAssetID { - return errImportNonAVAXOutputBanff + return ErrImportNonAVAXOutputBanff } } @@ -104,20 +115,20 @@ func (utx *UnsignedImportTx) Verify( return fmt.Errorf("atomic input failed verification: %w", err) } if rules.IsBanff && in.AssetID() != ctx.AVAXAssetID { - return errImportNonAVAXInputBanff + return ErrImportNonAVAXInputBanff } } if !utils.IsSortedAndUnique(utx.ImportedInputs) { - return errInputsNotSortedUnique + return ErrInputsNotSortedUnique } if rules.IsApricotPhase2 { if !utils.IsSortedAndUnique(utx.Outs) { - return errOutputsNotSortedUnique + return ErrOutputsNotSortedUnique } } else if rules.IsApricotPhase1 { if !slices.IsSortedFunc(utx.Outs, EVMOutput.Compare) { - return errOutputsNotSorted + return ErrOutputsNotSorted } } @@ -177,13 +188,14 @@ func (utx *UnsignedImportTx) Burned(assetID ids.ID) (uint64, error) { // SemanticVerify this transaction is valid. func (utx *UnsignedImportTx) SemanticVerify( - vm *VM, + backend *Backend, stx *Tx, - parent *Block, + parent AtomicBlockContext, baseFee *big.Int, - rules params.Rules, ) error { - if err := utx.Verify(vm.ctx, rules); err != nil { + ctx := backend.Ctx + rules := backend.Rules + if err := utx.Verify(ctx, rules); err != nil { return err } @@ -200,11 +212,11 @@ func (utx *UnsignedImportTx) SemanticVerify( if err != nil { return err } - fc.Produce(vm.ctx.AVAXAssetID, txFee) + fc.Produce(ctx.AVAXAssetID, txFee) // Apply fees to import transactions as of Apricot Phase 2 case rules.IsApricotPhase2: - fc.Produce(vm.ctx.AVAXAssetID, params.AvalancheAtomicTxFee) + fc.Produce(ctx.AVAXAssetID, params.AvalancheAtomicTxFee) } for _, out := range utx.Outs { fc.Produce(out.AssetID, out.Amount) @@ -221,7 +233,7 @@ func (utx *UnsignedImportTx) SemanticVerify( return fmt.Errorf("import tx contained mismatched number of inputs/credentials (%d vs. %d)", len(utx.ImportedInputs), len(stx.Creds)) } - if !vm.bootstrapped.Get() { + if !backend.Bootstrapped { // Allow for force committing during bootstrapping return nil } @@ -232,7 +244,7 @@ func (utx *UnsignedImportTx) SemanticVerify( utxoIDs[i] = inputID[:] } // allUTXOBytes is guaranteed to be the same length as utxoIDs - allUTXOBytes, err := vm.ctx.SharedMemory.Get(utx.SourceChain, utxoIDs) + allUTXOBytes, err := ctx.SharedMemory.Get(utx.SourceChain, utxoIDs) if err != nil { return fmt.Errorf("failed to fetch import UTXOs from %s due to: %w", utx.SourceChain, err) } @@ -241,7 +253,7 @@ func (utx *UnsignedImportTx) SemanticVerify( utxoBytes := allUTXOBytes[i] utxo := &avax.UTXO{} - if _, err := vm.codec.Unmarshal(utxoBytes, utxo); err != nil { + if _, err := Codec.Unmarshal(utxoBytes, utxo); err != nil { return fmt.Errorf("failed to unmarshal UTXO: %w", err) } @@ -250,15 +262,15 @@ func (utx *UnsignedImportTx) SemanticVerify( utxoAssetID := utxo.AssetID() inAssetID := in.AssetID() if utxoAssetID != inAssetID { - return errAssetIDMismatch + return ErrAssetIDMismatch } - if err := vm.fx.VerifyTransfer(utx, in.In, cred, utxo.Out); err != nil { + if err := backend.Fx.VerifyTransfer(utx, in.In, cred, utxo.Out); err != nil { return fmt.Errorf("import tx transfer failed verification: %w", err) } } - return vm.conflicts(utx.InputUTXOs(), parent) + return conflicts(backend, utx.InputUTXOs(), parent) } // AtomicOps returns imported inputs spent on this transaction @@ -275,28 +287,11 @@ func (utx *UnsignedImportTx) AtomicOps() (ids.ID, *atomic.Requests, error) { return utx.SourceChain, &atomic.Requests{RemoveRequests: utxoIDs}, nil } -// newImportTx returns a new ImportTx -func (vm *VM) newImportTx( - chainID ids.ID, // chain to import from - to common.Address, // Address of recipient - baseFee *big.Int, // fee to use post-AP3 - keys []*secp256k1.PrivateKey, // Keys to import the funds -) (*Tx, error) { - kc := secp256k1fx.NewKeychain() - for _, key := range keys { - kc.Add(key) - } - - atomicUTXOs, _, _, err := vm.GetAtomicUTXOs(chainID, kc.Addresses(), ids.ShortEmpty, ids.Empty, -1) - if err != nil { - return nil, fmt.Errorf("problem retrieving atomic UTXOs: %w", err) - } - - return vm.newImportTxWithUTXOs(chainID, to, baseFee, kc, atomicUTXOs) -} - -// newImportTx returns a new ImportTx -func (vm *VM) newImportTxWithUTXOs( +// NewImportTx returns a new ImportTx +func NewImportTx( + ctx *snow.Context, + rules params.Rules, + clk mockable.Clock, chainID ids.ID, // chain to import from to common.Address, // Address of recipient baseFee *big.Int, // fee to use post-AP3 @@ -307,7 +302,7 @@ func (vm *VM) newImportTxWithUTXOs( signers := [][]*secp256k1.PrivateKey{} importedAmount := make(map[ids.ID]uint64) - now := vm.clock.Unix() + now := clk.Unix() for _, utxo := range atomicUTXOs { inputIntf, utxoSigners, err := kc.Spend(utxo.Out, now) if err != nil { @@ -330,7 +325,7 @@ func (vm *VM) newImportTxWithUTXOs( signers = append(signers, utxoSigners) } avax.SortTransferableInputsWithSigners(importedInputs, signers) - importedAVAXAmount := importedAmount[vm.ctx.AVAXAssetID] + importedAVAXAmount := importedAmount[ctx.AVAXAssetID] outs := make([]EVMOutput, 0, len(importedAmount)) // This will create unique outputs (in the context of sorting) @@ -338,7 +333,7 @@ func (vm *VM) newImportTxWithUTXOs( for assetID, amount := range importedAmount { // Skip the AVAX amount since it is included separately to account for // the fee - if assetID == vm.ctx.AVAXAssetID || amount == 0 { + if assetID == ctx.AVAXAssetID || amount == 0 { continue } outs = append(outs, EVMOutput{ @@ -348,8 +343,6 @@ func (vm *VM) newImportTxWithUTXOs( }) } - rules := vm.currentRules() - var ( txFeeWithoutChange uint64 txFeeWithChange uint64 @@ -360,14 +353,14 @@ func (vm *VM) newImportTxWithUTXOs( return nil, errNilBaseFeeApricotPhase3 } utx := &UnsignedImportTx{ - NetworkID: vm.ctx.NetworkID, - BlockchainID: vm.ctx.ChainID, + NetworkID: ctx.NetworkID, + BlockchainID: ctx.ChainID, Outs: outs, ImportedInputs: importedInputs, SourceChain: chainID, } tx := &Tx{UnsignedAtomicTx: utx} - if err := tx.Sign(vm.codec, nil); err != nil { + if err := tx.Sign(Codec, nil); err != nil { return nil, err } @@ -399,7 +392,7 @@ func (vm *VM) newImportTxWithUTXOs( outs = append(outs, EVMOutput{ Address: to, Amount: importedAVAXAmount - txFeeWithChange, - AssetID: vm.ctx.AVAXAssetID, + AssetID: ctx.AVAXAssetID, }) } @@ -407,35 +400,35 @@ func (vm *VM) newImportTxWithUTXOs( // Note: this can happen if there is exactly enough AVAX to pay the // transaction fee, but no other funds to be imported. if len(outs) == 0 { - return nil, errNoEVMOutputs + return nil, ErrNoEVMOutputs } utils.Sort(outs) // Create the transaction utx := &UnsignedImportTx{ - NetworkID: vm.ctx.NetworkID, - BlockchainID: vm.ctx.ChainID, + NetworkID: ctx.NetworkID, + BlockchainID: ctx.ChainID, Outs: outs, ImportedInputs: importedInputs, SourceChain: chainID, } tx := &Tx{UnsignedAtomicTx: utx} - if err := tx.Sign(vm.codec, signers); err != nil { + if err := tx.Sign(Codec, signers); err != nil { return nil, err } - return tx, utx.Verify(vm.ctx, vm.currentRules()) + return tx, utx.Verify(ctx, rules) } // EVMStateTransfer performs the state transfer to increase the balances of // accounts accordingly with the imported EVMOutputs -func (utx *UnsignedImportTx) EVMStateTransfer(ctx *snow.Context, state *state.StateDB) error { +func (utx *UnsignedImportTx) EVMStateTransfer(ctx *snow.Context, state StateDB) error { for _, to := range utx.Outs { if to.AssetID == ctx.AVAXAssetID { log.Debug("import_tx", "src", utx.SourceChain, "addr", to.Address, "amount", to.Amount, "assetID", "AVAX") // If the asset is AVAX, convert the input amount in nAVAX to gWei by // multiplying by the x2c rate. - amount := new(uint256.Int).Mul(uint256.NewInt(to.Amount), x2cRate) + amount := new(uint256.Int).Mul(uint256.NewInt(to.Amount), X2CRate) state.AddBalance(to.Address, amount) } else { log.Debug("import_tx", "src", utx.SourceChain, "addr", to.Address, "amount", to.Amount, "assetID", to.AssetID) @@ -445,3 +438,43 @@ func (utx *UnsignedImportTx) EVMStateTransfer(ctx *snow.Context, state *state.St } return nil } + +// conflicts returns an error if [inputs] conflicts with any of the atomic inputs contained in [ancestor] +// or any of its ancestor blocks going back to the last accepted block in its ancestry. If [ancestor] is +// accepted, then nil will be returned immediately. +// If the ancestry of [ancestor] cannot be fetched, then [errRejectedParent] may be returned. +func conflicts(backend *Backend, inputs set.Set[ids.ID], ancestor AtomicBlockContext) error { + fetcher := backend.BlockFetcher + lastAcceptedBlock := fetcher.LastAcceptedBlockInternal() + lastAcceptedHeight := lastAcceptedBlock.Height() + for ancestor.Height() > lastAcceptedHeight { + // If any of the atomic transactions in the ancestor conflict with [inputs] + // return an error. + for _, atomicTx := range ancestor.AtomicTxs() { + if inputs.Overlaps(atomicTx.InputUTXOs()) { + return ErrConflictingAtomicInputs + } + } + + // Move up the chain. + nextAncestorID := ancestor.Parent() + // If the ancestor is unknown, then the parent failed + // verification when it was called. + // If the ancestor is rejected, then this block shouldn't be + // inserted into the canonical chain because the parent is + // will be missing. + // If the ancestor is processing, then the block may have + // been verified. + nextAncestorIntf, err := fetcher.GetBlockInternal(context.TODO(), nextAncestorID) + if err != nil { + return errRejectedParent + } + nextAncestor, ok := nextAncestorIntf.(AtomicBlockContext) + if !ok { + return fmt.Errorf("ancestor block %s had unexpected type %T", nextAncestor.ID(), nextAncestorIntf) + } + ancestor = nextAncestor + } + + return nil +} diff --git a/plugin/evm/metadata.go b/plugin/evm/atomic/metadata.go similarity index 98% rename from plugin/evm/metadata.go rename to plugin/evm/atomic/metadata.go index 2665d329bc..7cd570f7ec 100644 --- a/plugin/evm/metadata.go +++ b/plugin/evm/atomic/metadata.go @@ -1,7 +1,7 @@ // Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. -package evm +package atomic import ( "github.com/ava-labs/avalanchego/ids" diff --git a/plugin/evm/status.go b/plugin/evm/atomic/status.go similarity index 95% rename from plugin/evm/status.go rename to plugin/evm/atomic/status.go index 14d1b009a7..c7c72d0987 100644 --- a/plugin/evm/status.go +++ b/plugin/evm/atomic/status.go @@ -1,16 +1,14 @@ // (c) 2019-2020, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. -package evm +package atomic import ( "errors" "fmt" ) -var ( - errUnknownStatus = errors.New("unknown status") -) +var errUnknownStatus = errors.New("unknown status") // Status ... type Status uint32 diff --git a/plugin/evm/tx.go b/plugin/evm/atomic/tx.go similarity index 75% rename from plugin/evm/tx.go rename to plugin/evm/atomic/tx.go index 9361f71976..a911402dea 100644 --- a/plugin/evm/tx.go +++ b/plugin/evm/atomic/tx.go @@ -1,44 +1,50 @@ // (c) 2019-2020, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. -package evm +package atomic import ( "bytes" + "context" "errors" "fmt" "math/big" "sort" "github.com/ethereum/go-ethereum/common" + "github.com/holiman/uint256" - "github.com/ava-labs/coreth/core/state" "github.com/ava-labs/coreth/params" "github.com/ava-labs/avalanchego/chains/atomic" "github.com/ava-labs/avalanchego/codec" "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/snow" - "github.com/ava-labs/avalanchego/utils" + "github.com/ava-labs/avalanchego/snow/consensus/snowman" "github.com/ava-labs/avalanchego/utils/crypto/secp256k1" "github.com/ava-labs/avalanchego/utils/hashing" "github.com/ava-labs/avalanchego/utils/set" "github.com/ava-labs/avalanchego/utils/wrappers" "github.com/ava-labs/avalanchego/vms/components/verify" + "github.com/ava-labs/avalanchego/vms/platformvm/fx" "github.com/ava-labs/avalanchego/vms/secp256k1fx" ) +const ( + X2CRateUint64 uint64 = 1_000_000_000 + x2cRateMinus1Uint64 uint64 = X2CRateUint64 - 1 +) + var ( - errWrongBlockchainID = errors.New("wrong blockchain ID provided") - errWrongNetworkID = errors.New("tx was issued with a different network ID") - errNilTx = errors.New("tx is nil") - errNoValueOutput = errors.New("output has no value") - errNoValueInput = errors.New("input has no value") - errNilOutput = errors.New("nil output") - errNilInput = errors.New("nil input") - errEmptyAssetID = errors.New("empty asset ID is not valid") - errNilBaseFee = errors.New("cannot calculate dynamic fee with nil baseFee") - errFeeOverflow = errors.New("overflow occurred while calculating the fee") + ErrWrongNetworkID = errors.New("tx was issued with a different network ID") + ErrNilTx = errors.New("tx is nil") + errNoValueOutput = errors.New("output has no value") + ErrNoValueInput = errors.New("input has no value") + errNilOutput = errors.New("nil output") + errNilInput = errors.New("nil input") + errEmptyAssetID = errors.New("empty asset ID is not valid") + errNilBaseFee = errors.New("cannot calculate dynamic fee with nil baseFee") + errFeeOverflow = errors.New("overflow occurred while calculating the fee") ) // Constants for calculating the gas consumed by atomic transactions @@ -46,6 +52,12 @@ var ( TxBytesGas uint64 = 1 EVMOutputGas uint64 = (common.AddressLength + wrappers.LongLen + hashing.HashLen) * TxBytesGas EVMInputGas uint64 = (common.AddressLength+wrappers.LongLen+hashing.HashLen+wrappers.LongLen)*TxBytesGas + secp256k1fx.CostPerSignature + // X2CRate is the conversion rate between the smallest denomination on the X-Chain + // 1 nAVAX and the smallest denomination on the C-Chain 1 wei. Where 1 nAVAX = 1 gWei. + // This is only required for AVAX because the denomination of 1 AVAX is 9 decimal + // places on the X and P chains, but is 18 decimal places within the EVM. + X2CRate = uint256.NewInt(X2CRateUint64) + x2cRateMinus1 = uint256.NewInt(x2cRateMinus1Uint64) ) // EVMOutput defines an output that is added to the EVM state created by import transactions @@ -98,7 +110,7 @@ func (in *EVMInput) Verify() error { case in == nil: return errNilInput case in.Amount == 0: - return errNoValueInput + return ErrNoValueInput case in.AssetID == ids.Empty: return errEmptyAssetID } @@ -115,6 +127,39 @@ type UnsignedTx interface { SignedBytes() []byte } +type Backend struct { + Ctx *snow.Context + Fx fx.Fx + Rules params.Rules + Bootstrapped bool + BlockFetcher BlockFetcher + SecpCache *secp256k1.RecoverCache +} + +type BlockFetcher interface { + LastAcceptedBlockInternal() snowman.Block + GetBlockInternal(context.Context, ids.ID) (snowman.Block, error) +} + +type AtomicBlockContext interface { + AtomicTxs() []*Tx + snowman.Block +} + +type StateDB interface { + AddBalance(common.Address, *uint256.Int) + AddBalanceMultiCoin(common.Address, common.Hash, *big.Int) + + SubBalance(common.Address, *uint256.Int) + SubBalanceMultiCoin(common.Address, common.Hash, *big.Int) + + GetBalance(common.Address) *uint256.Int + GetBalanceMultiCoin(common.Address, common.Hash) *big.Int + + GetNonce(common.Address) uint64 + SetNonce(common.Address, uint64) +} + // UnsignedAtomicTx is an unsigned operation that can be atomically accepted type UnsignedAtomicTx interface { UnsignedTx @@ -124,13 +169,14 @@ type UnsignedAtomicTx interface { // Verify attempts to verify that the transaction is well formed Verify(ctx *snow.Context, rules params.Rules) error // Attempts to verify this transaction with the provided state. - SemanticVerify(vm *VM, stx *Tx, parent *Block, baseFee *big.Int, rules params.Rules) error + // SemanticVerify this transaction is valid. + SemanticVerify(backend *Backend, stx *Tx, parent AtomicBlockContext, baseFee *big.Int) error // AtomicOps returns the blockchainID and set of atomic requests that // must be applied to shared memory for this transaction to be accepted. // The set of atomic requests must be returned in a consistent order. AtomicOps() (ids.ID, *atomic.Requests, error) - EVMStateTransfer(ctx *snow.Context, state *state.StateDB) error + EVMStateTransfer(ctx *snow.Context, state StateDB) error } // Tx is a signed transaction @@ -157,7 +203,7 @@ func (tx *Tx) Compare(other *Tx) int { // Sign this transaction with the provided signers func (tx *Tx) Sign(c codec.Manager, signers [][]*secp256k1.PrivateKey) error { - unsignedBytes, err := c.Marshal(codecVersion, &tx.UnsignedAtomicTx) + unsignedBytes, err := c.Marshal(CodecVersion, &tx.UnsignedAtomicTx) if err != nil { return fmt.Errorf("couldn't marshal UnsignedAtomicTx: %w", err) } @@ -178,7 +224,7 @@ func (tx *Tx) Sign(c codec.Manager, signers [][]*secp256k1.PrivateKey) error { tx.Creds = append(tx.Creds, cred) // Attach credential } - signedBytes, err := c.Marshal(codecVersion, tx) + signedBytes, err := c.Marshal(CodecVersion, tx) if err != nil { return fmt.Errorf("couldn't marshal Tx: %w", err) } @@ -216,7 +262,7 @@ func (tx *Tx) BlockFeeContribution(fixedFee bool, avaxAssetID ids.ID, baseFee *b // Calculate the amount of AVAX that has been burned above the required fee denominated // in C-Chain native 18 decimal places - blockFeeContribution := new(big.Int).Mul(new(big.Int).SetUint64(excessBurned), x2cRate.ToBig()) + blockFeeContribution := new(big.Int).Mul(new(big.Int).SetUint64(excessBurned), X2CRate.ToBig()) return blockFeeContribution, new(big.Int).SetUint64(gasUsed), nil } @@ -255,7 +301,7 @@ func CalculateDynamicFee(cost uint64, baseFee *big.Int) (uint64, error) { bigCost := new(big.Int).SetUint64(cost) fee := new(big.Int).Mul(bigCost, baseFee) feeToRoundUp := new(big.Int).Add(fee, x2cRateMinus1.ToBig()) - feeInNAVAX := new(big.Int).Div(feeToRoundUp, x2cRate.ToBig()) + feeInNAVAX := new(big.Int).Div(feeToRoundUp, X2CRate.ToBig()) if !feeInNAVAX.IsUint64() { // the fee is more than can fit in a uint64 return 0, errFeeOverflow @@ -266,36 +312,3 @@ func CalculateDynamicFee(cost uint64, baseFee *big.Int) (uint64, error) { func calcBytesCost(len int) uint64 { return uint64(len) * TxBytesGas } - -// mergeAtomicOps merges atomic requests represented by [txs] -// to the [output] map, depending on whether [chainID] is present in the map. -func mergeAtomicOps(txs []*Tx) (map[ids.ID]*atomic.Requests, error) { - if len(txs) > 1 { - // txs should be stored in order of txID to ensure consistency - // with txs initialized from the txID index. - copyTxs := make([]*Tx, len(txs)) - copy(copyTxs, txs) - utils.Sort(copyTxs) - txs = copyTxs - } - output := make(map[ids.ID]*atomic.Requests) - for _, tx := range txs { - chainID, txRequests, err := tx.UnsignedAtomicTx.AtomicOps() - if err != nil { - return nil, err - } - mergeAtomicOpsToMap(output, chainID, txRequests) - } - return output, nil -} - -// mergeAtomicOps merges atomic ops for [chainID] represented by [requests] -// to the [output] map provided. -func mergeAtomicOpsToMap(output map[ids.ID]*atomic.Requests, chainID ids.ID, requests *atomic.Requests) { - if request, exists := output[chainID]; exists { - request.PutRequests = append(request.PutRequests, requests.PutRequests...) - request.RemoveRequests = append(request.RemoveRequests, requests.RemoveRequests...) - } else { - output[chainID] = requests - } -} diff --git a/plugin/evm/atomic/utils.go b/plugin/evm/atomic/utils.go new file mode 100644 index 0000000000..8872e09861 --- /dev/null +++ b/plugin/evm/atomic/utils.go @@ -0,0 +1,32 @@ +// (c) 2019-2024, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package atomic + +import ( + "errors" + + "github.com/ava-labs/avalanchego/utils/crypto/secp256k1" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" +) + +var errInvalidAddr = errors.New("invalid hex address") + +// ParseEthAddress parses [addrStr] and returns an Ethereum address +func ParseEthAddress(addrStr string) (common.Address, error) { + if !common.IsHexAddress(addrStr) { + return common.Address{}, errInvalidAddr + } + return common.HexToAddress(addrStr), nil +} + +// GetEthAddress returns the ethereum address derived from [privKey] +func GetEthAddress(privKey *secp256k1.PrivateKey) common.Address { + return PublicKeyToEthAddress(privKey.PublicKey()) +} + +// PublicKeyToEthAddress returns the ethereum address derived from [pubKey] +func PublicKeyToEthAddress(pubKey *secp256k1.PublicKey) common.Address { + return crypto.PubkeyToAddress(*(pubKey.ToECDSA())) +} diff --git a/plugin/evm/atomic_backend.go b/plugin/evm/atomic_backend.go index 5a84ac3748..2420021d6f 100644 --- a/plugin/evm/atomic_backend.go +++ b/plugin/evm/atomic_backend.go @@ -8,13 +8,15 @@ import ( "fmt" "time" - "github.com/ava-labs/avalanchego/chains/atomic" + avalancheatomic "github.com/ava-labs/avalanchego/chains/atomic" "github.com/ava-labs/avalanchego/codec" "github.com/ava-labs/avalanchego/database" "github.com/ava-labs/avalanchego/database/prefixdb" "github.com/ava-labs/avalanchego/database/versiondb" "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/avalanchego/utils" "github.com/ava-labs/avalanchego/utils/wrappers" + "github.com/ava-labs/coreth/plugin/evm/atomic" syncclient "github.com/ava-labs/coreth/sync/client" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/log" @@ -32,7 +34,7 @@ type AtomicBackend interface { // and it's the caller's responsibility to call either Accept or Reject on // the AtomicState which can be retreived from GetVerifiedAtomicState to commit the // changes or abort them and free memory. - InsertTxs(blockHash common.Hash, blockHeight uint64, parentHash common.Hash, txs []*Tx) (common.Hash, error) + InsertTxs(blockHash common.Hash, blockHeight uint64, parentHash common.Hash, txs []*atomic.Tx) (common.Hash, error) // Returns an AtomicState corresponding to a block hash that has been inserted // but not Accepted or Rejected yet. @@ -73,7 +75,7 @@ type atomicBackend struct { bonusBlocks map[uint64]ids.ID // Map of height to blockID for blocks to skip indexing db *versiondb.Database // Underlying database metadataDB database.Database // Underlying database containing the atomic trie metadata - sharedMemory atomic.SharedMemory + sharedMemory avalancheatomic.SharedMemory repo AtomicTxRepository atomicTrie AtomicTrie @@ -84,7 +86,7 @@ type atomicBackend struct { // NewAtomicBackend creates an AtomicBackend from the specified dependencies func NewAtomicBackend( - db *versiondb.Database, sharedMemory atomic.SharedMemory, + db *versiondb.Database, sharedMemory avalancheatomic.SharedMemory, bonusBlocks map[uint64]ids.ID, repo AtomicTxRepository, lastAcceptedHeight uint64, lastAcceptedHash common.Hash, commitInterval uint64, ) (AtomicBackend, error) { @@ -150,7 +152,7 @@ func (a *atomicBackend) initialize(lastAcceptedHeight uint64) error { // iterate over the transactions, indexing them if the height is < commit height // otherwise, add the atomic operations from the transaction to the uncommittedOpsMap height = binary.BigEndian.Uint64(iter.Key()) - txs, err := ExtractAtomicTxs(iter.Value(), true, a.codec) + txs, err := atomic.ExtractAtomicTxs(iter.Value(), true, a.codec) if err != nil { return err } @@ -266,7 +268,7 @@ func (a *atomicBackend) ApplyToSharedMemory(lastAcceptedBlock uint64) error { it.Next() } - batchOps := make(map[ids.ID]*atomic.Requests) + batchOps := make(map[ids.ID]*avalancheatomic.Requests) for it.Next() { height := it.BlockNumber() if height > lastAcceptedBlock { @@ -318,7 +320,7 @@ func (a *atomicBackend) ApplyToSharedMemory(lastAcceptedBlock uint64) error { lastHeight = height lastBlockchainID = blockchainID putRequests, removeRequests = 0, 0 - batchOps = make(map[ids.ID]*atomic.Requests) + batchOps = make(map[ids.ID]*avalancheatomic.Requests) } } if err := it.Error(); err != nil { @@ -395,7 +397,7 @@ func (a *atomicBackend) SetLastAccepted(lastAcceptedHash common.Hash) { // and it's the caller's responsibility to call either Accept or Reject on // the AtomicState which can be retreived from GetVerifiedAtomicState to commit the // changes or abort them and free memory. -func (a *atomicBackend) InsertTxs(blockHash common.Hash, blockHeight uint64, parentHash common.Hash, txs []*Tx) (common.Hash, error) { +func (a *atomicBackend) InsertTxs(blockHash common.Hash, blockHeight uint64, parentHash common.Hash, txs []*atomic.Tx) (common.Hash, error) { // access the atomic trie at the parent block parentRoot, err := a.getAtomicRootAt(parentHash) if err != nil { @@ -455,3 +457,36 @@ func (a *atomicBackend) IsBonus(blockHeight uint64, blockHash common.Hash) bool func (a *atomicBackend) AtomicTrie() AtomicTrie { return a.atomicTrie } + +// mergeAtomicOps merges atomic requests represented by [txs] +// to the [output] map, depending on whether [chainID] is present in the map. +func mergeAtomicOps(txs []*atomic.Tx) (map[ids.ID]*avalancheatomic.Requests, error) { + if len(txs) > 1 { + // txs should be stored in order of txID to ensure consistency + // with txs initialized from the txID index. + copyTxs := make([]*atomic.Tx, len(txs)) + copy(copyTxs, txs) + utils.Sort(copyTxs) + txs = copyTxs + } + output := make(map[ids.ID]*avalancheatomic.Requests) + for _, tx := range txs { + chainID, txRequests, err := tx.UnsignedAtomicTx.AtomicOps() + if err != nil { + return nil, err + } + mergeAtomicOpsToMap(output, chainID, txRequests) + } + return output, nil +} + +// mergeAtomicOps merges atomic ops for [chainID] represented by [requests] +// to the [output] map provided. +func mergeAtomicOpsToMap(output map[ids.ID]*avalancheatomic.Requests, chainID ids.ID, requests *avalancheatomic.Requests) { + if request, exists := output[chainID]; exists { + request.PutRequests = append(request.PutRequests, requests.PutRequests...) + request.RemoveRequests = append(request.RemoveRequests, requests.RemoveRequests...) + } else { + output[chainID] = requests + } +} diff --git a/plugin/evm/atomic_state.go b/plugin/evm/atomic_state.go index 667e4c2517..911f1afb3a 100644 --- a/plugin/evm/atomic_state.go +++ b/plugin/evm/atomic_state.go @@ -6,9 +6,10 @@ package evm import ( "fmt" - "github.com/ava-labs/avalanchego/chains/atomic" + avalancheatomic "github.com/ava-labs/avalanchego/chains/atomic" "github.com/ava-labs/avalanchego/database" "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/coreth/plugin/evm/atomic" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/log" ) @@ -25,7 +26,7 @@ type AtomicState interface { Root() common.Hash // Accept applies the state change to VM's persistent storage // Changes are persisted atomically along with the provided [commitBatch]. - Accept(commitBatch database.Batch, requests map[ids.ID]*atomic.Requests) error + Accept(commitBatch database.Batch, requests map[ids.ID]*avalancheatomic.Requests) error // Reject frees memory associated with the state change. Reject() error } @@ -36,8 +37,8 @@ type atomicState struct { backend *atomicBackend blockHash common.Hash blockHeight uint64 - txs []*Tx - atomicOps map[ids.ID]*atomic.Requests + txs []*atomic.Tx + atomicOps map[ids.ID]*avalancheatomic.Requests atomicRoot common.Hash } @@ -46,7 +47,7 @@ func (a *atomicState) Root() common.Hash { } // Accept applies the state change to VM's persistent storage. -func (a *atomicState) Accept(commitBatch database.Batch, requests map[ids.ID]*atomic.Requests) error { +func (a *atomicState) Accept(commitBatch database.Batch, requests map[ids.ID]*avalancheatomic.Requests) error { // Add the new requests to the batch to be accepted for chainID, requests := range requests { mergeAtomicOpsToMap(a.atomicOps, chainID, requests) @@ -83,7 +84,7 @@ func (a *atomicState) Accept(commitBatch database.Batch, requests map[ids.ID]*at // to shared memory. if a.backend.IsBonus(a.blockHeight, a.blockHash) { log.Info("skipping atomic tx acceptance on bonus block", "block", a.blockHash) - return atomic.WriteAll(commitBatch, atomicChangesBatch) + return avalancheatomic.WriteAll(commitBatch, atomicChangesBatch) } // Otherwise, atomically commit pending changes in the version db with diff --git a/plugin/evm/atomic_trie.go b/plugin/evm/atomic_trie.go index 2760850d18..d734268e23 100644 --- a/plugin/evm/atomic_trie.go +++ b/plugin/evm/atomic_trie.go @@ -7,7 +7,7 @@ import ( "fmt" "time" - "github.com/ava-labs/avalanchego/chains/atomic" + avalancheatomic "github.com/ava-labs/avalanchego/chains/atomic" "github.com/ava-labs/avalanchego/codec" "github.com/ava-labs/avalanchego/database" "github.com/ava-labs/avalanchego/ids" @@ -17,6 +17,7 @@ import ( "github.com/ava-labs/coreth/core" "github.com/ava-labs/coreth/core/rawdb" "github.com/ava-labs/coreth/core/types" + "github.com/ava-labs/coreth/plugin/evm/atomic" "github.com/ava-labs/coreth/trie" "github.com/ava-labs/coreth/trie/trienode" "github.com/ava-labs/coreth/triedb" @@ -52,7 +53,7 @@ type AtomicTrie interface { OpenTrie(hash common.Hash) (*trie.Trie, error) // UpdateTrie updates [tr] to inlude atomicOps for height. - UpdateTrie(tr *trie.Trie, height uint64, atomicOps map[ids.ID]*atomic.Requests) error + UpdateTrie(tr *trie.Trie, height uint64, atomicOps map[ids.ID]*avalancheatomic.Requests) error // Iterator returns an AtomicTrieIterator to iterate the trie at the given // root hash starting at [cursor]. @@ -108,7 +109,7 @@ type AtomicTrieIterator interface { // AtomicOps returns a map of blockchainIDs to the set of atomic requests // for that blockchainID at the current block number - AtomicOps() *atomic.Requests + AtomicOps() *avalancheatomic.Requests // Error returns error, if any encountered during this iteration Error() error @@ -221,9 +222,9 @@ func (a *atomicTrie) commit(height uint64, root common.Hash) error { return a.updateLastCommitted(root, height) } -func (a *atomicTrie) UpdateTrie(trie *trie.Trie, height uint64, atomicOps map[ids.ID]*atomic.Requests) error { +func (a *atomicTrie) UpdateTrie(trie *trie.Trie, height uint64, atomicOps map[ids.ID]*avalancheatomic.Requests) error { for blockchainID, requests := range atomicOps { - valueBytes, err := a.codec.Marshal(codecVersion, requests) + valueBytes, err := a.codec.Marshal(atomic.CodecVersion, requests) if err != nil { // highly unlikely but possible if atomic.Element // has a change that is unsupported by the codec diff --git a/plugin/evm/atomic_trie_test.go b/plugin/evm/atomic_trie_test.go index 5334c87101..193226f588 100644 --- a/plugin/evm/atomic_trie_test.go +++ b/plugin/evm/atomic_trie_test.go @@ -11,7 +11,7 @@ import ( "github.com/stretchr/testify/assert" - "github.com/ava-labs/avalanchego/chains/atomic" + avalancheatomic "github.com/ava-labs/avalanchego/chains/atomic" "github.com/ava-labs/avalanchego/database" "github.com/ava-labs/avalanchego/database/leveldb" "github.com/ava-labs/avalanchego/database/memdb" @@ -19,24 +19,25 @@ import ( "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/utils/logging" "github.com/ava-labs/avalanchego/utils/wrappers" + "github.com/ava-labs/coreth/plugin/evm/atomic" "github.com/ethereum/go-ethereum/common" ) const testCommitInterval = 100 -func (tx *Tx) mustAtomicOps() map[ids.ID]*atomic.Requests { +func mustAtomicOps(tx *atomic.Tx) map[ids.ID]*avalancheatomic.Requests { id, reqs, err := tx.AtomicOps() if err != nil { panic(err) } - return map[ids.ID]*atomic.Requests{id: reqs} + return map[ids.ID]*avalancheatomic.Requests{id: reqs} } // indexAtomicTxs updates [tr] with entries in [atomicOps] at height by creating // a new snapshot, calculating a new root, and calling InsertTrie followed // by AcceptTrie on the new root. -func indexAtomicTxs(tr AtomicTrie, height uint64, atomicOps map[ids.ID]*atomic.Requests) error { +func indexAtomicTxs(tr AtomicTrie, height uint64, atomicOps map[ids.ID]*avalancheatomic.Requests) error { snapshot, err := tr.OpenTrie(tr.LastAcceptedRoot()) if err != nil { return err @@ -143,7 +144,7 @@ func TestAtomicTrieInitialize(t *testing.T) { if err != nil { t.Fatal(err) } - operationsMap := make(map[uint64]map[ids.ID]*atomic.Requests) + operationsMap := make(map[uint64]map[ids.ID]*avalancheatomic.Requests) writeTxs(t, repo, 1, test.lastAcceptedHeight+1, test.numTxsPerBlock, nil, operationsMap) // Construct the atomic trie for the first time @@ -230,7 +231,7 @@ func TestIndexerInitializesOnlyOnce(t *testing.T) { codec := testTxCodec() repo, err := NewAtomicTxRepository(db, codec, lastAcceptedHeight) assert.NoError(t, err) - operationsMap := make(map[uint64]map[ids.ID]*atomic.Requests) + operationsMap := make(map[uint64]map[ids.ID]*avalancheatomic.Requests) writeTxs(t, repo, 1, lastAcceptedHeight+1, constTxsPerHeight(2), nil, operationsMap) // Initialize atomic repository @@ -246,7 +247,7 @@ func TestIndexerInitializesOnlyOnce(t *testing.T) { // re-initialize the atomic trie since initialize is not supposed to run again the height // at the trie should still be the old height with the old commit hash without any changes. // This scenario is not realistic, but is used to test potential double initialization behavior. - err = repo.Write(15, []*Tx{testDataExportTx()}) + err = repo.Write(15, []*atomic.Tx{testDataExportTx()}) assert.NoError(t, err) // Re-initialize the atomic trie @@ -281,7 +282,7 @@ func TestIndexerWriteAndRead(t *testing.T) { // process 305 blocks so that we get three commits (100, 200, 300) for height := uint64(1); height <= testCommitInterval*3+5; /*=305*/ height++ { - atomicRequests := testDataImportTx().mustAtomicOps() + atomicRequests := mustAtomicOps(testDataImportTx()) err := indexAtomicTxs(atomicTrie, height, atomicRequests) assert.NoError(t, err) if height%testCommitInterval == 0 { @@ -314,9 +315,9 @@ func TestAtomicOpsAreNotTxOrderDependent(t *testing.T) { for height := uint64(0); height <= testCommitInterval; /*=205*/ height++ { tx1 := testDataImportTx() tx2 := testDataImportTx() - atomicRequests1, err := mergeAtomicOps([]*Tx{tx1, tx2}) + atomicRequests1, err := mergeAtomicOps([]*atomic.Tx{tx1, tx2}) assert.NoError(t, err) - atomicRequests2, err := mergeAtomicOps([]*Tx{tx2, tx1}) + atomicRequests2, err := mergeAtomicOps([]*atomic.Tx{tx2, tx1}) assert.NoError(t, err) err = indexAtomicTxs(atomicTrie1, height, atomicRequests1) @@ -343,7 +344,7 @@ func TestAtomicTrieDoesNotSkipBonusBlocks(t *testing.T) { if err != nil { t.Fatal(err) } - operationsMap := make(map[uint64]map[ids.ID]*atomic.Requests) + operationsMap := make(map[uint64]map[ids.ID]*avalancheatomic.Requests) writeTxs(t, repo, 1, lastAcceptedHeight, constTxsPerHeight(numTxsPerBlock), nil, operationsMap) bonusBlocks := map[uint64]ids.ID{ @@ -368,9 +369,9 @@ func TestAtomicTrieDoesNotSkipBonusBlocks(t *testing.T) { func TestIndexingNilShouldNotImpactTrie(t *testing.T) { // operations to index - ops := make([]map[ids.ID]*atomic.Requests, 0) + ops := make([]map[ids.ID]*avalancheatomic.Requests, 0) for i := 0; i <= testCommitInterval; i++ { - ops = append(ops, testDataImportTx().mustAtomicOps()) + ops = append(ops, mustAtomicOps(testDataImportTx())) } // without nils @@ -411,19 +412,19 @@ func TestIndexingNilShouldNotImpactTrie(t *testing.T) { } type sharedMemories struct { - thisChain atomic.SharedMemory - peerChain atomic.SharedMemory + thisChain avalancheatomic.SharedMemory + peerChain avalancheatomic.SharedMemory thisChainID ids.ID peerChainID ids.ID } -func (s *sharedMemories) addItemsToBeRemovedToPeerChain(ops map[ids.ID]*atomic.Requests) error { +func (s *sharedMemories) addItemsToBeRemovedToPeerChain(ops map[ids.ID]*avalancheatomic.Requests) error { for _, reqs := range ops { - puts := make(map[ids.ID]*atomic.Requests) - puts[s.thisChainID] = &atomic.Requests{} + puts := make(map[ids.ID]*avalancheatomic.Requests) + puts[s.thisChainID] = &avalancheatomic.Requests{} for _, key := range reqs.RemoveRequests { val := []byte{0x1} - puts[s.thisChainID].PutRequests = append(puts[s.thisChainID].PutRequests, &atomic.Element{Key: key, Value: val}) + puts[s.thisChainID].PutRequests = append(puts[s.thisChainID].PutRequests, &avalancheatomic.Element{Key: key, Value: val}) } if err := s.peerChain.Apply(puts); err != nil { return err @@ -432,7 +433,7 @@ func (s *sharedMemories) addItemsToBeRemovedToPeerChain(ops map[ids.ID]*atomic.R return nil } -func (s *sharedMemories) assertOpsApplied(t *testing.T, ops map[ids.ID]*atomic.Requests) { +func (s *sharedMemories) assertOpsApplied(t *testing.T, ops map[ids.ID]*avalancheatomic.Requests) { t.Helper() for _, reqs := range ops { // should be able to get put requests @@ -452,7 +453,7 @@ func (s *sharedMemories) assertOpsApplied(t *testing.T, ops map[ids.ID]*atomic.R } } -func (s *sharedMemories) assertOpsNotApplied(t *testing.T, ops map[ids.ID]*atomic.Requests) { +func (s *sharedMemories) assertOpsNotApplied(t *testing.T, ops map[ids.ID]*avalancheatomic.Requests) { t.Helper() for _, reqs := range ops { // should not be able to get put requests @@ -470,7 +471,7 @@ func (s *sharedMemories) assertOpsNotApplied(t *testing.T, ops map[ids.ID]*atomi } } -func newSharedMemories(atomicMemory *atomic.Memory, thisChainID, peerChainID ids.ID) *sharedMemories { +func newSharedMemories(atomicMemory *avalancheatomic.Memory, thisChainID, peerChainID ids.ID) *sharedMemories { return &sharedMemories{ thisChain: atomicMemory.NewSharedMemory(thisChainID), peerChain: atomicMemory.NewSharedMemory(peerChainID), @@ -529,11 +530,11 @@ func TestApplyToSharedMemory(t *testing.T) { codec := testTxCodec() repo, err := NewAtomicTxRepository(db, codec, test.lastAcceptedHeight) assert.NoError(t, err) - operationsMap := make(map[uint64]map[ids.ID]*atomic.Requests) + operationsMap := make(map[uint64]map[ids.ID]*avalancheatomic.Requests) writeTxs(t, repo, 1, test.lastAcceptedHeight+1, constTxsPerHeight(2), nil, operationsMap) // Initialize atomic repository - m := atomic.NewMemory(db) + m := avalancheatomic.NewMemory(db) sharedMemories := newSharedMemories(m, testCChainID, blockChainID) backend, err := NewAtomicBackend(db, sharedMemories.thisChain, test.bonusBlockHeights, repo, test.lastAcceptedHeight, common.Hash{}, test.commitInterval) assert.NoError(t, err) @@ -594,7 +595,7 @@ func BenchmarkAtomicTrieInit(b *testing.B) { db := versiondb.New(memdb.New()) codec := testTxCodec() - operationsMap := make(map[uint64]map[ids.ID]*atomic.Requests) + operationsMap := make(map[uint64]map[ids.ID]*avalancheatomic.Requests) lastAcceptedHeight := uint64(25000) // add 25000 * 3 = 75000 transactions @@ -629,7 +630,7 @@ func BenchmarkAtomicTrieIterate(b *testing.B) { db := versiondb.New(memdb.New()) codec := testTxCodec() - operationsMap := make(map[uint64]map[ids.ID]*atomic.Requests) + operationsMap := make(map[uint64]map[ids.ID]*avalancheatomic.Requests) lastAcceptedHeight := uint64(25_000) // add 25000 * 3 = 75000 transactions diff --git a/plugin/evm/atomic_tx_repository.go b/plugin/evm/atomic_tx_repository.go index 4ee44576fe..d1074f60f2 100644 --- a/plugin/evm/atomic_tx_repository.go +++ b/plugin/evm/atomic_tx_repository.go @@ -19,6 +19,7 @@ import ( "github.com/ava-labs/avalanchego/utils" "github.com/ava-labs/avalanchego/utils/units" "github.com/ava-labs/avalanchego/utils/wrappers" + "github.com/ava-labs/coreth/plugin/evm/atomic" ) const ( @@ -39,10 +40,10 @@ var ( // atomic transactions type AtomicTxRepository interface { GetIndexHeight() (uint64, error) - GetByTxID(txID ids.ID) (*Tx, uint64, error) - GetByHeight(height uint64) ([]*Tx, error) - Write(height uint64, txs []*Tx) error - WriteBonus(height uint64, txs []*Tx) error + GetByTxID(txID ids.ID) (*atomic.Tx, uint64, error) + GetByHeight(height uint64) ([]*atomic.Tx, error) + Write(height uint64, txs []*atomic.Tx) error + WriteBonus(height uint64, txs []*atomic.Tx) error IterateByHeight(start uint64) database.Iterator Codec() codec.Manager @@ -136,7 +137,7 @@ func (a *atomicTxRepository) initializeHeightIndex(lastAcceptedHeight uint64) er // Get the tx iter is pointing to, len(txs) == 1 is expected here. txBytes := iterValue[wrappers.LongLen+wrappers.IntLen:] - tx, err := ExtractAtomicTx(txBytes, a.codec) + tx, err := atomic.ExtractAtomicTx(txBytes, a.codec) if err != nil { return err } @@ -198,10 +199,10 @@ func (a *atomicTxRepository) GetIndexHeight() (uint64, error) { return indexHeight, nil } -// GetByTxID queries [acceptedAtomicTxDB] for the [txID], parses a [*Tx] object +// GetByTxID queries [acceptedAtomicTxDB] for the [txID], parses a [*atomic.Tx] object // if an entry is found, and returns it with the block height the atomic tx it // represents was accepted on, along with an optional error. -func (a *atomicTxRepository) GetByTxID(txID ids.ID) (*Tx, uint64, error) { +func (a *atomicTxRepository) GetByTxID(txID ids.ID) (*atomic.Tx, uint64, error) { indexedTxBytes, err := a.acceptedAtomicTxDB.Get(txID[:]) if err != nil { return nil, 0, err @@ -215,7 +216,7 @@ func (a *atomicTxRepository) GetByTxID(txID ids.ID) (*Tx, uint64, error) { packer := wrappers.Packer{Bytes: indexedTxBytes} height := packer.UnpackLong() txBytes := packer.UnpackBytes() - tx, err := ExtractAtomicTx(txBytes, a.codec) + tx, err := atomic.ExtractAtomicTx(txBytes, a.codec) if err != nil { return nil, 0, err } @@ -229,40 +230,40 @@ func (a *atomicTxRepository) GetByTxID(txID ids.ID) (*Tx, uint64, error) { // no atomic transactions in the block accepted at [height]. // If [height] is greater than the last accepted height, then this will always return // [database.ErrNotFound] -func (a *atomicTxRepository) GetByHeight(height uint64) ([]*Tx, error) { +func (a *atomicTxRepository) GetByHeight(height uint64) ([]*atomic.Tx, error) { heightBytes := make([]byte, wrappers.LongLen) binary.BigEndian.PutUint64(heightBytes, height) return a.getByHeightBytes(heightBytes) } -func (a *atomicTxRepository) getByHeightBytes(heightBytes []byte) ([]*Tx, error) { +func (a *atomicTxRepository) getByHeightBytes(heightBytes []byte) ([]*atomic.Tx, error) { txsBytes, err := a.acceptedAtomicTxByHeightDB.Get(heightBytes) if err != nil { return nil, err } - return ExtractAtomicTxsBatch(txsBytes, a.codec) + return atomic.ExtractAtomicTxsBatch(txsBytes, a.codec) } // Write updates indexes maintained on atomic txs, so they can be queried // by txID or height. This method must be called only once per height, // and [txs] must include all atomic txs for the block accepted at the // corresponding height. -func (a *atomicTxRepository) Write(height uint64, txs []*Tx) error { +func (a *atomicTxRepository) Write(height uint64, txs []*atomic.Tx) error { return a.write(height, txs, false) } // WriteBonus is similar to Write, except the [txID] => [height] is not // overwritten if already exists. -func (a *atomicTxRepository) WriteBonus(height uint64, txs []*Tx) error { +func (a *atomicTxRepository) WriteBonus(height uint64, txs []*atomic.Tx) error { return a.write(height, txs, true) } -func (a *atomicTxRepository) write(height uint64, txs []*Tx, bonus bool) error { +func (a *atomicTxRepository) write(height uint64, txs []*atomic.Tx, bonus bool) error { if len(txs) > 1 { // txs should be stored in order of txID to ensure consistency // with txs initialized from the txID index. - copyTxs := make([]*Tx, len(txs)) + copyTxs := make([]*atomic.Tx, len(txs)) copy(copyTxs, txs) utils.Sort(copyTxs) txs = copyTxs @@ -300,8 +301,8 @@ func (a *atomicTxRepository) write(height uint64, txs []*Tx, bonus bool) error { // indexTxByID writes [tx] into the [acceptedAtomicTxDB] stored as // [height] + [tx bytes] -func (a *atomicTxRepository) indexTxByID(heightBytes []byte, tx *Tx) error { - txBytes, err := a.codec.Marshal(codecVersion, tx) +func (a *atomicTxRepository) indexTxByID(heightBytes []byte, tx *atomic.Tx) error { + txBytes, err := a.codec.Marshal(atomic.CodecVersion, tx) if err != nil { return err } @@ -320,8 +321,8 @@ func (a *atomicTxRepository) indexTxByID(heightBytes []byte, tx *Tx) error { } // indexTxsAtHeight adds [height] -> [txs] to the [acceptedAtomicTxByHeightDB] -func (a *atomicTxRepository) indexTxsAtHeight(heightBytes []byte, txs []*Tx) error { - txsBytes, err := a.codec.Marshal(codecVersion, txs) +func (a *atomicTxRepository) indexTxsAtHeight(heightBytes []byte, txs []*atomic.Tx) error { + txsBytes, err := a.codec.Marshal(atomic.CodecVersion, txs) if err != nil { return err } @@ -335,7 +336,7 @@ func (a *atomicTxRepository) indexTxsAtHeight(heightBytes []byte, txs []*Tx) err // [tx] to the slice of transactions stored there. // This function is used while initializing the atomic repository to re-index the atomic transactions // by txID into the height -> txs index. -func (a *atomicTxRepository) appendTxToHeightIndex(heightBytes []byte, tx *Tx) error { +func (a *atomicTxRepository) appendTxToHeightIndex(heightBytes []byte, tx *atomic.Tx) error { txs, err := a.getByHeightBytes(heightBytes) if err != nil && err != database.ErrNotFound { return err diff --git a/plugin/evm/atomic_tx_repository_test.go b/plugin/evm/atomic_tx_repository_test.go index b52860d57d..091bcd8f56 100644 --- a/plugin/evm/atomic_tx_repository_test.go +++ b/plugin/evm/atomic_tx_repository_test.go @@ -7,11 +7,12 @@ import ( "encoding/binary" "testing" - "github.com/ava-labs/avalanchego/chains/atomic" + avalancheatomic "github.com/ava-labs/avalanchego/chains/atomic" "github.com/ava-labs/avalanchego/database" "github.com/ava-labs/avalanchego/database/prefixdb" "github.com/ava-labs/avalanchego/database/versiondb" "github.com/ava-labs/avalanchego/utils" + "github.com/ava-labs/coreth/plugin/evm/atomic" "github.com/ethereum/go-ethereum/common" "github.com/ava-labs/avalanchego/codec" @@ -27,13 +28,13 @@ import ( // addTxs writes [txsPerHeight] txs for heights ranging in [fromHeight, toHeight) directly to [acceptedAtomicTxDB], // storing the resulting transactions in [txMap] if non-nil and the resulting atomic operations in [operationsMap] // if non-nil. -func addTxs(t testing.TB, codec codec.Manager, acceptedAtomicTxDB database.Database, fromHeight uint64, toHeight uint64, txsPerHeight int, txMap map[uint64][]*Tx, operationsMap map[uint64]map[ids.ID]*atomic.Requests) { +func addTxs(t testing.TB, codec codec.Manager, acceptedAtomicTxDB database.Database, fromHeight uint64, toHeight uint64, txsPerHeight int, txMap map[uint64][]*atomic.Tx, operationsMap map[uint64]map[ids.ID]*avalancheatomic.Requests) { for height := fromHeight; height < toHeight; height++ { - txs := make([]*Tx, 0, txsPerHeight) + txs := make([]*atomic.Tx, 0, txsPerHeight) for i := 0; i < txsPerHeight; i++ { tx := newTestTx() txs = append(txs, tx) - txBytes, err := codec.Marshal(codecVersion, tx) + txBytes, err := codec.Marshal(atomic.CodecVersion, tx) assert.NoError(t, err) // Write atomic transactions to the [acceptedAtomicTxDB] @@ -70,7 +71,7 @@ func constTxsPerHeight(txCount int) func(uint64) int { // storing the resulting transactions in [txMap] if non-nil and the resulting atomic operations in [operationsMap] // if non-nil. func writeTxs(t testing.TB, repo AtomicTxRepository, fromHeight uint64, toHeight uint64, - txsPerHeight func(height uint64) int, txMap map[uint64][]*Tx, operationsMap map[uint64]map[ids.ID]*atomic.Requests, + txsPerHeight func(height uint64) int, txMap map[uint64][]*atomic.Tx, operationsMap map[uint64]map[ids.ID]*avalancheatomic.Requests, ) { for height := fromHeight; height < toHeight; height++ { txs := newTestTxs(txsPerHeight(height)) @@ -95,7 +96,7 @@ func writeTxs(t testing.TB, repo AtomicTxRepository, fromHeight uint64, toHeight } // verifyTxs asserts [repo] can find all txs in [txMap] by height and txID -func verifyTxs(t testing.TB, repo AtomicTxRepository, txMap map[uint64][]*Tx) { +func verifyTxs(t testing.TB, repo AtomicTxRepository, txMap map[uint64][]*atomic.Tx) { // We should be able to fetch indexed txs by height: for height, expectedTxs := range txMap { txs, err := repo.GetByHeight(height) @@ -115,7 +116,7 @@ func verifyTxs(t testing.TB, repo AtomicTxRepository, txMap map[uint64][]*Tx) { // verifyOperations creates an iterator over the atomicTrie at [rootHash] and verifies that the all of the operations in the trie in the interval [from, to] are identical to // the atomic operations contained in [operationsMap] on the same interval. -func verifyOperations(t testing.TB, atomicTrie AtomicTrie, codec codec.Manager, rootHash common.Hash, from, to uint64, operationsMap map[uint64]map[ids.ID]*atomic.Requests) { +func verifyOperations(t testing.TB, atomicTrie AtomicTrie, codec codec.Manager, rootHash common.Hash, from, to uint64, operationsMap map[uint64]map[ids.ID]*avalancheatomic.Requests) { t.Helper() // Start the iterator at [from] @@ -187,7 +188,7 @@ func TestAtomicRepositoryReadWriteSingleTx(t *testing.T) { if err != nil { t.Fatal(err) } - txMap := make(map[uint64][]*Tx) + txMap := make(map[uint64][]*atomic.Tx) writeTxs(t, repo, 1, 100, constTxsPerHeight(1), txMap, nil) verifyTxs(t, repo, txMap) @@ -200,7 +201,7 @@ func TestAtomicRepositoryReadWriteMultipleTxs(t *testing.T) { if err != nil { t.Fatal(err) } - txMap := make(map[uint64][]*Tx) + txMap := make(map[uint64][]*atomic.Tx) writeTxs(t, repo, 1, 100, constTxsPerHeight(10), txMap, nil) verifyTxs(t, repo, txMap) @@ -211,7 +212,7 @@ func TestAtomicRepositoryPreAP5Migration(t *testing.T) { codec := testTxCodec() acceptedAtomicTxDB := prefixdb.New(atomicTxIDDBPrefix, db) - txMap := make(map[uint64][]*Tx) + txMap := make(map[uint64][]*atomic.Tx) addTxs(t, codec, acceptedAtomicTxDB, 1, 100, 1, txMap, nil) if err := db.Commit(); err != nil { t.Fatal(err) @@ -236,7 +237,7 @@ func TestAtomicRepositoryPostAP5Migration(t *testing.T) { codec := testTxCodec() acceptedAtomicTxDB := prefixdb.New(atomicTxIDDBPrefix, db) - txMap := make(map[uint64][]*Tx) + txMap := make(map[uint64][]*atomic.Tx) addTxs(t, codec, acceptedAtomicTxDB, 1, 100, 1, txMap, nil) addTxs(t, codec, acceptedAtomicTxDB, 100, 200, 10, txMap, nil) if err := db.Commit(); err != nil { @@ -261,7 +262,7 @@ func benchAtomicRepositoryIndex10_000(b *testing.B, maxHeight uint64, txsPerHeig codec := testTxCodec() acceptedAtomicTxDB := prefixdb.New(atomicTxIDDBPrefix, db) - txMap := make(map[uint64][]*Tx) + txMap := make(map[uint64][]*atomic.Tx) addTxs(b, codec, acceptedAtomicTxDB, 0, maxHeight, txsPerHeight, txMap, nil) if err := db.Commit(); err != nil { diff --git a/plugin/evm/block.go b/plugin/evm/block.go index a8d9084464..99451cb071 100644 --- a/plugin/evm/block.go +++ b/plugin/evm/block.go @@ -18,6 +18,7 @@ import ( "github.com/ava-labs/coreth/core/rawdb" "github.com/ava-labs/coreth/core/types" "github.com/ava-labs/coreth/params" + "github.com/ava-labs/coreth/plugin/evm/atomic" "github.com/ava-labs/coreth/precompile/precompileconfig" "github.com/ava-labs/coreth/predicate" @@ -31,9 +32,7 @@ var ( _ block.WithVerifyContext = (*Block)(nil) ) -var ( - errMissingUTXOs = errors.New("missing UTXOs") -) +var errMissingUTXOs = errors.New("missing UTXOs") // readMainnetBonusBlocks returns maps of bonus block numbers to block IDs. // Note bonus blocks are indexed in the atomic trie. @@ -114,13 +113,13 @@ type Block struct { id ids.ID ethBlock *types.Block vm *VM - atomicTxs []*Tx + atomicTxs []*atomic.Tx } // newBlock returns a new Block wrapping the ethBlock type and implementing the snowman.Block interface func (vm *VM) newBlock(ethBlock *types.Block) (*Block, error) { isApricotPhase5 := vm.chainConfig.IsApricotPhase5(ethBlock.Time()) - atomicTxs, err := ExtractAtomicTxs(ethBlock.ExtData(), isApricotPhase5, vm.codec) + atomicTxs, err := atomic.ExtractAtomicTxs(ethBlock.ExtData(), isApricotPhase5, atomic.Codec) if err != nil { return nil, err } @@ -136,6 +135,8 @@ func (vm *VM) newBlock(ethBlock *types.Block) (*Block, error) { // ID implements the snowman.Block interface func (b *Block) ID() ids.ID { return b.id } +func (b *Block) AtomicTxs() []*atomic.Tx { return b.atomicTxs } + // Accept implements the snowman.Block interface func (b *Block) Accept(context.Context) error { vm := b.vm diff --git a/plugin/evm/client.go b/plugin/evm/client/client.go similarity index 78% rename from plugin/evm/client.go rename to plugin/evm/client/client.go index 4701c22b9c..f92e55e59f 100644 --- a/plugin/evm/client.go +++ b/plugin/evm/client/client.go @@ -1,13 +1,14 @@ // (c) 2019-2020, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. -package evm +package client import ( "context" "fmt" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" "golang.org/x/exp/slog" "github.com/ava-labs/avalanchego/api" @@ -17,6 +18,7 @@ import ( "github.com/ava-labs/avalanchego/utils/formatting/address" "github.com/ava-labs/avalanchego/utils/json" "github.com/ava-labs/avalanchego/utils/rpc" + "github.com/ava-labs/coreth/plugin/evm/atomic" ) // Interface compliance @@ -25,7 +27,7 @@ var _ Client = (*client)(nil) // Client interface for interacting with EVM [chain] type Client interface { IssueTx(ctx context.Context, txBytes []byte, options ...rpc.Option) (ids.ID, error) - GetAtomicTxStatus(ctx context.Context, txID ids.ID, options ...rpc.Option) (Status, error) + GetAtomicTxStatus(ctx context.Context, txID ids.ID, options ...rpc.Option) (atomic.Status, error) GetAtomicTx(ctx context.Context, txID ids.ID, options ...rpc.Option) ([]byte, error) GetAtomicUTXOs(ctx context.Context, addrs []ids.ShortID, sourceChain string, limit uint32, startAddress ids.ShortID, startUTXOID ids.ID, options ...rpc.Option) ([][]byte, ids.ShortID, ids.ID, error) ExportKey(ctx context.Context, userPass api.UserPass, addr common.Address, options ...rpc.Option) (*secp256k1.PrivateKey, string, error) @@ -38,7 +40,7 @@ type Client interface { MemoryProfile(ctx context.Context, options ...rpc.Option) error LockProfile(ctx context.Context, options ...rpc.Option) error SetLogLevel(ctx context.Context, level slog.Level, options ...rpc.Option) error - GetVMConfig(ctx context.Context, options ...rpc.Option) (*Config, error) + // GetVMConfig(ctx context.Context, options ...rpc.Option) (*Config, error) } // Client implementation for interacting with EVM [chain] @@ -74,8 +76,14 @@ func (c *client) IssueTx(ctx context.Context, txBytes []byte, options ...rpc.Opt return res.TxID, err } +// GetAtomicTxStatusReply defines the GetAtomicTxStatus replies returned from the API +type GetAtomicTxStatusReply struct { + Status atomic.Status `json:"status"` + BlockHeight *json.Uint64 `json:"blockHeight,omitempty"` +} + // GetAtomicTxStatus returns the status of [txID] -func (c *client) GetAtomicTxStatus(ctx context.Context, txID ids.ID, options ...rpc.Option) (Status, error) { +func (c *client) GetAtomicTxStatus(ctx context.Context, txID ids.ID, options ...rpc.Option) (atomic.Status, error) { res := &GetAtomicTxStatusReply{} err := c.requester.SendRequest(ctx, "avax.getAtomicTxStatus", &api.JSONTxID{ TxID: txID, @@ -131,6 +139,19 @@ func (c *client) GetAtomicUTXOs(ctx context.Context, addrs []ids.ShortID, source return utxos, endAddr, endUTXOID, err } +// ExportKeyArgs are arguments for ExportKey +type ExportKeyArgs struct { + api.UserPass + Address string `json:"address"` +} + +// ExportKeyReply is the response for ExportKey +type ExportKeyReply struct { + // The decrypted PrivateKey for the Address provided in the arguments + PrivateKey *secp256k1.PrivateKey `json:"privateKey"` + PrivateKeyHex string `json:"privateKeyHex"` +} + // ExportKey returns the private key corresponding to [addr] controlled by [user] // in both Avalanche standard format and hex format func (c *client) ExportKey(ctx context.Context, user api.UserPass, addr common.Address, options ...rpc.Option) (*secp256k1.PrivateKey, string, error) { @@ -142,6 +163,12 @@ func (c *client) ExportKey(ctx context.Context, user api.UserPass, addr common.A return res.PrivateKey, res.PrivateKeyHex, err } +// ImportKeyArgs are arguments for ImportKey +type ImportKeyArgs struct { + api.UserPass + PrivateKey *secp256k1.PrivateKey `json:"privateKey"` +} + // ImportKey imports [privateKey] to [user] func (c *client) ImportKey(ctx context.Context, user api.UserPass, privateKey *secp256k1.PrivateKey, options ...rpc.Option) (common.Address, error) { res := &api.JSONAddress{} @@ -152,7 +179,21 @@ func (c *client) ImportKey(ctx context.Context, user api.UserPass, privateKey *s if err != nil { return common.Address{}, err } - return ParseEthAddress(res.Address) + return atomic.ParseEthAddress(res.Address) +} + +// ImportArgs are arguments for passing into Import requests +type ImportArgs struct { + api.UserPass + + // Fee that should be used when creating the tx + BaseFee *hexutil.Big `json:"baseFee"` + + // Chain the funds are coming from + SourceChain string `json:"sourceChain"` + + // The address that will receive the imported funds + To common.Address `json:"to"` } // Import sends an import transaction to import funds from [sourceChain] and @@ -180,6 +221,32 @@ func (c *client) ExportAVAX( return c.Export(ctx, user, amount, to, targetChain, "AVAX", options...) } +// ExportAVAXArgs are the arguments to ExportAVAX +type ExportAVAXArgs struct { + api.UserPass + + // Fee that should be used when creating the tx + BaseFee *hexutil.Big `json:"baseFee"` + + // Amount of asset to send + Amount json.Uint64 `json:"amount"` + + // Chain the funds are going to. Optional. Used if To address does not + // include the chainID. + TargetChain string `json:"targetChain"` + + // ID of the address that will receive the AVAX. This address may include + // the chainID, which is used to determine what the destination chain is. + To string `json:"to"` +} + +// ExportArgs are the arguments to Export +type ExportArgs struct { + ExportAVAXArgs + // AssetID of the tokens + AssetID string `json:"assetID"` +} + // Export sends an asset from this chain to the P/C-Chain. // After this tx is accepted, the AVAX must be imported to the P/C-chain with an importTx. // Returns the ID of the newly created atomic transaction @@ -221,6 +288,10 @@ func (c *client) LockProfile(ctx context.Context, options ...rpc.Option) error { return c.adminRequester.SendRequest(ctx, "admin.lockProfile", struct{}{}, &api.EmptyReply{}, options...) } +type SetLogLevelArgs struct { + Level string `json:"level"` +} + // SetLogLevel dynamically sets the log level for the C Chain func (c *client) SetLogLevel(ctx context.Context, level slog.Level, options ...rpc.Option) error { return c.adminRequester.SendRequest(ctx, "admin.setLogLevel", &SetLogLevelArgs{ @@ -229,8 +300,8 @@ func (c *client) SetLogLevel(ctx context.Context, level slog.Level, options ...r } // GetVMConfig returns the current config of the VM -func (c *client) GetVMConfig(ctx context.Context, options ...rpc.Option) (*Config, error) { - res := &ConfigReply{} - err := c.adminRequester.SendRequest(ctx, "admin.getVMConfig", struct{}{}, res, options...) - return res.Config, err -} +// func (c *client) GetVMConfig(ctx context.Context, options ...rpc.Option) (*Config, error) { +// res := &ConfigReply{} +// err := c.adminRequester.SendRequest(ctx, "admin.getVMConfig", struct{}{}, res, options...) +// return res.Config, err +// } diff --git a/plugin/evm/client_interface_test.go b/plugin/evm/client/client_interface_test.go similarity index 97% rename from plugin/evm/client_interface_test.go rename to plugin/evm/client/client_interface_test.go index d88c4926b4..332bb8bcf4 100644 --- a/plugin/evm/client_interface_test.go +++ b/plugin/evm/client/client_interface_test.go @@ -1,4 +1,4 @@ -package evm +package client import ( "reflect" diff --git a/plugin/evm/export_tx_test.go b/plugin/evm/export_tx_test.go index d8a7fed80f..3e2b0d2160 100644 --- a/plugin/evm/export_tx_test.go +++ b/plugin/evm/export_tx_test.go @@ -9,7 +9,7 @@ import ( "math/big" "testing" - "github.com/ava-labs/avalanchego/chains/atomic" + avalancheatomic "github.com/ava-labs/avalanchego/chains/atomic" "github.com/ava-labs/avalanchego/ids" engCommon "github.com/ava-labs/avalanchego/snow/engine/common" "github.com/ava-labs/avalanchego/utils/constants" @@ -18,13 +18,14 @@ import ( "github.com/ava-labs/avalanchego/vms/components/avax" "github.com/ava-labs/avalanchego/vms/secp256k1fx" "github.com/ava-labs/coreth/params" + "github.com/ava-labs/coreth/plugin/evm/atomic" "github.com/ethereum/go-ethereum/common" "github.com/holiman/uint256" ) // createExportTxOptions adds funds to shared memory, imports them, and returns a list of export transactions // that attempt to send the funds to each of the test keys (list of length 3). -func createExportTxOptions(t *testing.T, vm *VM, issuer chan engCommon.Message, sharedMemory *atomic.Memory) []*Tx { +func createExportTxOptions(t *testing.T, vm *VM, issuer chan engCommon.Message, sharedMemory *avalancheatomic.Memory) []*atomic.Tx { // Add a UTXO to shared memory utxo := &avax.UTXO{ UTXOID: avax.UTXOID{TxID: ids.GenerateTestID()}, @@ -37,14 +38,14 @@ func createExportTxOptions(t *testing.T, vm *VM, issuer chan engCommon.Message, }, }, } - utxoBytes, err := vm.codec.Marshal(codecVersion, utxo) + utxoBytes, err := atomic.Codec.Marshal(atomic.CodecVersion, utxo) if err != nil { t.Fatal(err) } xChainSharedMemory := sharedMemory.NewSharedMemory(vm.ctx.XChainID) inputID := utxo.InputID() - if err := xChainSharedMemory.Apply(map[ids.ID]*atomic.Requests{vm.ctx.ChainID: {PutRequests: []*atomic.Element{{ + if err := xChainSharedMemory.Apply(map[ids.ID]*avalancheatomic.Requests{vm.ctx.ChainID: {PutRequests: []*avalancheatomic.Element{{ Key: inputID[:], Value: utxoBytes, Traits: [][]byte{ @@ -84,9 +85,13 @@ func createExportTxOptions(t *testing.T, vm *VM, issuer chan engCommon.Message, } // Use the funds to create 3 conflicting export transactions sending the funds to each of the test addresses - exportTxs := make([]*Tx, 0, 3) + exportTxs := make([]*atomic.Tx, 0, 3) + state, err := vm.blockChain.State() + if err != nil { + t.Fatal(err) + } for _, addr := range testShortIDAddrs { - exportTx, err := vm.newExportTx(vm.ctx.AVAXAssetID, uint64(5000000), vm.ctx.XChainID, addr, initialBaseFee, []*secp256k1.PrivateKey{testKeys[0]}) + exportTx, err := atomic.NewExportTx(vm.ctx, vm.currentRules(), state, vm.ctx.AVAXAssetID, uint64(5000000), vm.ctx.XChainID, addr, initialBaseFee, []*secp256k1.PrivateKey{testKeys[0]}) if err != nil { t.Fatal(err) } @@ -99,7 +104,7 @@ func createExportTxOptions(t *testing.T, vm *VM, issuer chan engCommon.Message, func TestExportTxEVMStateTransfer(t *testing.T) { key := testKeys[0] addr := key.PublicKey().Address() - ethAddr := GetEthAddress(key) + ethAddr := atomic.GetEthAddress(key) avaxAmount := 50 * units.MilliAvax avaxUTXOID := avax.UTXOID{ @@ -128,7 +133,7 @@ func TestExportTxEVMStateTransfer(t *testing.T) { tests := []struct { name string - tx []EVMInput + tx []atomic.EVMInput avaxBalance *uint256.Int balances map[ids.ID]*big.Int expectedNonce uint64 @@ -137,7 +142,7 @@ func TestExportTxEVMStateTransfer(t *testing.T) { { name: "no transfers", tx: nil, - avaxBalance: uint256.NewInt(avaxAmount * x2cRateUint64), + avaxBalance: uint256.NewInt(avaxAmount * atomic.X2CRateUint64), balances: map[ids.ID]*big.Int{ customAssetID: big.NewInt(int64(customAmount)), }, @@ -146,7 +151,7 @@ func TestExportTxEVMStateTransfer(t *testing.T) { }, { name: "spend half AVAX", - tx: []EVMInput{ + tx: []atomic.EVMInput{ { Address: ethAddr, Amount: avaxAmount / 2, @@ -154,7 +159,7 @@ func TestExportTxEVMStateTransfer(t *testing.T) { Nonce: 0, }, }, - avaxBalance: uint256.NewInt(avaxAmount / 2 * x2cRateUint64), + avaxBalance: uint256.NewInt(avaxAmount / 2 * atomic.X2CRateUint64), balances: map[ids.ID]*big.Int{ customAssetID: big.NewInt(int64(customAmount)), }, @@ -163,7 +168,7 @@ func TestExportTxEVMStateTransfer(t *testing.T) { }, { name: "spend all AVAX", - tx: []EVMInput{ + tx: []atomic.EVMInput{ { Address: ethAddr, Amount: avaxAmount, @@ -180,7 +185,7 @@ func TestExportTxEVMStateTransfer(t *testing.T) { }, { name: "spend too much AVAX", - tx: []EVMInput{ + tx: []atomic.EVMInput{ { Address: ethAddr, Amount: avaxAmount + 1, @@ -197,7 +202,7 @@ func TestExportTxEVMStateTransfer(t *testing.T) { }, { name: "spend half custom", - tx: []EVMInput{ + tx: []atomic.EVMInput{ { Address: ethAddr, Amount: customAmount / 2, @@ -205,7 +210,7 @@ func TestExportTxEVMStateTransfer(t *testing.T) { Nonce: 0, }, }, - avaxBalance: uint256.NewInt(avaxAmount * x2cRateUint64), + avaxBalance: uint256.NewInt(avaxAmount * atomic.X2CRateUint64), balances: map[ids.ID]*big.Int{ customAssetID: big.NewInt(int64(customAmount / 2)), }, @@ -214,7 +219,7 @@ func TestExportTxEVMStateTransfer(t *testing.T) { }, { name: "spend all custom", - tx: []EVMInput{ + tx: []atomic.EVMInput{ { Address: ethAddr, Amount: customAmount, @@ -222,7 +227,7 @@ func TestExportTxEVMStateTransfer(t *testing.T) { Nonce: 0, }, }, - avaxBalance: uint256.NewInt(avaxAmount * x2cRateUint64), + avaxBalance: uint256.NewInt(avaxAmount * atomic.X2CRateUint64), balances: map[ids.ID]*big.Int{ customAssetID: big.NewInt(0), }, @@ -231,7 +236,7 @@ func TestExportTxEVMStateTransfer(t *testing.T) { }, { name: "spend too much custom", - tx: []EVMInput{ + tx: []atomic.EVMInput{ { Address: ethAddr, Amount: customAmount + 1, @@ -239,7 +244,7 @@ func TestExportTxEVMStateTransfer(t *testing.T) { Nonce: 0, }, }, - avaxBalance: uint256.NewInt(avaxAmount * x2cRateUint64), + avaxBalance: uint256.NewInt(avaxAmount * atomic.X2CRateUint64), balances: map[ids.ID]*big.Int{ customAssetID: big.NewInt(0), }, @@ -248,7 +253,7 @@ func TestExportTxEVMStateTransfer(t *testing.T) { }, { name: "spend everything", - tx: []EVMInput{ + tx: []atomic.EVMInput{ { Address: ethAddr, Amount: customAmount, @@ -271,7 +276,7 @@ func TestExportTxEVMStateTransfer(t *testing.T) { }, { name: "spend everything wrong nonce", - tx: []EVMInput{ + tx: []atomic.EVMInput{ { Address: ethAddr, Amount: customAmount, @@ -294,7 +299,7 @@ func TestExportTxEVMStateTransfer(t *testing.T) { }, { name: "spend everything changing nonces", - tx: []EVMInput{ + tx: []atomic.EVMInput{ { Address: ethAddr, Amount: customAmount, @@ -337,18 +342,18 @@ func TestExportTxEVMStateTransfer(t *testing.T) { }, } - avaxUTXOBytes, err := vm.codec.Marshal(codecVersion, avaxUTXO) + avaxUTXOBytes, err := atomic.Codec.Marshal(atomic.CodecVersion, avaxUTXO) if err != nil { t.Fatal(err) } - customUTXOBytes, err := vm.codec.Marshal(codecVersion, customUTXO) + customUTXOBytes, err := atomic.Codec.Marshal(atomic.CodecVersion, customUTXO) if err != nil { t.Fatal(err) } xChainSharedMemory := sharedMemory.NewSharedMemory(vm.ctx.XChainID) - if err := xChainSharedMemory.Apply(map[ids.ID]*atomic.Requests{vm.ctx.ChainID: {PutRequests: []*atomic.Element{ + if err := xChainSharedMemory.Apply(map[ids.ID]*avalancheatomic.Requests{vm.ctx.ChainID: {PutRequests: []*avalancheatomic.Element{ { Key: avaxInputID[:], Value: avaxUTXOBytes, @@ -395,7 +400,7 @@ func TestExportTxEVMStateTransfer(t *testing.T) { t.Fatal(err) } - newTx := UnsignedExportTx{ + newTx := atomic.UnsignedExportTx{ Ins: test.tx, } @@ -457,11 +462,11 @@ func TestExportTxSemanticVerify(t *testing.T) { custom1AssetID = ids.ID{1, 2, 3, 4, 5, 6} ) - validExportTx := &UnsignedExportTx{ + validExportTx := &atomic.UnsignedExportTx{ NetworkID: vm.ctx.NetworkID, BlockchainID: vm.ctx.ChainID, DestinationChain: vm.ctx.XChainID, - Ins: []EVMInput{ + Ins: []atomic.EVMInput{ { Address: ethAddr, Amount: avaxBalance, @@ -495,11 +500,11 @@ func TestExportTxSemanticVerify(t *testing.T) { }, } - validAVAXExportTx := &UnsignedExportTx{ + validAVAXExportTx := &atomic.UnsignedExportTx{ NetworkID: vm.ctx.NetworkID, BlockchainID: vm.ctx.ChainID, DestinationChain: vm.ctx.XChainID, - Ins: []EVMInput{ + Ins: []atomic.EVMInput{ { Address: ethAddr, Amount: avaxBalance, @@ -523,7 +528,7 @@ func TestExportTxSemanticVerify(t *testing.T) { tests := []struct { name string - tx *Tx + tx *atomic.Tx signers [][]*secp256k1.PrivateKey baseFee *big.Int rules params.Rules @@ -531,7 +536,7 @@ func TestExportTxSemanticVerify(t *testing.T) { }{ { name: "valid", - tx: &Tx{UnsignedAtomicTx: validExportTx}, + tx: &atomic.Tx{UnsignedAtomicTx: validExportTx}, signers: [][]*secp256k1.PrivateKey{ {key}, {key}, @@ -543,10 +548,10 @@ func TestExportTxSemanticVerify(t *testing.T) { }, { name: "P-chain before AP5", - tx: func() *Tx { + tx: func() *atomic.Tx { validExportTx := *validAVAXExportTx validExportTx.DestinationChain = constants.PlatformChainID - return &Tx{UnsignedAtomicTx: &validExportTx} + return &atomic.Tx{UnsignedAtomicTx: &validExportTx} }(), signers: [][]*secp256k1.PrivateKey{ {key}, @@ -557,10 +562,10 @@ func TestExportTxSemanticVerify(t *testing.T) { }, { name: "P-chain after AP5", - tx: func() *Tx { + tx: func() *atomic.Tx { validExportTx := *validAVAXExportTx validExportTx.DestinationChain = constants.PlatformChainID - return &Tx{UnsignedAtomicTx: &validExportTx} + return &atomic.Tx{UnsignedAtomicTx: &validExportTx} }(), signers: [][]*secp256k1.PrivateKey{ {key}, @@ -571,10 +576,10 @@ func TestExportTxSemanticVerify(t *testing.T) { }, { name: "random chain after AP5", - tx: func() *Tx { + tx: func() *atomic.Tx { validExportTx := *validAVAXExportTx validExportTx.DestinationChain = ids.GenerateTestID() - return &Tx{UnsignedAtomicTx: &validExportTx} + return &atomic.Tx{UnsignedAtomicTx: &validExportTx} }(), signers: [][]*secp256k1.PrivateKey{ {key}, @@ -585,10 +590,10 @@ func TestExportTxSemanticVerify(t *testing.T) { }, { name: "P-chain multi-coin before AP5", - tx: func() *Tx { + tx: func() *atomic.Tx { validExportTx := *validExportTx validExportTx.DestinationChain = constants.PlatformChainID - return &Tx{UnsignedAtomicTx: &validExportTx} + return &atomic.Tx{UnsignedAtomicTx: &validExportTx} }(), signers: [][]*secp256k1.PrivateKey{ {key}, @@ -601,10 +606,10 @@ func TestExportTxSemanticVerify(t *testing.T) { }, { name: "P-chain multi-coin after AP5", - tx: func() *Tx { + tx: func() *atomic.Tx { validExportTx := *validExportTx validExportTx.DestinationChain = constants.PlatformChainID - return &Tx{UnsignedAtomicTx: &validExportTx} + return &atomic.Tx{UnsignedAtomicTx: &validExportTx} }(), signers: [][]*secp256k1.PrivateKey{ {key}, @@ -617,10 +622,10 @@ func TestExportTxSemanticVerify(t *testing.T) { }, { name: "random chain multi-coin after AP5", - tx: func() *Tx { + tx: func() *atomic.Tx { validExportTx := *validExportTx validExportTx.DestinationChain = ids.GenerateTestID() - return &Tx{UnsignedAtomicTx: &validExportTx} + return &atomic.Tx{UnsignedAtomicTx: &validExportTx} }(), signers: [][]*secp256k1.PrivateKey{ {key}, @@ -633,10 +638,10 @@ func TestExportTxSemanticVerify(t *testing.T) { }, { name: "no outputs", - tx: func() *Tx { + tx: func() *atomic.Tx { validExportTx := *validExportTx validExportTx.ExportedOutputs = nil - return &Tx{UnsignedAtomicTx: &validExportTx} + return &atomic.Tx{UnsignedAtomicTx: &validExportTx} }(), signers: [][]*secp256k1.PrivateKey{ {key}, @@ -649,10 +654,10 @@ func TestExportTxSemanticVerify(t *testing.T) { }, { name: "wrong networkID", - tx: func() *Tx { + tx: func() *atomic.Tx { validExportTx := *validExportTx validExportTx.NetworkID++ - return &Tx{UnsignedAtomicTx: &validExportTx} + return &atomic.Tx{UnsignedAtomicTx: &validExportTx} }(), signers: [][]*secp256k1.PrivateKey{ {key}, @@ -665,10 +670,10 @@ func TestExportTxSemanticVerify(t *testing.T) { }, { name: "wrong chainID", - tx: func() *Tx { + tx: func() *atomic.Tx { validExportTx := *validExportTx validExportTx.BlockchainID = ids.GenerateTestID() - return &Tx{UnsignedAtomicTx: &validExportTx} + return &atomic.Tx{UnsignedAtomicTx: &validExportTx} }(), signers: [][]*secp256k1.PrivateKey{ {key}, @@ -681,11 +686,11 @@ func TestExportTxSemanticVerify(t *testing.T) { }, { name: "invalid input", - tx: func() *Tx { + tx: func() *atomic.Tx { validExportTx := *validExportTx - validExportTx.Ins = append([]EVMInput{}, validExportTx.Ins...) + validExportTx.Ins = append([]atomic.EVMInput{}, validExportTx.Ins...) validExportTx.Ins[2].Amount = 0 - return &Tx{UnsignedAtomicTx: &validExportTx} + return &atomic.Tx{UnsignedAtomicTx: &validExportTx} }(), signers: [][]*secp256k1.PrivateKey{ {key}, @@ -698,7 +703,7 @@ func TestExportTxSemanticVerify(t *testing.T) { }, { name: "invalid output", - tx: func() *Tx { + tx: func() *atomic.Tx { validExportTx := *validExportTx validExportTx.ExportedOutputs = []*avax.TransferableOutput{{ Asset: avax.Asset{ID: custom0AssetID}, @@ -710,7 +715,7 @@ func TestExportTxSemanticVerify(t *testing.T) { }, }, }} - return &Tx{UnsignedAtomicTx: &validExportTx} + return &atomic.Tx{UnsignedAtomicTx: &validExportTx} }(), signers: [][]*secp256k1.PrivateKey{ {key}, @@ -723,7 +728,7 @@ func TestExportTxSemanticVerify(t *testing.T) { }, { name: "unsorted outputs", - tx: func() *Tx { + tx: func() *atomic.Tx { validExportTx := *validExportTx exportOutputs := []*avax.TransferableOutput{ { @@ -748,10 +753,10 @@ func TestExportTxSemanticVerify(t *testing.T) { }, } // Sort the outputs and then swap the ordering to ensure that they are ordered incorrectly - avax.SortTransferableOutputs(exportOutputs, Codec) + avax.SortTransferableOutputs(exportOutputs, atomic.Codec) exportOutputs[0], exportOutputs[1] = exportOutputs[1], exportOutputs[0] validExportTx.ExportedOutputs = exportOutputs - return &Tx{UnsignedAtomicTx: &validExportTx} + return &atomic.Tx{UnsignedAtomicTx: &validExportTx} }(), signers: [][]*secp256k1.PrivateKey{ {key}, @@ -764,11 +769,11 @@ func TestExportTxSemanticVerify(t *testing.T) { }, { name: "not unique inputs", - tx: func() *Tx { + tx: func() *atomic.Tx { validExportTx := *validExportTx - validExportTx.Ins = append([]EVMInput{}, validExportTx.Ins...) + validExportTx.Ins = append([]atomic.EVMInput{}, validExportTx.Ins...) validExportTx.Ins[2] = validExportTx.Ins[1] - return &Tx{UnsignedAtomicTx: &validExportTx} + return &atomic.Tx{UnsignedAtomicTx: &validExportTx} }(), signers: [][]*secp256k1.PrivateKey{ {key}, @@ -781,7 +786,7 @@ func TestExportTxSemanticVerify(t *testing.T) { }, { name: "custom asset insufficient funds", - tx: func() *Tx { + tx: func() *atomic.Tx { validExportTx := *validExportTx validExportTx.ExportedOutputs = []*avax.TransferableOutput{ { @@ -795,7 +800,7 @@ func TestExportTxSemanticVerify(t *testing.T) { }, }, } - return &Tx{UnsignedAtomicTx: &validExportTx} + return &atomic.Tx{UnsignedAtomicTx: &validExportTx} }(), signers: [][]*secp256k1.PrivateKey{ {key}, @@ -808,7 +813,7 @@ func TestExportTxSemanticVerify(t *testing.T) { }, { name: "avax insufficient funds", - tx: func() *Tx { + tx: func() *atomic.Tx { validExportTx := *validExportTx validExportTx.ExportedOutputs = []*avax.TransferableOutput{ { @@ -822,7 +827,7 @@ func TestExportTxSemanticVerify(t *testing.T) { }, }, } - return &Tx{UnsignedAtomicTx: &validExportTx} + return &atomic.Tx{UnsignedAtomicTx: &validExportTx} }(), signers: [][]*secp256k1.PrivateKey{ {key}, @@ -835,7 +840,7 @@ func TestExportTxSemanticVerify(t *testing.T) { }, { name: "too many signatures", - tx: &Tx{UnsignedAtomicTx: validExportTx}, + tx: &atomic.Tx{UnsignedAtomicTx: validExportTx}, signers: [][]*secp256k1.PrivateKey{ {key}, {key}, @@ -848,7 +853,7 @@ func TestExportTxSemanticVerify(t *testing.T) { }, { name: "too few signatures", - tx: &Tx{UnsignedAtomicTx: validExportTx}, + tx: &atomic.Tx{UnsignedAtomicTx: validExportTx}, signers: [][]*secp256k1.PrivateKey{ {key}, {key}, @@ -859,7 +864,7 @@ func TestExportTxSemanticVerify(t *testing.T) { }, { name: "too many signatures on credential", - tx: &Tx{UnsignedAtomicTx: validExportTx}, + tx: &atomic.Tx{UnsignedAtomicTx: validExportTx}, signers: [][]*secp256k1.PrivateKey{ {key, testKeys[1]}, {key}, @@ -871,7 +876,7 @@ func TestExportTxSemanticVerify(t *testing.T) { }, { name: "too few signatures on credential", - tx: &Tx{UnsignedAtomicTx: validExportTx}, + tx: &atomic.Tx{UnsignedAtomicTx: validExportTx}, signers: [][]*secp256k1.PrivateKey{ {}, {key}, @@ -883,7 +888,7 @@ func TestExportTxSemanticVerify(t *testing.T) { }, { name: "wrong signature on credential", - tx: &Tx{UnsignedAtomicTx: validExportTx}, + tx: &atomic.Tx{UnsignedAtomicTx: validExportTx}, signers: [][]*secp256k1.PrivateKey{ {testKeys[1]}, {key}, @@ -895,7 +900,7 @@ func TestExportTxSemanticVerify(t *testing.T) { }, { name: "no signatures", - tx: &Tx{UnsignedAtomicTx: validExportTx}, + tx: &atomic.Tx{UnsignedAtomicTx: validExportTx}, signers: [][]*secp256k1.PrivateKey{}, baseFee: initialBaseFee, rules: apricotRulesPhase3, @@ -903,15 +908,24 @@ func TestExportTxSemanticVerify(t *testing.T) { }, } for _, test := range tests { - if err := test.tx.Sign(vm.codec, test.signers); err != nil { + if err := test.tx.Sign(atomic.Codec, test.signers); err != nil { t.Fatal(err) } + backend := &atomic.Backend{ + Ctx: vm.ctx, + Fx: &vm.fx, + Rules: test.rules, + Bootstrapped: vm.bootstrapped.Get(), + BlockFetcher: vm, + SecpCache: &vm.secpCache, + } + t.Run(test.name, func(t *testing.T) { tx := test.tx exportTx := tx.UnsignedAtomicTx - err := exportTx.SemanticVerify(vm, tx, parent, test.baseFee, test.rules) + err := exportTx.SemanticVerify(backend, tx, parent, test.baseFee) if test.shouldErr && err == nil { t.Fatalf("should have errored but returned valid") } @@ -943,11 +957,11 @@ func TestExportTxAccept(t *testing.T) { custom0AssetID = ids.ID{1, 2, 3, 4, 5} ) - exportTx := &UnsignedExportTx{ + exportTx := &atomic.UnsignedExportTx{ NetworkID: vm.ctx.NetworkID, BlockchainID: vm.ctx.ChainID, DestinationChain: vm.ctx.XChainID, - Ins: []EVMInput{ + Ins: []atomic.EVMInput{ { Address: ethAddr, Amount: avaxBalance, @@ -985,7 +999,7 @@ func TestExportTxAccept(t *testing.T) { }, } - tx := &Tx{UnsignedAtomicTx: exportTx} + tx := &atomic.Tx{UnsignedAtomicTx: exportTx} signers := [][]*secp256k1.PrivateKey{ {key}, @@ -993,7 +1007,7 @@ func TestExportTxAccept(t *testing.T) { {key}, } - if err := tx.Sign(vm.codec, signers); err != nil { + if err := tx.Sign(atomic.Codec, signers); err != nil { t.Fatal(err) } @@ -1006,7 +1020,7 @@ func TestExportTxAccept(t *testing.T) { t.Fatalf("Failed to accept export transaction due to: %s", err) } - if err := vm.ctx.SharedMemory.Apply(map[ids.ID]*atomic.Requests{chainID: {PutRequests: atomicRequests.PutRequests}}, commitBatch); err != nil { + if err := vm.ctx.SharedMemory.Apply(map[ids.ID]*avalancheatomic.Requests{chainID: {PutRequests: atomicRequests.PutRequests}}, commitBatch); err != nil { t.Fatal(err) } indexedValues, _, _, err := xChainSharedMemory.Indexed(vm.ctx.ChainID, [][]byte{addr.Bytes()}, nil, nil, 3) @@ -1045,7 +1059,7 @@ func TestExportTxAccept(t *testing.T) { t.Fatalf("inconsistent values returned fetched %x indexed %x", fetchedValues[1], indexedValues[1]) } - customUTXOBytes, err := Codec.Marshal(codecVersion, &avax.UTXO{ + customUTXOBytes, err := atomic.Codec.Marshal(atomic.CodecVersion, &avax.UTXO{ UTXOID: customUTXOID, Asset: avax.Asset{ID: custom0AssetID}, Out: exportTx.ExportedOutputs[1].Out, @@ -1054,7 +1068,7 @@ func TestExportTxAccept(t *testing.T) { t.Fatal(err) } - avaxUTXOBytes, err := Codec.Marshal(codecVersion, &avax.UTXO{ + avaxUTXOBytes, err := atomic.Codec.Marshal(atomic.CodecVersion, &avax.UTXO{ UTXOID: avaxUTXOID, Asset: avax.Asset{ID: vm.ctx.AVAXAssetID}, Out: exportTx.ExportedOutputs[0].Out, @@ -1073,11 +1087,11 @@ func TestExportTxAccept(t *testing.T) { func TestExportTxVerify(t *testing.T) { var exportAmount uint64 = 10000000 - exportTx := &UnsignedExportTx{ + exportTx := &atomic.UnsignedExportTx{ NetworkID: testNetworkID, BlockchainID: testCChainID, DestinationChain: testXChainID, - Ins: []EVMInput{ + Ins: []atomic.EVMInput{ { Address: testEthAddrs[0], Amount: exportAmount, @@ -1118,25 +1132,25 @@ func TestExportTxVerify(t *testing.T) { } // Sort the inputs and outputs to ensure the transaction is canonical - avax.SortTransferableOutputs(exportTx.ExportedOutputs, Codec) + avax.SortTransferableOutputs(exportTx.ExportedOutputs, atomic.Codec) // Pass in a list of signers here with the appropriate length // to avoid causing a nil-pointer error in the helper method emptySigners := make([][]*secp256k1.PrivateKey, 2) - SortEVMInputsAndSigners(exportTx.Ins, emptySigners) + atomic.SortEVMInputsAndSigners(exportTx.Ins, emptySigners) ctx := NewContext() tests := map[string]atomicTxVerifyTest{ "nil tx": { - generate: func(t *testing.T) UnsignedAtomicTx { - return (*UnsignedExportTx)(nil) + generate: func(t *testing.T) atomic.UnsignedAtomicTx { + return (*atomic.UnsignedExportTx)(nil) }, ctx: ctx, rules: apricotRulesPhase0, - expectedErr: errNilTx.Error(), + expectedErr: atomic.ErrNilTx.Error(), }, "valid export tx": { - generate: func(t *testing.T) UnsignedAtomicTx { + generate: func(t *testing.T) atomic.UnsignedAtomicTx { return exportTx }, ctx: ctx, @@ -1144,7 +1158,7 @@ func TestExportTxVerify(t *testing.T) { expectedErr: "", }, "valid export tx banff": { - generate: func(t *testing.T) UnsignedAtomicTx { + generate: func(t *testing.T) atomic.UnsignedAtomicTx { return exportTx }, ctx: ctx, @@ -1152,47 +1166,47 @@ func TestExportTxVerify(t *testing.T) { expectedErr: "", }, "incorrect networkID": { - generate: func(t *testing.T) UnsignedAtomicTx { + generate: func(t *testing.T) atomic.UnsignedAtomicTx { tx := *exportTx tx.NetworkID++ return &tx }, ctx: ctx, rules: apricotRulesPhase0, - expectedErr: errWrongNetworkID.Error(), + expectedErr: atomic.ErrWrongNetworkID.Error(), }, "incorrect blockchainID": { - generate: func(t *testing.T) UnsignedAtomicTx { + generate: func(t *testing.T) atomic.UnsignedAtomicTx { tx := *exportTx tx.BlockchainID = nonExistentID return &tx }, ctx: ctx, rules: apricotRulesPhase0, - expectedErr: errWrongBlockchainID.Error(), + expectedErr: atomic.ErrWrongChainID.Error(), }, "incorrect destination chain": { - generate: func(t *testing.T) UnsignedAtomicTx { + generate: func(t *testing.T) atomic.UnsignedAtomicTx { tx := *exportTx tx.DestinationChain = nonExistentID return &tx }, ctx: ctx, rules: apricotRulesPhase0, - expectedErr: errWrongChainID.Error(), // TODO make this error more specific to destination not just chainID + expectedErr: atomic.ErrWrongChainID.Error(), // TODO make this error more specific to destination not just chainID }, "no exported outputs": { - generate: func(t *testing.T) UnsignedAtomicTx { + generate: func(t *testing.T) atomic.UnsignedAtomicTx { tx := *exportTx tx.ExportedOutputs = nil return &tx }, ctx: ctx, rules: apricotRulesPhase0, - expectedErr: errNoExportOutputs.Error(), + expectedErr: atomic.ErrNoExportOutputs.Error(), }, "unsorted outputs": { - generate: func(t *testing.T) UnsignedAtomicTx { + generate: func(t *testing.T) atomic.UnsignedAtomicTx { tx := *exportTx tx.ExportedOutputs = []*avax.TransferableOutput{ tx.ExportedOutputs[1], @@ -1202,10 +1216,10 @@ func TestExportTxVerify(t *testing.T) { }, ctx: ctx, rules: apricotRulesPhase0, - expectedErr: errOutputsNotSorted.Error(), + expectedErr: atomic.ErrOutputsNotSorted.Error(), }, "invalid exported output": { - generate: func(t *testing.T) UnsignedAtomicTx { + generate: func(t *testing.T) atomic.UnsignedAtomicTx { tx := *exportTx tx.ExportedOutputs = []*avax.TransferableOutput{tx.ExportedOutputs[0], nil} return &tx @@ -1215,9 +1229,9 @@ func TestExportTxVerify(t *testing.T) { expectedErr: "nil transferable output is not valid", }, "unsorted EVM inputs before AP1": { - generate: func(t *testing.T) UnsignedAtomicTx { + generate: func(t *testing.T) atomic.UnsignedAtomicTx { tx := *exportTx - tx.Ins = []EVMInput{ + tx.Ins = []atomic.EVMInput{ tx.Ins[1], tx.Ins[0], } @@ -1228,9 +1242,9 @@ func TestExportTxVerify(t *testing.T) { expectedErr: "", }, "unsorted EVM inputs after AP1": { - generate: func(t *testing.T) UnsignedAtomicTx { + generate: func(t *testing.T) atomic.UnsignedAtomicTx { tx := *exportTx - tx.Ins = []EVMInput{ + tx.Ins = []atomic.EVMInput{ tx.Ins[1], tx.Ins[0], } @@ -1238,12 +1252,12 @@ func TestExportTxVerify(t *testing.T) { }, ctx: ctx, rules: apricotRulesPhase1, - expectedErr: errInputsNotSortedUnique.Error(), + expectedErr: atomic.ErrInputsNotSortedUnique.Error(), }, "EVM input with amount 0": { - generate: func(t *testing.T) UnsignedAtomicTx { + generate: func(t *testing.T) atomic.UnsignedAtomicTx { tx := *exportTx - tx.Ins = []EVMInput{ + tx.Ins = []atomic.EVMInput{ { Address: testEthAddrs[0], Amount: 0, @@ -1255,12 +1269,12 @@ func TestExportTxVerify(t *testing.T) { }, ctx: ctx, rules: apricotRulesPhase0, - expectedErr: errNoValueInput.Error(), + expectedErr: atomic.ErrNoValueInput.Error(), }, "non-unique EVM input before AP1": { - generate: func(t *testing.T) UnsignedAtomicTx { + generate: func(t *testing.T) atomic.UnsignedAtomicTx { tx := *exportTx - tx.Ins = []EVMInput{tx.Ins[0], tx.Ins[0]} + tx.Ins = []atomic.EVMInput{tx.Ins[0], tx.Ins[0]} return &tx }, ctx: ctx, @@ -1268,19 +1282,19 @@ func TestExportTxVerify(t *testing.T) { expectedErr: "", }, "non-unique EVM input after AP1": { - generate: func(t *testing.T) UnsignedAtomicTx { + generate: func(t *testing.T) atomic.UnsignedAtomicTx { tx := *exportTx - tx.Ins = []EVMInput{tx.Ins[0], tx.Ins[0]} + tx.Ins = []atomic.EVMInput{tx.Ins[0], tx.Ins[0]} return &tx }, ctx: ctx, rules: apricotRulesPhase1, - expectedErr: errInputsNotSortedUnique.Error(), + expectedErr: atomic.ErrInputsNotSortedUnique.Error(), }, "non-AVAX input Apricot Phase 6": { - generate: func(t *testing.T) UnsignedAtomicTx { + generate: func(t *testing.T) atomic.UnsignedAtomicTx { tx := *exportTx - tx.Ins = []EVMInput{ + tx.Ins = []atomic.EVMInput{ { Address: testEthAddrs[0], Amount: 1, @@ -1295,7 +1309,7 @@ func TestExportTxVerify(t *testing.T) { expectedErr: "", }, "non-AVAX output Apricot Phase 6": { - generate: func(t *testing.T) UnsignedAtomicTx { + generate: func(t *testing.T) atomic.UnsignedAtomicTx { tx := *exportTx tx.ExportedOutputs = []*avax.TransferableOutput{ { @@ -1317,9 +1331,9 @@ func TestExportTxVerify(t *testing.T) { expectedErr: "", }, "non-AVAX input Banff": { - generate: func(t *testing.T) UnsignedAtomicTx { + generate: func(t *testing.T) atomic.UnsignedAtomicTx { tx := *exportTx - tx.Ins = []EVMInput{ + tx.Ins = []atomic.EVMInput{ { Address: testEthAddrs[0], Amount: 1, @@ -1331,10 +1345,10 @@ func TestExportTxVerify(t *testing.T) { }, ctx: ctx, rules: banffRules, - expectedErr: errExportNonAVAXInputBanff.Error(), + expectedErr: atomic.ErrExportNonAVAXInputBanff.Error(), }, "non-AVAX output Banff": { - generate: func(t *testing.T) UnsignedAtomicTx { + generate: func(t *testing.T) atomic.UnsignedAtomicTx { tx := *exportTx tx.ExportedOutputs = []*avax.TransferableOutput{ { @@ -1353,7 +1367,7 @@ func TestExportTxVerify(t *testing.T) { }, ctx: ctx, rules: banffRules, - expectedErr: errExportNonAVAXOutputBanff.Error(), + expectedErr: atomic.ErrExportNonAVAXOutputBanff.Error(), }, } @@ -1374,7 +1388,7 @@ func TestExportTxGasCost(t *testing.T) { exportAmount := uint64(5000000) tests := map[string]struct { - UnsignedExportTx *UnsignedExportTx + UnsignedExportTx *atomic.UnsignedExportTx Keys [][]*secp256k1.PrivateKey BaseFee *big.Int @@ -1383,11 +1397,11 @@ func TestExportTxGasCost(t *testing.T) { FixedFee bool }{ "simple export 1wei BaseFee": { - UnsignedExportTx: &UnsignedExportTx{ + UnsignedExportTx: &atomic.UnsignedExportTx{ NetworkID: networkID, BlockchainID: chainID, DestinationChain: xChainID, - Ins: []EVMInput{ + Ins: []atomic.EVMInput{ { Address: testEthAddrs[0], Amount: exportAmount, @@ -1415,11 +1429,11 @@ func TestExportTxGasCost(t *testing.T) { BaseFee: big.NewInt(1), }, "simple export 1wei BaseFee + fixed fee": { - UnsignedExportTx: &UnsignedExportTx{ + UnsignedExportTx: &atomic.UnsignedExportTx{ NetworkID: networkID, BlockchainID: chainID, DestinationChain: xChainID, - Ins: []EVMInput{ + Ins: []atomic.EVMInput{ { Address: testEthAddrs[0], Amount: exportAmount, @@ -1448,11 +1462,11 @@ func TestExportTxGasCost(t *testing.T) { FixedFee: true, }, "simple export 25Gwei BaseFee": { - UnsignedExportTx: &UnsignedExportTx{ + UnsignedExportTx: &atomic.UnsignedExportTx{ NetworkID: networkID, BlockchainID: chainID, DestinationChain: xChainID, - Ins: []EVMInput{ + Ins: []atomic.EVMInput{ { Address: testEthAddrs[0], Amount: exportAmount, @@ -1480,11 +1494,11 @@ func TestExportTxGasCost(t *testing.T) { BaseFee: big.NewInt(25 * params.GWei), }, "simple export 225Gwei BaseFee": { - UnsignedExportTx: &UnsignedExportTx{ + UnsignedExportTx: &atomic.UnsignedExportTx{ NetworkID: networkID, BlockchainID: chainID, DestinationChain: xChainID, - Ins: []EVMInput{ + Ins: []atomic.EVMInput{ { Address: testEthAddrs[0], Amount: exportAmount, @@ -1512,11 +1526,11 @@ func TestExportTxGasCost(t *testing.T) { BaseFee: big.NewInt(225 * params.GWei), }, "complex export 25Gwei BaseFee": { - UnsignedExportTx: &UnsignedExportTx{ + UnsignedExportTx: &atomic.UnsignedExportTx{ NetworkID: networkID, BlockchainID: chainID, DestinationChain: xChainID, - Ins: []EVMInput{ + Ins: []atomic.EVMInput{ { Address: testEthAddrs[0], Amount: exportAmount, @@ -1556,11 +1570,11 @@ func TestExportTxGasCost(t *testing.T) { BaseFee: big.NewInt(25 * params.GWei), }, "complex export 225Gwei BaseFee": { - UnsignedExportTx: &UnsignedExportTx{ + UnsignedExportTx: &atomic.UnsignedExportTx{ NetworkID: networkID, BlockchainID: chainID, DestinationChain: xChainID, - Ins: []EVMInput{ + Ins: []atomic.EVMInput{ { Address: testEthAddrs[0], Amount: exportAmount, @@ -1603,10 +1617,10 @@ func TestExportTxGasCost(t *testing.T) { for name, test := range tests { t.Run(name, func(t *testing.T) { - tx := &Tx{UnsignedAtomicTx: test.UnsignedExportTx} + tx := &atomic.Tx{UnsignedAtomicTx: test.UnsignedExportTx} // Sign with the correct key - if err := tx.Sign(Codec, test.Keys); err != nil { + if err := tx.Sign(atomic.Codec, test.Keys); err != nil { t.Fatal(err) } @@ -1618,7 +1632,7 @@ func TestExportTxGasCost(t *testing.T) { t.Fatalf("Expected gasUsed to be %d, but found %d", test.ExpectedGasUsed, gasUsed) } - fee, err := CalculateDynamicFee(gasUsed, test.BaseFee) + fee, err := atomic.CalculateDynamicFee(gasUsed, test.BaseFee) if err != nil { t.Fatal(err) } @@ -1705,14 +1719,14 @@ func TestNewExportTx(t *testing.T) { }, }, } - utxoBytes, err := vm.codec.Marshal(codecVersion, utxo) + utxoBytes, err := atomic.Codec.Marshal(atomic.CodecVersion, utxo) if err != nil { t.Fatal(err) } xChainSharedMemory := sharedMemory.NewSharedMemory(vm.ctx.XChainID) inputID := utxo.InputID() - if err := xChainSharedMemory.Apply(map[ids.ID]*atomic.Requests{vm.ctx.ChainID: {PutRequests: []*atomic.Element{{ + if err := xChainSharedMemory.Apply(map[ids.ID]*avalancheatomic.Requests{vm.ctx.ChainID: {PutRequests: []*avalancheatomic.Element{{ Key: inputID[:], Value: utxoBytes, Traits: [][]byte{ @@ -1753,14 +1767,28 @@ func TestNewExportTx(t *testing.T) { parent = vm.LastAcceptedBlockInternal().(*Block) exportAmount := uint64(5000000) - tx, err = vm.newExportTx(vm.ctx.AVAXAssetID, exportAmount, vm.ctx.XChainID, testShortIDAddrs[0], initialBaseFee, []*secp256k1.PrivateKey{testKeys[0]}) + state, err := vm.blockChain.State() + if err != nil { + t.Fatal(err) + } + + tx, err = atomic.NewExportTx(vm.ctx, test.rules, state, vm.ctx.AVAXAssetID, exportAmount, vm.ctx.XChainID, testShortIDAddrs[0], initialBaseFee, []*secp256k1.PrivateKey{testKeys[0]}) if err != nil { t.Fatal(err) } exportTx := tx.UnsignedAtomicTx - if err := exportTx.SemanticVerify(vm, tx, parent, parent.ethBlock.BaseFee(), test.rules); err != nil { + backend := &atomic.Backend{ + Ctx: vm.ctx, + Fx: &vm.fx, + Rules: vm.currentRules(), + Bootstrapped: vm.bootstrapped.Get(), + BlockFetcher: vm, + SecpCache: &vm.secpCache, + } + + if err := exportTx.SemanticVerify(backend, tx, parent, parent.ethBlock.BaseFee()); err != nil { t.Fatal("newExportTx created an invalid transaction", err) } @@ -1781,7 +1809,7 @@ func TestNewExportTx(t *testing.T) { t.Fatalf("Failed to accept export transaction due to: %s", err) } - if err := vm.ctx.SharedMemory.Apply(map[ids.ID]*atomic.Requests{chainID: {PutRequests: atomicRequests.PutRequests}}, commitBatch); err != nil { + if err := vm.ctx.SharedMemory.Apply(map[ids.ID]*avalancheatomic.Requests{chainID: {PutRequests: atomicRequests.PutRequests}}, commitBatch); err != nil { t.Fatal(err) } @@ -1794,7 +1822,7 @@ func TestNewExportTx(t *testing.T) { t.Fatal(err) } - addr := GetEthAddress(testKeys[0]) + addr := atomic.GetEthAddress(testKeys[0]) if sdb.GetBalance(addr).Cmp(uint256.NewInt(test.bal*units.Avax)) != 0 { t.Fatalf("address balance %s equal %s not %s", addr.String(), sdb.GetBalance(addr), new(big.Int).SetUint64(test.bal*units.Avax)) } @@ -1864,7 +1892,7 @@ func TestNewExportTxMulticoin(t *testing.T) { }, }, } - utxoBytes, err := vm.codec.Marshal(codecVersion, utxo) + utxoBytes, err := atomic.Codec.Marshal(atomic.CodecVersion, utxo) if err != nil { t.Fatal(err) } @@ -1885,14 +1913,14 @@ func TestNewExportTxMulticoin(t *testing.T) { }, }, } - utxoBytes2, err := vm.codec.Marshal(codecVersion, utxo2) + utxoBytes2, err := atomic.Codec.Marshal(atomic.CodecVersion, utxo2) if err != nil { t.Fatal(err) } xChainSharedMemory := sharedMemory.NewSharedMemory(vm.ctx.XChainID) inputID2 := utxo2.InputID() - if err := xChainSharedMemory.Apply(map[ids.ID]*atomic.Requests{vm.ctx.ChainID: {PutRequests: []*atomic.Element{ + if err := xChainSharedMemory.Apply(map[ids.ID]*avalancheatomic.Requests{vm.ctx.ChainID: {PutRequests: []*avalancheatomic.Element{ { Key: inputID[:], Value: utxoBytes, @@ -1942,20 +1970,33 @@ func TestNewExportTxMulticoin(t *testing.T) { parent = vm.LastAcceptedBlockInternal().(*Block) exportAmount := uint64(5000000) - testKeys0Addr := GetEthAddress(testKeys[0]) + testKeys0Addr := atomic.GetEthAddress(testKeys[0]) exportId, err := ids.ToShortID(testKeys0Addr[:]) if err != nil { t.Fatal(err) } - tx, err = vm.newExportTx(tid, exportAmount, vm.ctx.XChainID, exportId, initialBaseFee, []*secp256k1.PrivateKey{testKeys[0]}) + state, err := vm.blockChain.State() + if err != nil { + t.Fatal(err) + } + + tx, err = atomic.NewExportTx(vm.ctx, vm.currentRules(), state, tid, exportAmount, vm.ctx.XChainID, exportId, initialBaseFee, []*secp256k1.PrivateKey{testKeys[0]}) if err != nil { t.Fatal(err) } exportTx := tx.UnsignedAtomicTx + backend := &atomic.Backend{ + Ctx: vm.ctx, + Fx: &vm.fx, + Rules: vm.currentRules(), + Bootstrapped: vm.bootstrapped.Get(), + BlockFetcher: vm, + SecpCache: &vm.secpCache, + } - if err := exportTx.SemanticVerify(vm, tx, parent, parent.ethBlock.BaseFee(), test.rules); err != nil { + if err := exportTx.SemanticVerify(backend, tx, parent, parent.ethBlock.BaseFee()); err != nil { t.Fatal("newExportTx created an invalid transaction", err) } @@ -1968,7 +2009,7 @@ func TestNewExportTxMulticoin(t *testing.T) { t.Fatalf("Failed to accept export transaction due to: %s", err) } - if err := vm.ctx.SharedMemory.Apply(map[ids.ID]*atomic.Requests{chainID: {PutRequests: atomicRequests.PutRequests}}, commitBatch); err != nil { + if err := vm.ctx.SharedMemory.Apply(map[ids.ID]*avalancheatomic.Requests{chainID: {PutRequests: atomicRequests.PutRequests}}, commitBatch); err != nil { t.Fatal(err) } @@ -1981,7 +2022,7 @@ func TestNewExportTxMulticoin(t *testing.T) { t.Fatal(err) } - addr := GetEthAddress(testKeys[0]) + addr := atomic.GetEthAddress(testKeys[0]) if stdb.GetBalance(addr).Cmp(uint256.NewInt(test.bal*units.Avax)) != 0 { t.Fatalf("address balance %s equal %s not %s", addr.String(), stdb.GetBalance(addr), new(big.Int).SetUint64(test.bal*units.Avax)) } diff --git a/plugin/evm/formatting.go b/plugin/evm/formatting.go index ba9cea589f..feeab134b7 100644 --- a/plugin/evm/formatting.go +++ b/plugin/evm/formatting.go @@ -8,10 +8,7 @@ import ( "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/utils/constants" - "github.com/ava-labs/avalanchego/utils/crypto/secp256k1" "github.com/ava-labs/avalanchego/utils/formatting/address" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/crypto" ) // ParseServiceAddress get address ID from address string, being it either localized (using address manager, @@ -53,21 +50,3 @@ func (vm *VM) FormatAddress(chainID ids.ID, addr ids.ShortID) (string, error) { hrp := constants.GetHRP(vm.ctx.NetworkID) return address.Format(chainIDAlias, hrp, addr.Bytes()) } - -// ParseEthAddress parses [addrStr] and returns an Ethereum address -func ParseEthAddress(addrStr string) (common.Address, error) { - if !common.IsHexAddress(addrStr) { - return common.Address{}, errInvalidAddr - } - return common.HexToAddress(addrStr), nil -} - -// GetEthAddress returns the ethereum address derived from [privKey] -func GetEthAddress(privKey *secp256k1.PrivateKey) common.Address { - return PublicKeyToEthAddress(privKey.PublicKey()) -} - -// PublicKeyToEthAddress returns the ethereum address derived from [pubKey] -func PublicKeyToEthAddress(pubKey *secp256k1.PublicKey) common.Address { - return crypto.PubkeyToAddress(*(pubKey.ToECDSA())) -} diff --git a/plugin/evm/gossip.go b/plugin/evm/gossip.go index a760936021..d6b377d13a 100644 --- a/plugin/evm/gossip.go +++ b/plugin/evm/gossip.go @@ -7,7 +7,7 @@ import ( "context" "fmt" "sync" - "sync/atomic" + syncatomic "sync/atomic" "time" ethcommon "github.com/ethereum/go-ethereum/common" @@ -24,6 +24,7 @@ import ( "github.com/ava-labs/coreth/core/txpool" "github.com/ava-labs/coreth/core/types" "github.com/ava-labs/coreth/eth" + "github.com/ava-labs/coreth/plugin/evm/atomic" ) const pendingTxsBuffer = 10 @@ -97,14 +98,14 @@ func (g GossipAtomicTxMarshaller) MarshalGossip(tx *GossipAtomicTx) ([]byte, err } func (g GossipAtomicTxMarshaller) UnmarshalGossip(bytes []byte) (*GossipAtomicTx, error) { - tx, err := ExtractAtomicTx(bytes, Codec) + tx, err := atomic.ExtractAtomicTx(bytes, atomic.Codec) return &GossipAtomicTx{ Tx: tx, }, err } type GossipAtomicTx struct { - Tx *Tx + Tx *atomic.Tx } func (tx *GossipAtomicTx) GossipID() ids.ID { @@ -133,7 +134,7 @@ type GossipEthTxPool struct { // subscribed is set to true when the gossip subscription is active // mostly used for testing - subscribed atomic.Bool + subscribed syncatomic.Bool } // IsSubscribed returns whether or not the gossip subscription is active. diff --git a/plugin/evm/gossip_test.go b/plugin/evm/gossip_test.go index 8ed7aee3cf..15ebd15871 100644 --- a/plugin/evm/gossip_test.go +++ b/plugin/evm/gossip_test.go @@ -22,6 +22,7 @@ import ( "github.com/ava-labs/coreth/core/types" "github.com/ava-labs/coreth/core/vm" "github.com/ava-labs/coreth/params" + "github.com/ava-labs/coreth/plugin/evm/atomic" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" "github.com/prometheus/client_golang/prometheus" @@ -33,15 +34,15 @@ func TestGossipAtomicTxMarshaller(t *testing.T) { require := require.New(t) want := &GossipAtomicTx{ - Tx: &Tx{ - UnsignedAtomicTx: &UnsignedImportTx{}, + Tx: &atomic.Tx{ + UnsignedAtomicTx: &atomic.UnsignedImportTx{}, Creds: []verify.Verifiable{}, }, } marshaller := GossipAtomicTxMarshaller{} key0 := testKeys[0] - require.NoError(want.Tx.Sign(Codec, [][]*secp256k1.PrivateKey{{key0}})) + require.NoError(want.Tx.Sign(atomic.Codec, [][]*secp256k1.PrivateKey{{key0}})) bytes, err := marshaller.MarshalGossip(want) require.NoError(err) @@ -54,14 +55,14 @@ func TestGossipAtomicTxMarshaller(t *testing.T) { func TestAtomicMempoolIterate(t *testing.T) { txs := []*GossipAtomicTx{ { - Tx: &Tx{ + Tx: &atomic.Tx{ UnsignedAtomicTx: &TestUnsignedTx{ IDV: ids.GenerateTestID(), }, }, }, { - Tx: &Tx{ + Tx: &atomic.Tx{ UnsignedAtomicTx: &TestUnsignedTx{ IDV: ids.GenerateTestID(), }, diff --git a/plugin/evm/handler.go b/plugin/evm/handler.go index ce970c822f..c4b41a85e7 100644 --- a/plugin/evm/handler.go +++ b/plugin/evm/handler.go @@ -11,6 +11,7 @@ import ( "github.com/ava-labs/coreth/core/txpool" "github.com/ava-labs/coreth/core/types" + "github.com/ava-labs/coreth/plugin/evm/atomic" "github.com/ava-labs/coreth/plugin/evm/message" ) @@ -47,15 +48,15 @@ func (h *GossipHandler) HandleAtomicTx(nodeID ids.NodeID, msg message.AtomicTxGo // In the case that the gossip message contains a transaction, // attempt to parse it and add it as a remote. - tx := Tx{} - if _, err := Codec.Unmarshal(msg.Tx, &tx); err != nil { + tx := atomic.Tx{} + if _, err := atomic.Codec.Unmarshal(msg.Tx, &tx); err != nil { log.Trace( "AppGossip provided invalid tx", "err", err, ) return nil } - unsignedBytes, err := Codec.Marshal(codecVersion, &tx.UnsignedAtomicTx) + unsignedBytes, err := atomic.Codec.Marshal(atomic.CodecVersion, &tx.UnsignedAtomicTx) if err != nil { log.Trace( "AppGossip failed to marshal unsigned tx", diff --git a/plugin/evm/import_tx_test.go b/plugin/evm/import_tx_test.go index ec8b2b2fb4..d254153712 100644 --- a/plugin/evm/import_tx_test.go +++ b/plugin/evm/import_tx_test.go @@ -8,10 +8,11 @@ import ( "testing" "github.com/ava-labs/coreth/params" + "github.com/ava-labs/coreth/plugin/evm/atomic" "github.com/ethereum/go-ethereum/common" "github.com/holiman/uint256" - "github.com/ava-labs/avalanchego/chains/atomic" + avalancheatomic "github.com/ava-labs/avalanchego/chains/atomic" "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/utils" "github.com/ava-labs/avalanchego/utils/constants" @@ -23,7 +24,7 @@ import ( // createImportTxOptions adds a UTXO to shared memory and generates a list of import transactions sending this UTXO // to each of the three test keys (conflicting transactions) -func createImportTxOptions(t *testing.T, vm *VM, sharedMemory *atomic.Memory) []*Tx { +func createImportTxOptions(t *testing.T, vm *VM, sharedMemory *avalancheatomic.Memory) []*atomic.Tx { utxo := &avax.UTXO{ UTXOID: avax.UTXOID{TxID: ids.GenerateTestID()}, Asset: avax.Asset{ID: vm.ctx.AVAXAssetID}, @@ -35,14 +36,14 @@ func createImportTxOptions(t *testing.T, vm *VM, sharedMemory *atomic.Memory) [] }, }, } - utxoBytes, err := vm.codec.Marshal(codecVersion, utxo) + utxoBytes, err := atomic.Codec.Marshal(atomic.CodecVersion, utxo) if err != nil { t.Fatal(err) } xChainSharedMemory := sharedMemory.NewSharedMemory(vm.ctx.XChainID) inputID := utxo.InputID() - if err := xChainSharedMemory.Apply(map[ids.ID]*atomic.Requests{vm.ctx.ChainID: {PutRequests: []*atomic.Element{{ + if err := xChainSharedMemory.Apply(map[ids.ID]*avalancheatomic.Requests{vm.ctx.ChainID: {PutRequests: []*avalancheatomic.Element{{ Key: inputID[:], Value: utxoBytes, Traits: [][]byte{ @@ -52,7 +53,7 @@ func createImportTxOptions(t *testing.T, vm *VM, sharedMemory *atomic.Memory) [] t.Fatal(err) } - importTxs := make([]*Tx, 0, 3) + importTxs := make([]*atomic.Tx, 0, 3) for _, ethAddr := range testEthAddrs { importTx, err := vm.newImportTx(vm.ctx.XChainID, ethAddr, initialBaseFee, []*secp256k1.PrivateKey{testKeys[0]}) if err != nil { @@ -69,7 +70,7 @@ func TestImportTxVerify(t *testing.T) { var importAmount uint64 = 10000000 txID := ids.GenerateTestID() - importTx := &UnsignedImportTx{ + importTx := &atomic.UnsignedImportTx{ NetworkID: ctx.NetworkID, BlockchainID: ctx.ChainID, SourceChain: ctx.XChainID, @@ -101,7 +102,7 @@ func TestImportTxVerify(t *testing.T) { }, }, }, - Outs: []EVMOutput{ + Outs: []atomic.EVMOutput{ { Address: testEthAddrs[0], Amount: importAmount - params.AvalancheAtomicTxFee, @@ -121,16 +122,16 @@ func TestImportTxVerify(t *testing.T) { tests := map[string]atomicTxVerifyTest{ "nil tx": { - generate: func(t *testing.T) UnsignedAtomicTx { - var importTx *UnsignedImportTx + generate: func(t *testing.T) atomic.UnsignedAtomicTx { + var importTx *atomic.UnsignedImportTx return importTx }, ctx: ctx, rules: apricotRulesPhase0, - expectedErr: errNilTx.Error(), + expectedErr: atomic.ErrNilTx.Error(), }, "valid import tx": { - generate: func(t *testing.T) UnsignedAtomicTx { + generate: func(t *testing.T) atomic.UnsignedAtomicTx { return importTx }, ctx: ctx, @@ -138,7 +139,7 @@ func TestImportTxVerify(t *testing.T) { expectedErr: "", // Expect this transaction to be valid in Apricot Phase 0 }, "valid import tx banff": { - generate: func(t *testing.T) UnsignedAtomicTx { + generate: func(t *testing.T) atomic.UnsignedAtomicTx { return importTx }, ctx: ctx, @@ -146,37 +147,37 @@ func TestImportTxVerify(t *testing.T) { expectedErr: "", // Expect this transaction to be valid in Banff }, "invalid network ID": { - generate: func(t *testing.T) UnsignedAtomicTx { + generate: func(t *testing.T) atomic.UnsignedAtomicTx { tx := *importTx tx.NetworkID++ return &tx }, ctx: ctx, rules: apricotRulesPhase0, - expectedErr: errWrongNetworkID.Error(), + expectedErr: atomic.ErrWrongNetworkID.Error(), }, "invalid blockchain ID": { - generate: func(t *testing.T) UnsignedAtomicTx { + generate: func(t *testing.T) atomic.UnsignedAtomicTx { tx := *importTx tx.BlockchainID = ids.GenerateTestID() return &tx }, ctx: ctx, rules: apricotRulesPhase0, - expectedErr: errWrongBlockchainID.Error(), + expectedErr: atomic.ErrWrongChainID.Error(), }, "P-chain source before AP5": { - generate: func(t *testing.T) UnsignedAtomicTx { + generate: func(t *testing.T) atomic.UnsignedAtomicTx { tx := *importTx tx.SourceChain = constants.PlatformChainID return &tx }, ctx: ctx, rules: apricotRulesPhase0, - expectedErr: errWrongChainID.Error(), + expectedErr: atomic.ErrWrongChainID.Error(), }, "P-chain source after AP5": { - generate: func(t *testing.T) UnsignedAtomicTx { + generate: func(t *testing.T) atomic.UnsignedAtomicTx { tx := *importTx tx.SourceChain = constants.PlatformChainID return &tx @@ -185,27 +186,27 @@ func TestImportTxVerify(t *testing.T) { rules: apricotRulesPhase5, }, "invalid source chain ID": { - generate: func(t *testing.T) UnsignedAtomicTx { + generate: func(t *testing.T) atomic.UnsignedAtomicTx { tx := *importTx tx.SourceChain = ids.GenerateTestID() return &tx }, ctx: ctx, rules: apricotRulesPhase5, - expectedErr: errWrongChainID.Error(), + expectedErr: atomic.ErrWrongChainID.Error(), }, "no inputs": { - generate: func(t *testing.T) UnsignedAtomicTx { + generate: func(t *testing.T) atomic.UnsignedAtomicTx { tx := *importTx tx.ImportedInputs = nil return &tx }, ctx: ctx, rules: apricotRulesPhase0, - expectedErr: errNoImportInputs.Error(), + expectedErr: atomic.ErrNoImportInputs.Error(), }, "inputs sorted incorrectly": { - generate: func(t *testing.T) UnsignedAtomicTx { + generate: func(t *testing.T) atomic.UnsignedAtomicTx { tx := *importTx tx.ImportedInputs = []*avax.TransferableInput{ tx.ImportedInputs[1], @@ -215,10 +216,10 @@ func TestImportTxVerify(t *testing.T) { }, ctx: ctx, rules: apricotRulesPhase0, - expectedErr: errInputsNotSortedUnique.Error(), + expectedErr: atomic.ErrInputsNotSortedUnique.Error(), }, "invalid input": { - generate: func(t *testing.T) UnsignedAtomicTx { + generate: func(t *testing.T) atomic.UnsignedAtomicTx { tx := *importTx tx.ImportedInputs = []*avax.TransferableInput{ tx.ImportedInputs[0], @@ -231,9 +232,9 @@ func TestImportTxVerify(t *testing.T) { expectedErr: "atomic input failed verification", }, "unsorted outputs phase 0 passes verification": { - generate: func(t *testing.T) UnsignedAtomicTx { + generate: func(t *testing.T) atomic.UnsignedAtomicTx { tx := *importTx - tx.Outs = []EVMOutput{ + tx.Outs = []atomic.EVMOutput{ tx.Outs[1], tx.Outs[0], } @@ -244,9 +245,9 @@ func TestImportTxVerify(t *testing.T) { expectedErr: "", }, "non-unique outputs phase 0 passes verification": { - generate: func(t *testing.T) UnsignedAtomicTx { + generate: func(t *testing.T) atomic.UnsignedAtomicTx { tx := *importTx - tx.Outs = []EVMOutput{ + tx.Outs = []atomic.EVMOutput{ tx.Outs[0], tx.Outs[0], } @@ -257,9 +258,9 @@ func TestImportTxVerify(t *testing.T) { expectedErr: "", }, "unsorted outputs phase 1 fails verification": { - generate: func(t *testing.T) UnsignedAtomicTx { + generate: func(t *testing.T) atomic.UnsignedAtomicTx { tx := *importTx - tx.Outs = []EVMOutput{ + tx.Outs = []atomic.EVMOutput{ tx.Outs[1], tx.Outs[0], } @@ -267,12 +268,12 @@ func TestImportTxVerify(t *testing.T) { }, ctx: ctx, rules: apricotRulesPhase1, - expectedErr: errOutputsNotSorted.Error(), + expectedErr: atomic.ErrOutputsNotSorted.Error(), }, "non-unique outputs phase 1 passes verification": { - generate: func(t *testing.T) UnsignedAtomicTx { + generate: func(t *testing.T) atomic.UnsignedAtomicTx { tx := *importTx - tx.Outs = []EVMOutput{ + tx.Outs = []atomic.EVMOutput{ tx.Outs[0], tx.Outs[0], } @@ -283,9 +284,9 @@ func TestImportTxVerify(t *testing.T) { expectedErr: "", }, "outputs not sorted and unique phase 2 fails verification": { - generate: func(t *testing.T) UnsignedAtomicTx { + generate: func(t *testing.T) atomic.UnsignedAtomicTx { tx := *importTx - tx.Outs = []EVMOutput{ + tx.Outs = []atomic.EVMOutput{ tx.Outs[0], tx.Outs[0], } @@ -293,12 +294,12 @@ func TestImportTxVerify(t *testing.T) { }, ctx: ctx, rules: apricotRulesPhase2, - expectedErr: errOutputsNotSortedUnique.Error(), + expectedErr: atomic.ErrOutputsNotSortedUnique.Error(), }, "outputs not sorted phase 2 fails verification": { - generate: func(t *testing.T) UnsignedAtomicTx { + generate: func(t *testing.T) atomic.UnsignedAtomicTx { tx := *importTx - tx.Outs = []EVMOutput{ + tx.Outs = []atomic.EVMOutput{ tx.Outs[1], tx.Outs[0], } @@ -306,12 +307,12 @@ func TestImportTxVerify(t *testing.T) { }, ctx: ctx, rules: apricotRulesPhase2, - expectedErr: errOutputsNotSortedUnique.Error(), + expectedErr: atomic.ErrOutputsNotSortedUnique.Error(), }, "invalid EVMOutput fails verification": { - generate: func(t *testing.T) UnsignedAtomicTx { + generate: func(t *testing.T) atomic.UnsignedAtomicTx { tx := *importTx - tx.Outs = []EVMOutput{ + tx.Outs = []atomic.EVMOutput{ { Address: testEthAddrs[0], Amount: 0, @@ -325,17 +326,17 @@ func TestImportTxVerify(t *testing.T) { expectedErr: "EVM Output failed verification", }, "no outputs apricot phase 3": { - generate: func(t *testing.T) UnsignedAtomicTx { + generate: func(t *testing.T) atomic.UnsignedAtomicTx { tx := *importTx tx.Outs = nil return &tx }, ctx: ctx, rules: apricotRulesPhase3, - expectedErr: errNoEVMOutputs.Error(), + expectedErr: atomic.ErrNoEVMOutputs.Error(), }, "non-AVAX input Apricot Phase 6": { - generate: func(t *testing.T) UnsignedAtomicTx { + generate: func(t *testing.T) atomic.UnsignedAtomicTx { tx := *importTx tx.ImportedInputs = []*avax.TransferableInput{ { @@ -359,9 +360,9 @@ func TestImportTxVerify(t *testing.T) { expectedErr: "", }, "non-AVAX output Apricot Phase 6": { - generate: func(t *testing.T) UnsignedAtomicTx { + generate: func(t *testing.T) atomic.UnsignedAtomicTx { tx := *importTx - tx.Outs = []EVMOutput{ + tx.Outs = []atomic.EVMOutput{ { Address: importTx.Outs[0].Address, Amount: importTx.Outs[0].Amount, @@ -375,7 +376,7 @@ func TestImportTxVerify(t *testing.T) { expectedErr: "", }, "non-AVAX input Banff": { - generate: func(t *testing.T) UnsignedAtomicTx { + generate: func(t *testing.T) atomic.UnsignedAtomicTx { tx := *importTx tx.ImportedInputs = []*avax.TransferableInput{ { @@ -396,12 +397,12 @@ func TestImportTxVerify(t *testing.T) { }, ctx: ctx, rules: banffRules, - expectedErr: errImportNonAVAXInputBanff.Error(), + expectedErr: atomic.ErrImportNonAVAXInputBanff.Error(), }, "non-AVAX output Banff": { - generate: func(t *testing.T) UnsignedAtomicTx { + generate: func(t *testing.T) atomic.UnsignedAtomicTx { tx := *importTx - tx.Outs = []EVMOutput{ + tx.Outs = []atomic.EVMOutput{ { Address: importTx.Outs[0].Address, Amount: importTx.Outs[0].Amount, @@ -412,7 +413,7 @@ func TestImportTxVerify(t *testing.T) { }, ctx: ctx, rules: banffRules, - expectedErr: errImportNonAVAXOutputBanff.Error(), + expectedErr: atomic.ErrImportNonAVAXOutputBanff.Error(), }, } for name, test := range tests { @@ -426,7 +427,7 @@ func TestNewImportTx(t *testing.T) { importAmount := uint64(5000000) // createNewImportAVAXTx adds a UTXO to shared memory and then constructs a new import transaction // and checks that it has the correct fee for the base fee that has been used - createNewImportAVAXTx := func(t *testing.T, vm *VM, sharedMemory *atomic.Memory) *Tx { + createNewImportAVAXTx := func(t *testing.T, vm *VM, sharedMemory *avalancheatomic.Memory) *atomic.Tx { txID := ids.GenerateTestID() _, err := addUTXO(sharedMemory, vm.ctx, txID, 0, vm.ctx.AVAXAssetID, importAmount, testShortIDAddrs[0]) if err != nil { @@ -450,7 +451,7 @@ func TestNewImportTx(t *testing.T) { if err != nil { t.Fatal(err) } - actualFee, err = CalculateDynamicFee(actualCost, initialBaseFee) + actualFee, err = atomic.CalculateDynamicFee(actualCost, initialBaseFee) if err != nil { t.Fatal(err) } @@ -496,8 +497,8 @@ func TestNewImportTx(t *testing.T) { } expectedRemainingBalance := new(uint256.Int).Mul( - uint256.NewInt(importAmount-actualAVAXBurned), x2cRate) - addr := GetEthAddress(testKeys[0]) + uint256.NewInt(importAmount-actualAVAXBurned), atomic.X2CRate) + addr := atomic.GetEthAddress(testKeys[0]) if actualBalance := sdb.GetBalance(addr); actualBalance.Cmp(expectedRemainingBalance) != 0 { t.Fatalf("address remaining balance %s equal %s not %s", addr.String(), actualBalance, expectedRemainingBalance) } @@ -543,7 +544,7 @@ func TestImportTxGasCost(t *testing.T) { importAmount := uint64(5000000) tests := map[string]struct { - UnsignedImportTx *UnsignedImportTx + UnsignedImportTx *atomic.UnsignedImportTx Keys [][]*secp256k1.PrivateKey ExpectedGasUsed uint64 @@ -552,7 +553,7 @@ func TestImportTxGasCost(t *testing.T) { FixedFee bool }{ "simple import": { - UnsignedImportTx: &UnsignedImportTx{ + UnsignedImportTx: &atomic.UnsignedImportTx{ NetworkID: networkID, BlockchainID: chainID, SourceChain: xChainID, @@ -564,7 +565,7 @@ func TestImportTxGasCost(t *testing.T) { Input: secp256k1fx.Input{SigIndices: []uint32{0}}, }, }}, - Outs: []EVMOutput{{ + Outs: []atomic.EVMOutput{{ Address: testEthAddrs[0], Amount: importAmount, AssetID: avaxAssetID, @@ -576,7 +577,7 @@ func TestImportTxGasCost(t *testing.T) { BaseFee: big.NewInt(25 * params.GWei), }, "simple import 1wei": { - UnsignedImportTx: &UnsignedImportTx{ + UnsignedImportTx: &atomic.UnsignedImportTx{ NetworkID: networkID, BlockchainID: chainID, SourceChain: xChainID, @@ -588,7 +589,7 @@ func TestImportTxGasCost(t *testing.T) { Input: secp256k1fx.Input{SigIndices: []uint32{0}}, }, }}, - Outs: []EVMOutput{{ + Outs: []atomic.EVMOutput{{ Address: testEthAddrs[0], Amount: importAmount, AssetID: avaxAssetID, @@ -600,7 +601,7 @@ func TestImportTxGasCost(t *testing.T) { BaseFee: big.NewInt(1), }, "simple import 1wei + fixed fee": { - UnsignedImportTx: &UnsignedImportTx{ + UnsignedImportTx: &atomic.UnsignedImportTx{ NetworkID: networkID, BlockchainID: chainID, SourceChain: xChainID, @@ -612,7 +613,7 @@ func TestImportTxGasCost(t *testing.T) { Input: secp256k1fx.Input{SigIndices: []uint32{0}}, }, }}, - Outs: []EVMOutput{{ + Outs: []atomic.EVMOutput{{ Address: testEthAddrs[0], Amount: importAmount, AssetID: avaxAssetID, @@ -625,7 +626,7 @@ func TestImportTxGasCost(t *testing.T) { FixedFee: true, }, "simple ANT import": { - UnsignedImportTx: &UnsignedImportTx{ + UnsignedImportTx: &atomic.UnsignedImportTx{ NetworkID: networkID, BlockchainID: chainID, SourceChain: xChainID, @@ -647,7 +648,7 @@ func TestImportTxGasCost(t *testing.T) { }, }, }, - Outs: []EVMOutput{ + Outs: []atomic.EVMOutput{ { Address: testEthAddrs[0], Amount: importAmount, @@ -661,7 +662,7 @@ func TestImportTxGasCost(t *testing.T) { BaseFee: big.NewInt(25 * params.GWei), }, "complex ANT import": { - UnsignedImportTx: &UnsignedImportTx{ + UnsignedImportTx: &atomic.UnsignedImportTx{ NetworkID: networkID, BlockchainID: chainID, SourceChain: xChainID, @@ -683,7 +684,7 @@ func TestImportTxGasCost(t *testing.T) { }, }, }, - Outs: []EVMOutput{ + Outs: []atomic.EVMOutput{ { Address: testEthAddrs[0], Amount: importAmount, @@ -702,7 +703,7 @@ func TestImportTxGasCost(t *testing.T) { BaseFee: big.NewInt(25 * params.GWei), }, "multisig import": { - UnsignedImportTx: &UnsignedImportTx{ + UnsignedImportTx: &atomic.UnsignedImportTx{ NetworkID: networkID, BlockchainID: chainID, SourceChain: xChainID, @@ -714,7 +715,7 @@ func TestImportTxGasCost(t *testing.T) { Input: secp256k1fx.Input{SigIndices: []uint32{0, 1}}, }, }}, - Outs: []EVMOutput{{ + Outs: []atomic.EVMOutput{{ Address: testEthAddrs[0], Amount: importAmount, AssetID: avaxAssetID, @@ -726,7 +727,7 @@ func TestImportTxGasCost(t *testing.T) { BaseFee: big.NewInt(25 * params.GWei), }, "large import": { - UnsignedImportTx: &UnsignedImportTx{ + UnsignedImportTx: &atomic.UnsignedImportTx{ NetworkID: networkID, BlockchainID: chainID, SourceChain: xChainID, @@ -812,7 +813,7 @@ func TestImportTxGasCost(t *testing.T) { }, }, }, - Outs: []EVMOutput{ + Outs: []atomic.EVMOutput{ { Address: testEthAddrs[0], Amount: importAmount * 10, @@ -840,10 +841,10 @@ func TestImportTxGasCost(t *testing.T) { for name, test := range tests { t.Run(name, func(t *testing.T) { - tx := &Tx{UnsignedAtomicTx: test.UnsignedImportTx} + tx := &atomic.Tx{UnsignedAtomicTx: test.UnsignedImportTx} // Sign with the correct key - if err := tx.Sign(Codec, test.Keys); err != nil { + if err := tx.Sign(atomic.Codec, test.Keys); err != nil { t.Fatal(err) } @@ -855,7 +856,7 @@ func TestImportTxGasCost(t *testing.T) { t.Fatalf("Expected gasUsed to be %d, but found %d", test.ExpectedGasUsed, gasUsed) } - fee, err := CalculateDynamicFee(gasUsed, test.BaseFee) + fee, err := atomic.CalculateDynamicFee(gasUsed, test.BaseFee) if err != nil { t.Fatal(err) } @@ -869,8 +870,8 @@ func TestImportTxGasCost(t *testing.T) { func TestImportTxSemanticVerify(t *testing.T) { tests := map[string]atomicTxTest{ "UTXO not present during bootstrapping": { - setup: func(t *testing.T, vm *VM, sharedMemory *atomic.Memory) *Tx { - tx := &Tx{UnsignedAtomicTx: &UnsignedImportTx{ + setup: func(t *testing.T, vm *VM, sharedMemory *avalancheatomic.Memory) *atomic.Tx { + tx := &atomic.Tx{UnsignedAtomicTx: &atomic.UnsignedImportTx{ NetworkID: vm.ctx.NetworkID, BlockchainID: vm.ctx.ChainID, SourceChain: vm.ctx.XChainID, @@ -884,13 +885,13 @@ func TestImportTxSemanticVerify(t *testing.T) { Input: secp256k1fx.Input{SigIndices: []uint32{0}}, }, }}, - Outs: []EVMOutput{{ + Outs: []atomic.EVMOutput{{ Address: testEthAddrs[0], Amount: 1, AssetID: vm.ctx.AVAXAssetID, }}, }} - if err := tx.Sign(vm.codec, [][]*secp256k1.PrivateKey{{testKeys[0]}}); err != nil { + if err := tx.Sign(atomic.Codec, [][]*secp256k1.PrivateKey{{testKeys[0]}}); err != nil { t.Fatal(err) } return tx @@ -898,8 +899,8 @@ func TestImportTxSemanticVerify(t *testing.T) { bootstrapping: true, }, "UTXO not present": { - setup: func(t *testing.T, vm *VM, sharedMemory *atomic.Memory) *Tx { - tx := &Tx{UnsignedAtomicTx: &UnsignedImportTx{ + setup: func(t *testing.T, vm *VM, sharedMemory *avalancheatomic.Memory) *atomic.Tx { + tx := &atomic.Tx{UnsignedAtomicTx: &atomic.UnsignedImportTx{ NetworkID: vm.ctx.NetworkID, BlockchainID: vm.ctx.ChainID, SourceChain: vm.ctx.XChainID, @@ -913,13 +914,13 @@ func TestImportTxSemanticVerify(t *testing.T) { Input: secp256k1fx.Input{SigIndices: []uint32{0}}, }, }}, - Outs: []EVMOutput{{ + Outs: []atomic.EVMOutput{{ Address: testEthAddrs[0], Amount: 1, AssetID: vm.ctx.AVAXAssetID, }}, }} - if err := tx.Sign(vm.codec, [][]*secp256k1.PrivateKey{{testKeys[0]}}); err != nil { + if err := tx.Sign(atomic.Codec, [][]*secp256k1.PrivateKey{{testKeys[0]}}); err != nil { t.Fatal(err) } return tx @@ -927,11 +928,11 @@ func TestImportTxSemanticVerify(t *testing.T) { semanticVerifyErr: "failed to fetch import UTXOs from", }, "garbage UTXO": { - setup: func(t *testing.T, vm *VM, sharedMemory *atomic.Memory) *Tx { + setup: func(t *testing.T, vm *VM, sharedMemory *avalancheatomic.Memory) *atomic.Tx { utxoID := avax.UTXOID{TxID: ids.GenerateTestID()} xChainSharedMemory := sharedMemory.NewSharedMemory(vm.ctx.XChainID) inputID := utxoID.InputID() - if err := xChainSharedMemory.Apply(map[ids.ID]*atomic.Requests{vm.ctx.ChainID: {PutRequests: []*atomic.Element{{ + if err := xChainSharedMemory.Apply(map[ids.ID]*avalancheatomic.Requests{vm.ctx.ChainID: {PutRequests: []*avalancheatomic.Element{{ Key: inputID[:], Value: []byte("hey there"), Traits: [][]byte{ @@ -941,7 +942,7 @@ func TestImportTxSemanticVerify(t *testing.T) { t.Fatal(err) } - tx := &Tx{UnsignedAtomicTx: &UnsignedImportTx{ + tx := &atomic.Tx{UnsignedAtomicTx: &atomic.UnsignedImportTx{ NetworkID: vm.ctx.NetworkID, BlockchainID: vm.ctx.ChainID, SourceChain: vm.ctx.XChainID, @@ -953,13 +954,13 @@ func TestImportTxSemanticVerify(t *testing.T) { Input: secp256k1fx.Input{SigIndices: []uint32{0}}, }, }}, - Outs: []EVMOutput{{ + Outs: []atomic.EVMOutput{{ Address: testEthAddrs[0], Amount: 1, AssetID: vm.ctx.AVAXAssetID, }}, }} - if err := tx.Sign(vm.codec, [][]*secp256k1.PrivateKey{{testKeys[0]}}); err != nil { + if err := tx.Sign(atomic.Codec, [][]*secp256k1.PrivateKey{{testKeys[0]}}); err != nil { t.Fatal(err) } return tx @@ -967,7 +968,7 @@ func TestImportTxSemanticVerify(t *testing.T) { semanticVerifyErr: "failed to unmarshal UTXO", }, "UTXO AssetID mismatch": { - setup: func(t *testing.T, vm *VM, sharedMemory *atomic.Memory) *Tx { + setup: func(t *testing.T, vm *VM, sharedMemory *avalancheatomic.Memory) *atomic.Tx { txID := ids.GenerateTestID() expectedAssetID := ids.GenerateTestID() utxo, err := addUTXO(sharedMemory, vm.ctx, txID, 0, expectedAssetID, 1, testShortIDAddrs[0]) @@ -975,7 +976,7 @@ func TestImportTxSemanticVerify(t *testing.T) { t.Fatal(err) } - tx := &Tx{UnsignedAtomicTx: &UnsignedImportTx{ + tx := &atomic.Tx{UnsignedAtomicTx: &atomic.UnsignedImportTx{ NetworkID: vm.ctx.NetworkID, BlockchainID: vm.ctx.ChainID, SourceChain: vm.ctx.XChainID, @@ -987,28 +988,28 @@ func TestImportTxSemanticVerify(t *testing.T) { Input: secp256k1fx.Input{SigIndices: []uint32{0}}, }, }}, - Outs: []EVMOutput{{ + Outs: []atomic.EVMOutput{{ Address: testEthAddrs[0], Amount: 1, AssetID: vm.ctx.AVAXAssetID, }}, }} - if err := tx.Sign(vm.codec, [][]*secp256k1.PrivateKey{{testKeys[0]}}); err != nil { + if err := tx.Sign(atomic.Codec, [][]*secp256k1.PrivateKey{{testKeys[0]}}); err != nil { t.Fatal(err) } return tx }, - semanticVerifyErr: errAssetIDMismatch.Error(), + semanticVerifyErr: atomic.ErrAssetIDMismatch.Error(), }, "insufficient AVAX funds": { - setup: func(t *testing.T, vm *VM, sharedMemory *atomic.Memory) *Tx { + setup: func(t *testing.T, vm *VM, sharedMemory *avalancheatomic.Memory) *atomic.Tx { txID := ids.GenerateTestID() utxo, err := addUTXO(sharedMemory, vm.ctx, txID, 0, vm.ctx.AVAXAssetID, 1, testShortIDAddrs[0]) if err != nil { t.Fatal(err) } - tx := &Tx{UnsignedAtomicTx: &UnsignedImportTx{ + tx := &atomic.Tx{UnsignedAtomicTx: &atomic.UnsignedImportTx{ NetworkID: vm.ctx.NetworkID, BlockchainID: vm.ctx.ChainID, SourceChain: vm.ctx.XChainID, @@ -1020,13 +1021,13 @@ func TestImportTxSemanticVerify(t *testing.T) { Input: secp256k1fx.Input{SigIndices: []uint32{0}}, }, }}, - Outs: []EVMOutput{{ + Outs: []atomic.EVMOutput{{ Address: testEthAddrs[0], Amount: 2, // Produce more output than is consumed by the transaction AssetID: vm.ctx.AVAXAssetID, }}, }} - if err := tx.Sign(vm.codec, [][]*secp256k1.PrivateKey{{testKeys[0]}}); err != nil { + if err := tx.Sign(atomic.Codec, [][]*secp256k1.PrivateKey{{testKeys[0]}}); err != nil { t.Fatal(err) } return tx @@ -1034,7 +1035,7 @@ func TestImportTxSemanticVerify(t *testing.T) { semanticVerifyErr: "import tx flow check failed due to", }, "insufficient non-AVAX funds": { - setup: func(t *testing.T, vm *VM, sharedMemory *atomic.Memory) *Tx { + setup: func(t *testing.T, vm *VM, sharedMemory *avalancheatomic.Memory) *atomic.Tx { txID := ids.GenerateTestID() assetID := ids.GenerateTestID() utxo, err := addUTXO(sharedMemory, vm.ctx, txID, 0, assetID, 1, testShortIDAddrs[0]) @@ -1042,7 +1043,7 @@ func TestImportTxSemanticVerify(t *testing.T) { t.Fatal(err) } - tx := &Tx{UnsignedAtomicTx: &UnsignedImportTx{ + tx := &atomic.Tx{UnsignedAtomicTx: &atomic.UnsignedImportTx{ NetworkID: vm.ctx.NetworkID, BlockchainID: vm.ctx.ChainID, SourceChain: vm.ctx.XChainID, @@ -1054,13 +1055,13 @@ func TestImportTxSemanticVerify(t *testing.T) { Input: secp256k1fx.Input{SigIndices: []uint32{0}}, }, }}, - Outs: []EVMOutput{{ + Outs: []atomic.EVMOutput{{ Address: testEthAddrs[0], Amount: 2, // Produce more output than is consumed by the transaction AssetID: assetID, }}, }} - if err := tx.Sign(vm.codec, [][]*secp256k1.PrivateKey{{testKeys[0]}}); err != nil { + if err := tx.Sign(atomic.Codec, [][]*secp256k1.PrivateKey{{testKeys[0]}}); err != nil { t.Fatal(err) } return tx @@ -1068,14 +1069,14 @@ func TestImportTxSemanticVerify(t *testing.T) { semanticVerifyErr: "import tx flow check failed due to", }, "no signatures": { - setup: func(t *testing.T, vm *VM, sharedMemory *atomic.Memory) *Tx { + setup: func(t *testing.T, vm *VM, sharedMemory *avalancheatomic.Memory) *atomic.Tx { txID := ids.GenerateTestID() utxo, err := addUTXO(sharedMemory, vm.ctx, txID, 0, vm.ctx.AVAXAssetID, 1, testShortIDAddrs[0]) if err != nil { t.Fatal(err) } - tx := &Tx{UnsignedAtomicTx: &UnsignedImportTx{ + tx := &atomic.Tx{UnsignedAtomicTx: &atomic.UnsignedImportTx{ NetworkID: vm.ctx.NetworkID, BlockchainID: vm.ctx.ChainID, SourceChain: vm.ctx.XChainID, @@ -1087,13 +1088,13 @@ func TestImportTxSemanticVerify(t *testing.T) { Input: secp256k1fx.Input{SigIndices: []uint32{0}}, }, }}, - Outs: []EVMOutput{{ + Outs: []atomic.EVMOutput{{ Address: testEthAddrs[0], Amount: 1, AssetID: vm.ctx.AVAXAssetID, }}, }} - if err := tx.Sign(vm.codec, nil); err != nil { + if err := tx.Sign(atomic.Codec, nil); err != nil { t.Fatal(err) } return tx @@ -1101,14 +1102,14 @@ func TestImportTxSemanticVerify(t *testing.T) { semanticVerifyErr: "import tx contained mismatched number of inputs/credentials", }, "incorrect signature": { - setup: func(t *testing.T, vm *VM, sharedMemory *atomic.Memory) *Tx { + setup: func(t *testing.T, vm *VM, sharedMemory *avalancheatomic.Memory) *atomic.Tx { txID := ids.GenerateTestID() utxo, err := addUTXO(sharedMemory, vm.ctx, txID, 0, vm.ctx.AVAXAssetID, 1, testShortIDAddrs[0]) if err != nil { t.Fatal(err) } - tx := &Tx{UnsignedAtomicTx: &UnsignedImportTx{ + tx := &atomic.Tx{UnsignedAtomicTx: &atomic.UnsignedImportTx{ NetworkID: vm.ctx.NetworkID, BlockchainID: vm.ctx.ChainID, SourceChain: vm.ctx.XChainID, @@ -1120,14 +1121,14 @@ func TestImportTxSemanticVerify(t *testing.T) { Input: secp256k1fx.Input{SigIndices: []uint32{0}}, }, }}, - Outs: []EVMOutput{{ + Outs: []atomic.EVMOutput{{ Address: testEthAddrs[0], Amount: 1, AssetID: vm.ctx.AVAXAssetID, }}, }} // Sign the transaction with the incorrect key - if err := tx.Sign(vm.codec, [][]*secp256k1.PrivateKey{{testKeys[1]}}); err != nil { + if err := tx.Sign(atomic.Codec, [][]*secp256k1.PrivateKey{{testKeys[1]}}); err != nil { t.Fatal(err) } return tx @@ -1135,14 +1136,14 @@ func TestImportTxSemanticVerify(t *testing.T) { semanticVerifyErr: "import tx transfer failed verification", }, "non-unique EVM Outputs": { - setup: func(t *testing.T, vm *VM, sharedMemory *atomic.Memory) *Tx { + setup: func(t *testing.T, vm *VM, sharedMemory *avalancheatomic.Memory) *atomic.Tx { txID := ids.GenerateTestID() utxo, err := addUTXO(sharedMemory, vm.ctx, txID, 0, vm.ctx.AVAXAssetID, 2, testShortIDAddrs[0]) if err != nil { t.Fatal(err) } - tx := &Tx{UnsignedAtomicTx: &UnsignedImportTx{ + tx := &atomic.Tx{UnsignedAtomicTx: &atomic.UnsignedImportTx{ NetworkID: vm.ctx.NetworkID, BlockchainID: vm.ctx.ChainID, SourceChain: vm.ctx.XChainID, @@ -1154,7 +1155,7 @@ func TestImportTxSemanticVerify(t *testing.T) { Input: secp256k1fx.Input{SigIndices: []uint32{0}}, }, }}, - Outs: []EVMOutput{ + Outs: []atomic.EVMOutput{ { Address: testEthAddrs[0], Amount: 1, @@ -1167,13 +1168,13 @@ func TestImportTxSemanticVerify(t *testing.T) { }, }, }} - if err := tx.Sign(vm.codec, [][]*secp256k1.PrivateKey{{testKeys[0]}}); err != nil { + if err := tx.Sign(atomic.Codec, [][]*secp256k1.PrivateKey{{testKeys[0]}}); err != nil { t.Fatal(err) } return tx }, genesisJSON: genesisJSONApricotPhase3, - semanticVerifyErr: errOutputsNotSortedUnique.Error(), + semanticVerifyErr: atomic.ErrOutputsNotSortedUnique.Error(), }, } @@ -1188,14 +1189,14 @@ func TestImportTxEVMStateTransfer(t *testing.T) { assetID := ids.GenerateTestID() tests := map[string]atomicTxTest{ "AVAX UTXO": { - setup: func(t *testing.T, vm *VM, sharedMemory *atomic.Memory) *Tx { + setup: func(t *testing.T, vm *VM, sharedMemory *avalancheatomic.Memory) *atomic.Tx { txID := ids.GenerateTestID() utxo, err := addUTXO(sharedMemory, vm.ctx, txID, 0, vm.ctx.AVAXAssetID, 1, testShortIDAddrs[0]) if err != nil { t.Fatal(err) } - tx := &Tx{UnsignedAtomicTx: &UnsignedImportTx{ + tx := &atomic.Tx{UnsignedAtomicTx: &atomic.UnsignedImportTx{ NetworkID: vm.ctx.NetworkID, BlockchainID: vm.ctx.ChainID, SourceChain: vm.ctx.XChainID, @@ -1207,13 +1208,13 @@ func TestImportTxEVMStateTransfer(t *testing.T) { Input: secp256k1fx.Input{SigIndices: []uint32{0}}, }, }}, - Outs: []EVMOutput{{ + Outs: []atomic.EVMOutput{{ Address: testEthAddrs[0], Amount: 1, AssetID: vm.ctx.AVAXAssetID, }}, }} - if err := tx.Sign(vm.codec, [][]*secp256k1.PrivateKey{{testKeys[0]}}); err != nil { + if err := tx.Sign(atomic.Codec, [][]*secp256k1.PrivateKey{{testKeys[0]}}); err != nil { t.Fatal(err) } return tx @@ -1227,20 +1228,20 @@ func TestImportTxEVMStateTransfer(t *testing.T) { } avaxBalance := sdb.GetBalance(testEthAddrs[0]) - if avaxBalance.Cmp(x2cRate) != 0 { - t.Fatalf("Expected AVAX balance to be %d, found balance: %d", x2cRate, avaxBalance) + if avaxBalance.Cmp(atomic.X2CRate) != 0 { + t.Fatalf("Expected AVAX balance to be %d, found balance: %d", *atomic.X2CRate, avaxBalance) } }, }, "non-AVAX UTXO": { - setup: func(t *testing.T, vm *VM, sharedMemory *atomic.Memory) *Tx { + setup: func(t *testing.T, vm *VM, sharedMemory *avalancheatomic.Memory) *atomic.Tx { txID := ids.GenerateTestID() utxo, err := addUTXO(sharedMemory, vm.ctx, txID, 0, assetID, 1, testShortIDAddrs[0]) if err != nil { t.Fatal(err) } - tx := &Tx{UnsignedAtomicTx: &UnsignedImportTx{ + tx := &atomic.Tx{UnsignedAtomicTx: &atomic.UnsignedImportTx{ NetworkID: vm.ctx.NetworkID, BlockchainID: vm.ctx.ChainID, SourceChain: vm.ctx.XChainID, @@ -1252,13 +1253,13 @@ func TestImportTxEVMStateTransfer(t *testing.T) { Input: secp256k1fx.Input{SigIndices: []uint32{0}}, }, }}, - Outs: []EVMOutput{{ + Outs: []atomic.EVMOutput{{ Address: testEthAddrs[0], Amount: 1, AssetID: assetID, }}, }} - if err := tx.Sign(vm.codec, [][]*secp256k1.PrivateKey{{testKeys[0]}}); err != nil { + if err := tx.Sign(atomic.Codec, [][]*secp256k1.PrivateKey{{testKeys[0]}}); err != nil { t.Fatal(err) } return tx diff --git a/plugin/evm/mempool.go b/plugin/evm/mempool.go index 69a832cd2e..acb8db4e3f 100644 --- a/plugin/evm/mempool.go +++ b/plugin/evm/mempool.go @@ -15,6 +15,7 @@ import ( "github.com/prometheus/client_golang/prometheus" "github.com/ava-labs/coreth/metrics" + "github.com/ava-labs/coreth/plugin/evm/atomic" "github.com/ethereum/go-ethereum/log" ) @@ -58,12 +59,12 @@ type Mempool struct { // maxSize is the maximum number of transactions allowed to be kept in mempool maxSize int // currentTxs is the set of transactions about to be added to a block. - currentTxs map[ids.ID]*Tx + currentTxs map[ids.ID]*atomic.Tx // issuedTxs is the set of transactions that have been issued into a new block - issuedTxs map[ids.ID]*Tx + issuedTxs map[ids.ID]*atomic.Tx // discardedTxs is an LRU Cache of transactions that have been discarded after failing // verification. - discardedTxs *cache.LRU[ids.ID, *Tx] + discardedTxs *cache.LRU[ids.ID, *atomic.Tx] // Pending is a channel of length one, which the mempool ensures has an item on // it as long as there is an unissued transaction remaining in [txs] Pending chan struct{} @@ -71,17 +72,17 @@ type Mempool struct { // NOTE: [txHeap] ONLY contains pending txs txHeap *txHeap // utxoSpenders maps utxoIDs to the transaction consuming them in the mempool - utxoSpenders map[ids.ID]*Tx + utxoSpenders map[ids.ID]*atomic.Tx // bloom is a bloom filter containing the txs in the mempool bloom *gossip.BloomFilter metrics *mempoolMetrics - verify func(tx *Tx) error + verify func(tx *atomic.Tx) error } // NewMempool returns a Mempool with [maxSize] -func NewMempool(ctx *snow.Context, registerer prometheus.Registerer, maxSize int, verify func(tx *Tx) error) (*Mempool, error) { +func NewMempool(ctx *snow.Context, registerer prometheus.Registerer, maxSize int, verify func(tx *atomic.Tx) error) (*Mempool, error) { bloom, err := gossip.NewBloomFilter(registerer, "atomic_mempool_bloom_filter", txGossipBloomMinTargetElements, txGossipBloomTargetFalsePositiveRate, txGossipBloomResetFalsePositiveRate) if err != nil { return nil, fmt.Errorf("failed to initialize bloom filter: %w", err) @@ -89,13 +90,13 @@ func NewMempool(ctx *snow.Context, registerer prometheus.Registerer, maxSize int return &Mempool{ ctx: ctx, - issuedTxs: make(map[ids.ID]*Tx), - discardedTxs: &cache.LRU[ids.ID, *Tx]{Size: discardedTxsCacheSize}, - currentTxs: make(map[ids.ID]*Tx), + issuedTxs: make(map[ids.ID]*atomic.Tx), + discardedTxs: &cache.LRU[ids.ID, *atomic.Tx]{Size: discardedTxsCacheSize}, + currentTxs: make(map[ids.ID]*atomic.Tx), Pending: make(chan struct{}, 1), txHeap: newTxHeap(maxSize), maxSize: maxSize, - utxoSpenders: make(map[ids.ID]*Tx), + utxoSpenders: make(map[ids.ID]*atomic.Tx), bloom: bloom, metrics: newMempoolMetrics(), verify: verify, @@ -117,7 +118,7 @@ func (m *Mempool) length() int { // atomicTxGasPrice is the [gasPrice] paid by a transaction to burn a given // amount of [AVAXAssetID] given the value of [gasUsed]. -func (m *Mempool) atomicTxGasPrice(tx *Tx) (uint64, error) { +func (m *Mempool) atomicTxGasPrice(tx *atomic.Tx) (uint64, error) { gasUsed, err := tx.GasUsed(true) if err != nil { return 0, err @@ -158,7 +159,7 @@ func (m *Mempool) Add(tx *GossipAtomicTx) error { // AddTx attempts to add [tx] to the mempool and returns an error if // it could not be added to the mempool. -func (m *Mempool) AddTx(tx *Tx) error { +func (m *Mempool) AddTx(tx *atomic.Tx) error { m.lock.Lock() defer m.lock.Unlock() @@ -180,7 +181,7 @@ func (m *Mempool) AddTx(tx *Tx) error { return err } -func (m *Mempool) AddLocalTx(tx *Tx) error { +func (m *Mempool) AddLocalTx(tx *atomic.Tx) error { m.lock.Lock() defer m.lock.Unlock() @@ -192,8 +193,8 @@ func (m *Mempool) AddLocalTx(tx *Tx) error { return err } -// forceAddTx forcibly adds a *Tx to the mempool and bypasses all verification. -func (m *Mempool) ForceAddTx(tx *Tx) error { +// forceAddTx forcibly adds a *atomic.Tx to the mempool and bypasses all verification. +func (m *Mempool) ForceAddTx(tx *atomic.Tx) error { m.lock.Lock() defer m.lock.Unlock() @@ -208,13 +209,13 @@ func (m *Mempool) ForceAddTx(tx *Tx) error { // checkConflictTx checks for any transactions in the mempool that spend the same input UTXOs as [tx]. // If any conflicts are present, it returns the highest gas price of any conflicting transaction, the // txID of the corresponding tx and the full list of transactions that conflict with [tx]. -func (m *Mempool) checkConflictTx(tx *Tx) (uint64, ids.ID, []*Tx, error) { +func (m *Mempool) checkConflictTx(tx *atomic.Tx) (uint64, ids.ID, []*atomic.Tx, error) { utxoSet := tx.InputUTXOs() var ( - highestGasPrice uint64 = 0 - conflictingTxs []*Tx = make([]*Tx, 0) - highestGasPriceConflictTxID ids.ID = ids.ID{} + highestGasPrice uint64 = 0 + conflictingTxs []*atomic.Tx = make([]*atomic.Tx, 0) + highestGasPriceConflictTxID ids.ID = ids.ID{} ) for utxoID := range utxoSet { // Get current gas price of the existing tx in the mempool @@ -239,7 +240,7 @@ func (m *Mempool) checkConflictTx(tx *Tx) (uint64, ids.ID, []*Tx, error) { // addTx adds [tx] to the mempool. Assumes [m.lock] is held. // If [force], skips conflict checks within the mempool. -func (m *Mempool) addTx(tx *Tx, force bool) error { +func (m *Mempool) addTx(tx *atomic.Tx, force bool) error { txID := tx.ID() // If [txID] has already been issued or is in the currentTxs map // there's no need to add it. @@ -371,7 +372,7 @@ func (m *Mempool) GetFilter() ([]byte, []byte) { } // NextTx returns a transaction to be issued from the mempool. -func (m *Mempool) NextTx() (*Tx, bool) { +func (m *Mempool) NextTx() (*atomic.Tx, bool) { m.lock.Lock() defer m.lock.Unlock() @@ -391,7 +392,7 @@ func (m *Mempool) NextTx() (*Tx, bool) { // GetPendingTx returns the transaction [txID] and true if it is // currently in the [txHeap] waiting to be issued into a block. // Returns nil, false otherwise. -func (m *Mempool) GetPendingTx(txID ids.ID) (*Tx, bool) { +func (m *Mempool) GetPendingTx(txID ids.ID) (*atomic.Tx, bool) { m.lock.RLock() defer m.lock.RUnlock() @@ -401,7 +402,7 @@ func (m *Mempool) GetPendingTx(txID ids.ID) (*Tx, bool) { // GetTx returns the transaction [txID] if it was issued // by this node and returns whether it was dropped and whether // it exists. -func (m *Mempool) GetTx(txID ids.ID) (*Tx, bool, bool) { +func (m *Mempool) GetTx(txID ids.ID) (*atomic.Tx, bool, bool) { m.lock.RLock() defer m.lock.RUnlock() @@ -484,7 +485,7 @@ func (m *Mempool) CancelCurrentTxs() { // cancelTx removes [tx] from current transactions and moves it back into the // tx heap. // assumes the lock is held. -func (m *Mempool) cancelTx(tx *Tx) { +func (m *Mempool) cancelTx(tx *atomic.Tx) { // Add tx to heap sorted by gasPrice gasPrice, err := m.atomicTxGasPrice(tx) if err == nil { @@ -526,7 +527,7 @@ func (m *Mempool) DiscardCurrentTxs() { // discardCurrentTx discards [tx] from the set of current transactions. // Assumes the lock is held. -func (m *Mempool) discardCurrentTx(tx *Tx) { +func (m *Mempool) discardCurrentTx(tx *atomic.Tx) { m.removeSpenders(tx) m.discardedTxs.Put(tx.ID(), tx) delete(m.currentTxs, tx.ID()) @@ -540,7 +541,7 @@ func (m *Mempool) discardCurrentTx(tx *Tx) { // removeTx must be called for all conflicts before overwriting the utxoSpenders // map. // Assumes lock is held. -func (m *Mempool) removeTx(tx *Tx, discard bool) { +func (m *Mempool) removeTx(tx *atomic.Tx, discard bool) { txID := tx.ID() // Remove from [currentTxs], [txHeap], and [issuedTxs]. @@ -565,7 +566,7 @@ func (m *Mempool) removeTx(tx *Tx, discard bool) { // removeSpenders deletes the entries for all input UTXOs of [tx] from the // [utxoSpenders] map. // Assumes the lock is held. -func (m *Mempool) removeSpenders(tx *Tx) { +func (m *Mempool) removeSpenders(tx *atomic.Tx) { for utxoID := range tx.InputUTXOs() { delete(m.utxoSpenders, utxoID) } @@ -573,7 +574,7 @@ func (m *Mempool) removeSpenders(tx *Tx) { // RemoveTx removes [txID] from the mempool completely. // Evicts [tx] from the discarded cache if present. -func (m *Mempool) RemoveTx(tx *Tx) { +func (m *Mempool) RemoveTx(tx *atomic.Tx) { m.lock.Lock() defer m.lock.Unlock() diff --git a/plugin/evm/mempool_atomic_gossiping_test.go b/plugin/evm/mempool_atomic_gossiping_test.go index b44f2097b4..3e22fef486 100644 --- a/plugin/evm/mempool_atomic_gossiping_test.go +++ b/plugin/evm/mempool_atomic_gossiping_test.go @@ -11,6 +11,7 @@ import ( "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/utils/crypto/secp256k1" "github.com/ava-labs/avalanchego/vms/components/chain" + "github.com/ava-labs/coreth/plugin/evm/atomic" "github.com/stretchr/testify/assert" ) @@ -32,7 +33,7 @@ func TestMempoolAddLocallyCreateAtomicTx(t *testing.T) { // generate a valid and conflicting tx var ( - tx, conflictingTx *Tx + tx, conflictingTx *atomic.Tx ) if name == "import" { importTxs := createImportTxOptions(t, vm, sharedMemory) diff --git a/plugin/evm/mempool_test.go b/plugin/evm/mempool_test.go index a56c43bbee..8129edc577 100644 --- a/plugin/evm/mempool_test.go +++ b/plugin/evm/mempool_test.go @@ -8,6 +8,7 @@ import ( "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/snow" + "github.com/ava-labs/coreth/plugin/evm/atomic" "github.com/prometheus/client_golang/prometheus" "github.com/stretchr/testify/require" ) @@ -20,7 +21,7 @@ func TestMempoolAddTx(t *testing.T) { txs := make([]*GossipAtomicTx, 0) for i := 0; i < 3_000; i++ { tx := &GossipAtomicTx{ - Tx: &Tx{ + Tx: &atomic.Tx{ UnsignedAtomicTx: &TestUnsignedTx{ IDV: ids.GenerateTestID(), }, @@ -43,7 +44,7 @@ func TestMempoolAdd(t *testing.T) { require.NoError(err) tx := &GossipAtomicTx{ - Tx: &Tx{ + Tx: &atomic.Tx{ UnsignedAtomicTx: &TestUnsignedTx{ IDV: ids.GenerateTestID(), }, diff --git a/plugin/evm/service.go b/plugin/evm/service.go index 7f57be5520..59fddb1ea4 100644 --- a/plugin/evm/service.go +++ b/plugin/evm/service.go @@ -12,11 +12,12 @@ import ( "github.com/ava-labs/avalanchego/api" "github.com/ava-labs/avalanchego/ids" - "github.com/ava-labs/avalanchego/utils/crypto/secp256k1" "github.com/ava-labs/avalanchego/utils/formatting" "github.com/ava-labs/avalanchego/utils/json" "github.com/ava-labs/avalanchego/utils/set" "github.com/ava-labs/coreth/params" + "github.com/ava-labs/coreth/plugin/evm/atomic" + "github.com/ava-labs/coreth/plugin/evm/client" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/log" @@ -91,24 +92,11 @@ func (service *AvaxAPI) Version(r *http.Request, _ *struct{}, reply *VersionRepl return nil } -// ExportKeyArgs are arguments for ExportKey -type ExportKeyArgs struct { - api.UserPass - Address string `json:"address"` -} - -// ExportKeyReply is the response for ExportKey -type ExportKeyReply struct { - // The decrypted PrivateKey for the Address provided in the arguments - PrivateKey *secp256k1.PrivateKey `json:"privateKey"` - PrivateKeyHex string `json:"privateKeyHex"` -} - // ExportKey returns a private key from the provided user -func (service *AvaxAPI) ExportKey(r *http.Request, args *ExportKeyArgs, reply *ExportKeyReply) error { +func (service *AvaxAPI) ExportKey(r *http.Request, args *client.ExportKeyArgs, reply *client.ExportKeyReply) error { log.Info("EVM: ExportKey called") - address, err := ParseEthAddress(args.Address) + address, err := atomic.ParseEthAddress(args.Address) if err != nil { return fmt.Errorf("couldn't parse %s to address: %s", args.Address, err) } @@ -131,21 +119,15 @@ func (service *AvaxAPI) ExportKey(r *http.Request, args *ExportKeyArgs, reply *E return nil } -// ImportKeyArgs are arguments for ImportKey -type ImportKeyArgs struct { - api.UserPass - PrivateKey *secp256k1.PrivateKey `json:"privateKey"` -} - // ImportKey adds a private key to the provided user -func (service *AvaxAPI) ImportKey(r *http.Request, args *ImportKeyArgs, reply *api.JSONAddress) error { +func (service *AvaxAPI) ImportKey(r *http.Request, args *client.ImportKeyArgs, reply *api.JSONAddress) error { log.Info("EVM: ImportKey called", "username", args.Username) if args.PrivateKey == nil { return errMissingPrivateKey } - reply.Address = GetEthAddress(args.PrivateKey).Hex() + reply.Address = atomic.GetEthAddress(args.PrivateKey).Hex() service.vm.ctx.Lock.Lock() defer service.vm.ctx.Lock.Unlock() @@ -163,28 +145,14 @@ func (service *AvaxAPI) ImportKey(r *http.Request, args *ImportKeyArgs, reply *a return nil } -// ImportArgs are arguments for passing into Import requests -type ImportArgs struct { - api.UserPass - - // Fee that should be used when creating the tx - BaseFee *hexutil.Big `json:"baseFee"` - - // Chain the funds are coming from - SourceChain string `json:"sourceChain"` - - // The address that will receive the imported funds - To common.Address `json:"to"` -} - // ImportAVAX is a deprecated name for Import. -func (service *AvaxAPI) ImportAVAX(_ *http.Request, args *ImportArgs, response *api.JSONTxID) error { +func (service *AvaxAPI) ImportAVAX(_ *http.Request, args *client.ImportArgs, response *api.JSONTxID) error { return service.Import(nil, args, response) } // Import issues a transaction to import AVAX from the X-chain. The AVAX // must have already been exported from the X-Chain. -func (service *AvaxAPI) Import(_ *http.Request, args *ImportArgs, response *api.JSONTxID) error { +func (service *AvaxAPI) Import(_ *http.Request, args *client.ImportArgs, response *api.JSONTxID) error { log.Info("EVM: ImportAVAX called") chainID, err := service.vm.ctx.BCLookup.Lookup(args.SourceChain) @@ -232,44 +200,18 @@ func (service *AvaxAPI) Import(_ *http.Request, args *ImportArgs, response *api. return nil } -// ExportAVAXArgs are the arguments to ExportAVAX -type ExportAVAXArgs struct { - api.UserPass - - // Fee that should be used when creating the tx - BaseFee *hexutil.Big `json:"baseFee"` - - // Amount of asset to send - Amount json.Uint64 `json:"amount"` - - // Chain the funds are going to. Optional. Used if To address does not - // include the chainID. - TargetChain string `json:"targetChain"` - - // ID of the address that will receive the AVAX. This address may include - // the chainID, which is used to determine what the destination chain is. - To string `json:"to"` -} - // ExportAVAX exports AVAX from the C-Chain to the X-Chain // It must be imported on the X-Chain to complete the transfer -func (service *AvaxAPI) ExportAVAX(_ *http.Request, args *ExportAVAXArgs, response *api.JSONTxID) error { - return service.Export(nil, &ExportArgs{ +func (service *AvaxAPI) ExportAVAX(_ *http.Request, args *client.ExportAVAXArgs, response *api.JSONTxID) error { + return service.Export(nil, &client.ExportArgs{ ExportAVAXArgs: *args, AssetID: service.vm.ctx.AVAXAssetID.String(), }, response) } -// ExportArgs are the arguments to Export -type ExportArgs struct { - ExportAVAXArgs - // AssetID of the tokens - AssetID string `json:"assetID"` -} - // Export exports an asset from the C-Chain to the X-Chain // It must be imported on the X-Chain to complete the transfer -func (service *AvaxAPI) Export(_ *http.Request, args *ExportArgs, response *api.JSONTxID) error { +func (service *AvaxAPI) Export(_ *http.Request, args *client.ExportArgs, response *api.JSONTxID) error { log.Info("EVM: Export called") assetID, err := service.parseAssetID(args.AssetID) @@ -401,7 +343,7 @@ func (service *AvaxAPI) GetUTXOs(r *http.Request, args *api.GetUTXOsArgs, reply reply.UTXOs = make([]string, len(utxos)) for i, utxo := range utxos { - b, err := service.vm.codec.Marshal(codecVersion, utxo) + b, err := atomic.Codec.Marshal(atomic.CodecVersion, utxo) if err != nil { return fmt.Errorf("problem marshalling UTXO: %w", err) } @@ -432,11 +374,11 @@ func (service *AvaxAPI) IssueTx(r *http.Request, args *api.FormattedTx, response return fmt.Errorf("problem decoding transaction: %w", err) } - tx := &Tx{} - if _, err := service.vm.codec.Unmarshal(txBytes, tx); err != nil { + tx := &atomic.Tx{} + if _, err := atomic.Codec.Unmarshal(txBytes, tx); err != nil { return fmt.Errorf("problem parsing transaction: %w", err) } - if err := tx.Sign(service.vm.codec, nil); err != nil { + if err := tx.Sign(atomic.Codec, nil); err != nil { return fmt.Errorf("problem initializing transaction: %w", err) } @@ -452,14 +394,8 @@ func (service *AvaxAPI) IssueTx(r *http.Request, args *api.FormattedTx, response return nil } -// GetAtomicTxStatusReply defines the GetAtomicTxStatus replies returned from the API -type GetAtomicTxStatusReply struct { - Status Status `json:"status"` - BlockHeight *json.Uint64 `json:"blockHeight,omitempty"` -} - // GetAtomicTxStatus returns the status of the specified transaction -func (service *AvaxAPI) GetAtomicTxStatus(r *http.Request, args *api.JSONTxID, reply *GetAtomicTxStatusReply) error { +func (service *AvaxAPI) GetAtomicTxStatus(r *http.Request, args *api.JSONTxID, reply *client.GetAtomicTxStatusReply) error { log.Info("EVM: GetAtomicTxStatus called", "txID", args.TxID) if args.TxID == ids.Empty { @@ -472,13 +408,13 @@ func (service *AvaxAPI) GetAtomicTxStatus(r *http.Request, args *api.JSONTxID, r _, status, height, _ := service.vm.getAtomicTx(args.TxID) reply.Status = status - if status == Accepted { + if status == atomic.Accepted { // Since chain state updates run asynchronously with VM block acceptance, // avoid returning [Accepted] until the chain state reaches the block // containing the atomic tx. lastAccepted := service.vm.blockChain.LastAcceptedBlock() if height > lastAccepted.NumberU64() { - reply.Status = Processing + reply.Status = atomic.Processing return nil } @@ -509,7 +445,7 @@ func (service *AvaxAPI) GetAtomicTx(r *http.Request, args *api.GetTxArgs, reply return err } - if status == Unknown { + if status == atomic.Unknown { return fmt.Errorf("could not find tx %s", args.TxID) } @@ -519,7 +455,7 @@ func (service *AvaxAPI) GetAtomicTx(r *http.Request, args *api.GetTxArgs, reply } reply.Tx = txBytes reply.Encoding = args.Encoding - if status == Accepted { + if status == atomic.Accepted { // Since chain state updates run asynchronously with VM block acceptance, // avoid returning [Accepted] until the chain state reaches the block // containing the atomic tx. diff --git a/plugin/evm/syncervm_test.go b/plugin/evm/syncervm_test.go index bba4663153..bd7f993cb5 100644 --- a/plugin/evm/syncervm_test.go +++ b/plugin/evm/syncervm_test.go @@ -15,7 +15,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/ava-labs/avalanchego/chains/atomic" + avalancheatomic "github.com/ava-labs/avalanchego/chains/atomic" "github.com/ava-labs/avalanchego/database" "github.com/ava-labs/avalanchego/database/prefixdb" "github.com/ava-labs/avalanchego/ids" @@ -35,6 +35,7 @@ import ( "github.com/ava-labs/coreth/core/types" "github.com/ava-labs/coreth/metrics" "github.com/ava-labs/coreth/params" + "github.com/ava-labs/coreth/plugin/evm/atomic" "github.com/ava-labs/coreth/predicate" statesyncclient "github.com/ava-labs/coreth/sync/client" "github.com/ava-labs/coreth/sync/statesync" @@ -291,7 +292,7 @@ func createSyncServerAndClientVMs(t *testing.T, test syncTest, numBlocks int) *s require.NoError(serverVM.Shutdown(context.Background())) }) var ( - importTx, exportTx *Tx + importTx, exportTx *atomic.Tx err error ) generateAndAcceptBlocks(t, serverVM, numBlocks, func(i int, gen *core.BlockGen) { @@ -336,8 +337,8 @@ func createSyncServerAndClientVMs(t *testing.T, test syncTest, numBlocks int) *s require.NoError(serverVM.db.Commit()) serverSharedMemories := newSharedMemories(serverAtomicMemory, serverVM.ctx.ChainID, serverVM.ctx.XChainID) - serverSharedMemories.assertOpsApplied(t, importTx.mustAtomicOps()) - serverSharedMemories.assertOpsApplied(t, exportTx.mustAtomicOps()) + serverSharedMemories.assertOpsApplied(t, mustAtomicOps(importTx)) + serverSharedMemories.assertOpsApplied(t, mustAtomicOps(exportTx)) // make some accounts trieDB := triedb.NewDatabase(serverVM.chaindb, nil) @@ -406,7 +407,7 @@ func createSyncServerAndClientVMs(t *testing.T, test syncTest, numBlocks int) *s return &syncVMSetup{ serverVM: serverVM, serverAppSender: serverAppSender, - includedAtomicTxs: []*Tx{ + includedAtomicTxs: []*atomic.Tx{ importTx, exportTx, }, @@ -425,13 +426,13 @@ type syncVMSetup struct { serverVM *VM serverAppSender *enginetest.Sender - includedAtomicTxs []*Tx + includedAtomicTxs []*atomic.Tx fundedAccounts map[*keystore.Key]*types.StateAccount syncerVM *VM syncerDB database.Database syncerEngineChan <-chan commonEng.Message - syncerAtomicMemory *atomic.Memory + syncerAtomicMemory *avalancheatomic.Memory shutdownOnceSyncerVM *shutdownOnceVM } @@ -560,7 +561,7 @@ func testSyncerVM(t *testing.T, vmSetup *syncVMSetup, test syncTest) { syncerSharedMemories := newSharedMemories(syncerAtomicMemory, syncerVM.ctx.ChainID, syncerVM.ctx.XChainID) for _, tx := range includedAtomicTxs { - syncerSharedMemories.assertOpsApplied(t, tx.mustAtomicOps()) + syncerSharedMemories.assertOpsApplied(t, mustAtomicOps(tx)) } // Generate blocks after we have entered normal consensus as well diff --git a/plugin/evm/test_tx.go b/plugin/evm/test_tx.go index c057c874ad..e001cb4dda 100644 --- a/plugin/evm/test_tx.go +++ b/plugin/evm/test_tx.go @@ -9,21 +9,21 @@ import ( "github.com/ava-labs/avalanchego/utils" - "github.com/ava-labs/avalanchego/chains/atomic" + avalancheatomic "github.com/ava-labs/avalanchego/chains/atomic" "github.com/ava-labs/avalanchego/codec" "github.com/ava-labs/avalanchego/codec/linearcodec" "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/snow" "github.com/ava-labs/avalanchego/utils/set" "github.com/ava-labs/avalanchego/utils/wrappers" - "github.com/ava-labs/coreth/core/state" "github.com/ava-labs/coreth/params" + "github.com/ava-labs/coreth/plugin/evm/atomic" ) type TestUnsignedTx struct { - GasUsedV uint64 `serialize:"true"` - AcceptRequestsBlockchainIDV ids.ID `serialize:"true"` - AcceptRequestsV *atomic.Requests `serialize:"true"` + GasUsedV uint64 `serialize:"true"` + AcceptRequestsBlockchainIDV ids.ID `serialize:"true"` + AcceptRequestsV *avalancheatomic.Requests `serialize:"true"` VerifyV error IDV ids.ID `serialize:"true" json:"id"` BurnedV uint64 `serialize:"true"` @@ -34,7 +34,7 @@ type TestUnsignedTx struct { EVMStateTransferV error } -var _ UnsignedAtomicTx = &TestUnsignedTx{} +var _ atomic.UnsignedAtomicTx = &TestUnsignedTx{} // GasUsed implements the UnsignedAtomicTx interface func (t *TestUnsignedTx) GasUsed(fixedFee bool) (uint64, error) { return t.GasUsedV, nil } @@ -43,7 +43,7 @@ func (t *TestUnsignedTx) GasUsed(fixedFee bool) (uint64, error) { return t.GasUs func (t *TestUnsignedTx) Verify(ctx *snow.Context, rules params.Rules) error { return t.VerifyV } // AtomicOps implements the UnsignedAtomicTx interface -func (t *TestUnsignedTx) AtomicOps() (ids.ID, *atomic.Requests, error) { +func (t *TestUnsignedTx) AtomicOps() (ids.ID, *avalancheatomic.Requests, error) { return t.AcceptRequestsBlockchainIDV, t.AcceptRequestsV, nil } @@ -66,12 +66,12 @@ func (t *TestUnsignedTx) SignedBytes() []byte { return t.SignedBytesV } func (t *TestUnsignedTx) InputUTXOs() set.Set[ids.ID] { return t.InputUTXOsV } // SemanticVerify implements the UnsignedAtomicTx interface -func (t *TestUnsignedTx) SemanticVerify(vm *VM, stx *Tx, parent *Block, baseFee *big.Int, rules params.Rules) error { +func (t *TestUnsignedTx) SemanticVerify(backend *atomic.Backend, stx *atomic.Tx, parent atomic.AtomicBlockContext, baseFee *big.Int) error { return t.SemanticVerifyV } // EVMStateTransfer implements the UnsignedAtomicTx interface -func (t *TestUnsignedTx) EVMStateTransfer(ctx *snow.Context, state *state.StateDB) error { +func (t *TestUnsignedTx) EVMStateTransfer(ctx *snow.Context, state atomic.StateDB) error { return t.EVMStateTransferV } @@ -82,9 +82,9 @@ func testTxCodec() codec.Manager { errs := wrappers.Errs{} errs.Add( c.RegisterType(&TestUnsignedTx{}), - c.RegisterType(&atomic.Element{}), - c.RegisterType(&atomic.Requests{}), - codec.RegisterCodec(codecVersion, c), + c.RegisterType(&avalancheatomic.Element{}), + c.RegisterType(&avalancheatomic.Requests{}), + codec.RegisterCodec(atomic.CodecVersion, c), ) if errs.Errored() { @@ -95,12 +95,12 @@ func testTxCodec() codec.Manager { var blockChainID = ids.GenerateTestID() -func testDataImportTx() *Tx { - return &Tx{ +func testDataImportTx() *atomic.Tx { + return &atomic.Tx{ UnsignedAtomicTx: &TestUnsignedTx{ IDV: ids.GenerateTestID(), AcceptRequestsBlockchainIDV: blockChainID, - AcceptRequestsV: &atomic.Requests{ + AcceptRequestsV: &avalancheatomic.Requests{ RemoveRequests: [][]byte{ utils.RandomBytes(32), utils.RandomBytes(32), @@ -110,13 +110,13 @@ func testDataImportTx() *Tx { } } -func testDataExportTx() *Tx { - return &Tx{ +func testDataExportTx() *atomic.Tx { + return &atomic.Tx{ UnsignedAtomicTx: &TestUnsignedTx{ IDV: ids.GenerateTestID(), AcceptRequestsBlockchainIDV: blockChainID, - AcceptRequestsV: &atomic.Requests{ - PutRequests: []*atomic.Element{ + AcceptRequestsV: &avalancheatomic.Requests{ + PutRequests: []*avalancheatomic.Element{ { Key: utils.RandomBytes(16), Value: utils.RandomBytes(24), @@ -131,7 +131,7 @@ func testDataExportTx() *Tx { } } -func newTestTx() *Tx { +func newTestTx() *atomic.Tx { txType := rand.Intn(2) switch txType { case 0: @@ -143,8 +143,8 @@ func newTestTx() *Tx { } } -func newTestTxs(numTxs int) []*Tx { - txs := make([]*Tx, 0, numTxs) +func newTestTxs(numTxs int) []*atomic.Tx { + txs := make([]*atomic.Tx, 0, numTxs) for i := 0; i < numTxs; i++ { txs = append(txs, newTestTx()) } diff --git a/plugin/evm/tx_gossip_test.go b/plugin/evm/tx_gossip_test.go index af544e9415..fb1d7388d9 100644 --- a/plugin/evm/tx_gossip_test.go +++ b/plugin/evm/tx_gossip_test.go @@ -11,7 +11,7 @@ import ( "testing" "time" - "github.com/ava-labs/avalanchego/chains/atomic" + avalancheatomic "github.com/ava-labs/avalanchego/chains/atomic" "github.com/ava-labs/avalanchego/database/memdb" "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/network/p2p" @@ -35,6 +35,7 @@ import ( "github.com/ava-labs/coreth/core/types" "github.com/ava-labs/coreth/params" + "github.com/ava-labs/coreth/plugin/evm/atomic" "github.com/ava-labs/coreth/utils" ) @@ -47,7 +48,7 @@ func TestEthTxGossip(t *testing.T) { pk, err := secp256k1.NewPrivateKey() require.NoError(err) - address := GetEthAddress(pk) + address := atomic.GetEthAddress(pk) genesis := newPrefundedGenesis(100_000_000_000_000_000, address) genesisBytes, err := genesis.MarshalJSON() require.NoError(err) @@ -170,12 +171,12 @@ func TestAtomicTxGossip(t *testing.T) { snowCtx.AVAXAssetID = ids.GenerateTestID() validatorState := utils.NewTestValidatorState() snowCtx.ValidatorState = validatorState - memory := atomic.NewMemory(memdb.New()) + memory := avalancheatomic.NewMemory(memdb.New()) snowCtx.SharedMemory = memory.NewSharedMemory(snowCtx.ChainID) pk, err := secp256k1.NewPrivateKey() require.NoError(err) - address := GetEthAddress(pk) + address := atomic.GetEthAddress(pk) genesis := newPrefundedGenesis(100_000_000_000_000_000, address) genesisBytes, err := genesis.MarshalJSON() require.NoError(err) @@ -267,7 +268,7 @@ func TestAtomicTxGossip(t *testing.T) { pk.PublicKey().Address(), ) require.NoError(err) - tx, err := vm.newImportTxWithUTXOs(vm.ctx.XChainID, address, initialBaseFee, secp256k1fx.NewKeychain(pk), []*avax.UTXO{utxo}) + tx, err := atomic.NewImportTx(vm.ctx, vm.currentRules(), vm.clock, vm.ctx.XChainID, address, initialBaseFee, secp256k1fx.NewKeychain(pk), []*avax.UTXO{utxo}) require.NoError(err) require.NoError(vm.mempool.AddLocalTx(tx)) @@ -314,7 +315,7 @@ func TestEthTxPushGossipOutbound(t *testing.T) { pk, err := secp256k1.NewPrivateKey() require.NoError(err) - address := GetEthAddress(pk) + address := atomic.GetEthAddress(pk) genesis := newPrefundedGenesis(100_000_000_000_000_000, address) genesisBytes, err := genesis.MarshalJSON() require.NoError(err) @@ -374,7 +375,7 @@ func TestEthTxPushGossipInbound(t *testing.T) { pk, err := secp256k1.NewPrivateKey() require.NoError(err) - address := GetEthAddress(pk) + address := atomic.GetEthAddress(pk) genesis := newPrefundedGenesis(100_000_000_000_000_000, address) genesisBytes, err := genesis.MarshalJSON() require.NoError(err) @@ -428,12 +429,12 @@ func TestAtomicTxPushGossipOutbound(t *testing.T) { snowCtx.AVAXAssetID = ids.GenerateTestID() validatorState := utils.NewTestValidatorState() snowCtx.ValidatorState = validatorState - memory := atomic.NewMemory(memdb.New()) + memory := avalancheatomic.NewMemory(memdb.New()) snowCtx.SharedMemory = memory.NewSharedMemory(snowCtx.ChainID) pk, err := secp256k1.NewPrivateKey() require.NoError(err) - address := GetEthAddress(pk) + address := atomic.GetEthAddress(pk) genesis := newPrefundedGenesis(100_000_000_000_000_000, address) genesisBytes, err := genesis.MarshalJSON() require.NoError(err) @@ -475,7 +476,7 @@ func TestAtomicTxPushGossipOutbound(t *testing.T) { pk.PublicKey().Address(), ) require.NoError(err) - tx, err := vm.newImportTxWithUTXOs(vm.ctx.XChainID, address, initialBaseFee, secp256k1fx.NewKeychain(pk), []*avax.UTXO{utxo}) + tx, err := atomic.NewImportTx(vm.ctx, vm.currentRules(), vm.clock, vm.ctx.XChainID, address, initialBaseFee, secp256k1fx.NewKeychain(pk), []*avax.UTXO{utxo}) require.NoError(err) require.NoError(vm.mempool.AddLocalTx(tx)) vm.atomicTxPushGossiper.Add(&GossipAtomicTx{tx}) @@ -501,12 +502,12 @@ func TestAtomicTxPushGossipInbound(t *testing.T) { snowCtx.AVAXAssetID = ids.GenerateTestID() validatorState := utils.NewTestValidatorState() snowCtx.ValidatorState = validatorState - memory := atomic.NewMemory(memdb.New()) + memory := avalancheatomic.NewMemory(memdb.New()) snowCtx.SharedMemory = memory.NewSharedMemory(snowCtx.ChainID) pk, err := secp256k1.NewPrivateKey() require.NoError(err) - address := GetEthAddress(pk) + address := atomic.GetEthAddress(pk) genesis := newPrefundedGenesis(100_000_000_000_000_000, address) genesisBytes, err := genesis.MarshalJSON() require.NoError(err) @@ -546,7 +547,7 @@ func TestAtomicTxPushGossipInbound(t *testing.T) { pk.PublicKey().Address(), ) require.NoError(err) - tx, err := vm.newImportTxWithUTXOs(vm.ctx.XChainID, address, initialBaseFee, secp256k1fx.NewKeychain(pk), []*avax.UTXO{utxo}) + tx, err := atomic.NewImportTx(vm.ctx, vm.currentRules(), vm.clock, vm.ctx.XChainID, address, initialBaseFee, secp256k1fx.NewKeychain(pk), []*avax.UTXO{utxo}) require.NoError(err) require.NoError(vm.mempool.AddLocalTx(tx)) diff --git a/plugin/evm/tx_heap.go b/plugin/evm/tx_heap.go index d44020039e..c6562fd9b0 100644 --- a/plugin/evm/tx_heap.go +++ b/plugin/evm/tx_heap.go @@ -7,6 +7,7 @@ import ( "container/heap" "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/coreth/plugin/evm/atomic" ) // txEntry is used to track the [gasPrice] transactions pay to be included in @@ -14,7 +15,7 @@ import ( type txEntry struct { id ids.ID gasPrice uint64 - tx *Tx + tx *atomic.Tx index int } @@ -91,7 +92,7 @@ func newTxHeap(maxSize int) *txHeap { } } -func (th *txHeap) Push(tx *Tx, gasPrice uint64) { +func (th *txHeap) Push(tx *atomic.Tx, gasPrice uint64) { txID := tx.ID() oldLen := th.Len() heap.Push(th.maxHeap, &txEntry{ @@ -109,28 +110,28 @@ func (th *txHeap) Push(tx *Tx, gasPrice uint64) { } // Assumes there is non-zero items in [txHeap] -func (th *txHeap) PeekMax() (*Tx, uint64) { +func (th *txHeap) PeekMax() (*atomic.Tx, uint64) { txEntry := th.maxHeap.items[0] return txEntry.tx, txEntry.gasPrice } // Assumes there is non-zero items in [txHeap] -func (th *txHeap) PeekMin() (*Tx, uint64) { +func (th *txHeap) PeekMin() (*atomic.Tx, uint64) { txEntry := th.minHeap.items[0] return txEntry.tx, txEntry.gasPrice } // Assumes there is non-zero items in [txHeap] -func (th *txHeap) PopMax() *Tx { +func (th *txHeap) PopMax() *atomic.Tx { return th.Remove(th.maxHeap.items[0].id) } // Assumes there is non-zero items in [txHeap] -func (th *txHeap) PopMin() *Tx { +func (th *txHeap) PopMin() *atomic.Tx { return th.Remove(th.minHeap.items[0].id) } -func (th *txHeap) Remove(id ids.ID) *Tx { +func (th *txHeap) Remove(id ids.ID) *atomic.Tx { maxEntry, ok := th.maxHeap.Get(id) if !ok { return nil @@ -150,7 +151,7 @@ func (th *txHeap) Len() int { return th.maxHeap.Len() } -func (th *txHeap) Get(id ids.ID) (*Tx, bool) { +func (th *txHeap) Get(id ids.ID) (*atomic.Tx, bool) { txEntry, ok := th.maxHeap.Get(id) if !ok { return nil, false diff --git a/plugin/evm/tx_heap_test.go b/plugin/evm/tx_heap_test.go index 206b87bbdb..a054b7362e 100644 --- a/plugin/evm/tx_heap_test.go +++ b/plugin/evm/tx_heap_test.go @@ -6,27 +6,28 @@ package evm import ( "testing" + "github.com/ava-labs/coreth/plugin/evm/atomic" "github.com/stretchr/testify/assert" ) func TestTxHeap(t *testing.T) { var ( - tx0 = &Tx{ - UnsignedAtomicTx: &UnsignedImportTx{ + tx0 = &atomic.Tx{ + UnsignedAtomicTx: &atomic.UnsignedImportTx{ NetworkID: 0, }, } tx0Bytes = []byte{0} - tx1 = &Tx{ - UnsignedAtomicTx: &UnsignedImportTx{ + tx1 = &atomic.Tx{ + UnsignedAtomicTx: &atomic.UnsignedImportTx{ NetworkID: 1, }, } tx1Bytes = []byte{1} - tx2 = &Tx{ - UnsignedAtomicTx: &UnsignedImportTx{ + tx2 = &atomic.Tx{ + UnsignedAtomicTx: &atomic.UnsignedImportTx{ NetworkID: 2, }, } diff --git a/plugin/evm/tx_test.go b/plugin/evm/tx_test.go index d99ee70309..a710a3c9e1 100644 --- a/plugin/evm/tx_test.go +++ b/plugin/evm/tx_test.go @@ -14,8 +14,9 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ava-labs/coreth/params" + "github.com/ava-labs/coreth/plugin/evm/atomic" - "github.com/ava-labs/avalanchego/chains/atomic" + avalancheatomic "github.com/ava-labs/avalanchego/chains/atomic" "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/snow" ) @@ -30,7 +31,7 @@ func TestCalculateDynamicFee(t *testing.T) { var tests []test = []test{ { gas: 1, - baseFee: new(big.Int).Set(x2cRate.ToBig()), + baseFee: new(big.Int).Set(atomic.X2CRate.ToBig()), expectedValue: 1, }, { @@ -41,7 +42,7 @@ func TestCalculateDynamicFee(t *testing.T) { } for _, test := range tests { - cost, err := CalculateDynamicFee(test.gas, test.baseFee) + cost, err := atomic.CalculateDynamicFee(test.gas, test.baseFee) if test.expectedErr == nil { if err != nil { t.Fatalf("Unexpectedly failed to calculate dynamic fee: %s", err) @@ -59,7 +60,7 @@ func TestCalculateDynamicFee(t *testing.T) { type atomicTxVerifyTest struct { ctx *snow.Context - generate func(t *testing.T) UnsignedAtomicTx + generate func(t *testing.T) atomic.UnsignedAtomicTx rules params.Rules expectedErr string } @@ -78,7 +79,7 @@ func executeTxVerifyTest(t *testing.T, test atomicTxVerifyTest) { type atomicTxTest struct { // setup returns the atomic transaction for the test - setup func(t *testing.T, vm *VM, sharedMemory *atomic.Memory) *Tx + setup func(t *testing.T, vm *VM, sharedMemory *avalancheatomic.Memory) *atomic.Tx // define a string that should be contained in the error message if the tx fails verification // at some point. If the strings are empty, then the tx should pass verification at the // respective step. @@ -115,7 +116,15 @@ func executeTxTest(t *testing.T, test atomicTxTest) { } lastAcceptedBlock := vm.LastAcceptedBlockInternal().(*Block) - if err := tx.UnsignedAtomicTx.SemanticVerify(vm, tx, lastAcceptedBlock, baseFee, rules); len(test.semanticVerifyErr) == 0 && err != nil { + backend := &atomic.Backend{ + Ctx: vm.ctx, + Fx: &vm.fx, + Rules: rules, + Bootstrapped: vm.bootstrapped.Get(), + BlockFetcher: vm, + SecpCache: &vm.secpCache, + } + if err := tx.UnsignedAtomicTx.SemanticVerify(backend, tx, lastAcceptedBlock, baseFee); len(test.semanticVerifyErr) == 0 && err != nil { t.Fatalf("SemanticVerify failed unexpectedly due to: %s", err) } else if len(test.semanticVerifyErr) != 0 { if err == nil { @@ -191,18 +200,18 @@ func executeTxTest(t *testing.T, test atomicTxTest) { func TestEVMOutputCompare(t *testing.T) { type test struct { name string - a, b EVMOutput + a, b atomic.EVMOutput expected int } tests := []test{ { name: "address less", - a: EVMOutput{ + a: atomic.EVMOutput{ Address: common.BytesToAddress([]byte{0x01}), AssetID: ids.ID{1}, }, - b: EVMOutput{ + b: atomic.EVMOutput{ Address: common.BytesToAddress([]byte{0x02}), AssetID: ids.ID{0}, }, @@ -210,11 +219,11 @@ func TestEVMOutputCompare(t *testing.T) { }, { name: "address greater; assetIDs equal", - a: EVMOutput{ + a: atomic.EVMOutput{ Address: common.BytesToAddress([]byte{0x02}), AssetID: ids.ID{}, }, - b: EVMOutput{ + b: atomic.EVMOutput{ Address: common.BytesToAddress([]byte{0x01}), AssetID: ids.ID{}, }, @@ -222,11 +231,11 @@ func TestEVMOutputCompare(t *testing.T) { }, { name: "addresses equal; assetID less", - a: EVMOutput{ + a: atomic.EVMOutput{ Address: common.BytesToAddress([]byte{0x01}), AssetID: ids.ID{0}, }, - b: EVMOutput{ + b: atomic.EVMOutput{ Address: common.BytesToAddress([]byte{0x01}), AssetID: ids.ID{1}, }, @@ -234,8 +243,8 @@ func TestEVMOutputCompare(t *testing.T) { }, { name: "equal", - a: EVMOutput{}, - b: EVMOutput{}, + a: atomic.EVMOutput{}, + b: atomic.EVMOutput{}, expected: 0, }, } @@ -253,18 +262,18 @@ func TestEVMOutputCompare(t *testing.T) { func TestEVMInputCompare(t *testing.T) { type test struct { name string - a, b EVMInput + a, b atomic.EVMInput expected int } tests := []test{ { name: "address less", - a: EVMInput{ + a: atomic.EVMInput{ Address: common.BytesToAddress([]byte{0x01}), AssetID: ids.ID{1}, }, - b: EVMInput{ + b: atomic.EVMInput{ Address: common.BytesToAddress([]byte{0x02}), AssetID: ids.ID{0}, }, @@ -272,11 +281,11 @@ func TestEVMInputCompare(t *testing.T) { }, { name: "address greater; assetIDs equal", - a: EVMInput{ + a: atomic.EVMInput{ Address: common.BytesToAddress([]byte{0x02}), AssetID: ids.ID{}, }, - b: EVMInput{ + b: atomic.EVMInput{ Address: common.BytesToAddress([]byte{0x01}), AssetID: ids.ID{}, }, @@ -284,11 +293,11 @@ func TestEVMInputCompare(t *testing.T) { }, { name: "addresses equal; assetID less", - a: EVMInput{ + a: atomic.EVMInput{ Address: common.BytesToAddress([]byte{0x01}), AssetID: ids.ID{0}, }, - b: EVMInput{ + b: atomic.EVMInput{ Address: common.BytesToAddress([]byte{0x01}), AssetID: ids.ID{1}, }, @@ -296,8 +305,8 @@ func TestEVMInputCompare(t *testing.T) { }, { name: "equal", - a: EVMInput{}, - b: EVMInput{}, + a: atomic.EVMInput{}, + b: atomic.EVMInput{}, expected: 0, }, } diff --git a/plugin/evm/user.go b/plugin/evm/user.go index 330d03b01d..627a7af1d1 100644 --- a/plugin/evm/user.go +++ b/plugin/evm/user.go @@ -9,6 +9,7 @@ import ( "github.com/ava-labs/avalanchego/database/encdb" "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/utils/crypto/secp256k1" + "github.com/ava-labs/coreth/plugin/evm/atomic" "github.com/ethereum/go-ethereum/common" ) @@ -47,7 +48,7 @@ func (u *user) getAddresses() ([]common.Address, error) { return nil, err } addresses := []common.Address{} - if _, err := Codec.Unmarshal(bytes, &addresses); err != nil { + if _, err := atomic.Codec.Unmarshal(bytes, &addresses); err != nil { return nil, err } return addresses, nil @@ -69,7 +70,7 @@ func (u *user) putAddress(privKey *secp256k1.PrivateKey) error { return errKeyNil } - address := GetEthAddress(privKey) // address the privKey controls + address := atomic.GetEthAddress(privKey) // address the privKey controls controlsAddress, err := u.controlsAddress(address) if err != nil { return err @@ -93,7 +94,7 @@ func (u *user) putAddress(privKey *secp256k1.PrivateKey) error { } } addresses = append(addresses, address) - bytes, err := Codec.Marshal(codecVersion, addresses) + bytes, err := atomic.Codec.Marshal(atomic.CodecVersion, addresses) if err != nil { return err } diff --git a/plugin/evm/vm.go b/plugin/evm/vm.go index 50606b0af7..4adab936bc 100644 --- a/plugin/evm/vm.go +++ b/plugin/evm/vm.go @@ -23,7 +23,6 @@ import ( "github.com/ava-labs/avalanchego/network/p2p/gossip" "github.com/ava-labs/avalanchego/upgrade" avalanchegoConstants "github.com/ava-labs/avalanchego/utils/constants" - "github.com/holiman/uint256" "github.com/prometheus/client_golang/prometheus" "github.com/ava-labs/coreth/consensus/dummy" @@ -41,6 +40,7 @@ import ( "github.com/ava-labs/coreth/node" "github.com/ava-labs/coreth/params" "github.com/ava-labs/coreth/peer" + "github.com/ava-labs/coreth/plugin/evm/atomic" "github.com/ava-labs/coreth/plugin/evm/message" "github.com/ava-labs/coreth/triedb" "github.com/ava-labs/coreth/triedb/hashdb" @@ -84,7 +84,6 @@ import ( "github.com/ava-labs/avalanchego/utils/crypto/secp256k1" "github.com/ava-labs/avalanchego/utils/formatting/address" "github.com/ava-labs/avalanchego/utils/logging" - "github.com/ava-labs/avalanchego/utils/math" "github.com/ava-labs/avalanchego/utils/perms" "github.com/ava-labs/avalanchego/utils/profiler" "github.com/ava-labs/avalanchego/utils/set" @@ -108,27 +107,12 @@ var ( _ secp256k1fx.VM = &VM{} ) -const ( - x2cRateUint64 uint64 = 1_000_000_000 - x2cRateMinus1Uint64 uint64 = x2cRateUint64 - 1 -) - -var ( - // x2cRate is the conversion rate between the smallest denomination on the X-Chain - // 1 nAVAX and the smallest denomination on the C-Chain 1 wei. Where 1 nAVAX = 1 gWei. - // This is only required for AVAX because the denomination of 1 AVAX is 9 decimal - // places on the X and P chains, but is 18 decimal places within the EVM. - x2cRate = uint256.NewInt(x2cRateUint64) - x2cRateMinus1 = uint256.NewInt(x2cRateMinus1Uint64) -) - const ( // Max time from current time allowed for blocks, before they're considered future blocks // and fail verification maxFutureBlockTime = 10 * time.Second maxUTXOsToFetch = 1024 defaultMempoolSize = 4096 - codecVersion = uint16(0) secpCacheSize = 1024 decidedCacheSize = 10 * units.MiB @@ -184,30 +168,15 @@ var ( errEmptyBlock = errors.New("empty block") errUnsupportedFXs = errors.New("unsupported feature extensions") errInvalidBlock = errors.New("invalid block") - errInvalidAddr = errors.New("invalid hex address") errInsufficientAtomicTxFee = errors.New("atomic tx fee too low for atomic mempool") - errAssetIDMismatch = errors.New("asset IDs in the input don't match the utxo") - errNoImportInputs = errors.New("tx has no imported inputs") - errInputsNotSortedUnique = errors.New("inputs not sorted and unique") - errPublicKeySignatureMismatch = errors.New("signature doesn't match public key") - errWrongChainID = errors.New("tx has wrong chain ID") - errInsufficientFunds = errors.New("insufficient funds") - errNoExportOutputs = errors.New("tx has no export outputs") - errOutputsNotSorted = errors.New("tx outputs not sorted") - errOutputsNotSortedUnique = errors.New("outputs not sorted and unique") - errOverflowExport = errors.New("overflow when computing export amount + txFee") errInvalidNonce = errors.New("invalid nonce") - errConflictingAtomicInputs = errors.New("invalid block due to conflicting atomic inputs") errUnclesUnsupported = errors.New("uncles unsupported") errRejectedParent = errors.New("rejected parent") - errInsufficientFundsForFee = errors.New("insufficient AVAX funds to pay transaction fee") - errNoEVMOutputs = errors.New("tx has no EVM outputs") errNilBaseFeeApricotPhase3 = errors.New("nil base fee is invalid after apricotPhase3") errNilExtDataGasUsedApricotPhase4 = errors.New("nil extDataGasUsed is invalid after apricotPhase4") errNilBlockGasCostApricotPhase4 = errors.New("nil blockGasCost is invalid after apricotPhase4") errConflictingAtomicTx = errors.New("conflicting atomic tx present") errTooManyAtomicTx = errors.New("too many atomic tx") - errMissingAtomicTxs = errors.New("cannot build a block with non-empty extra data and zero atomic transactions") errInvalidHeaderPredicateResults = errors.New("invalid header predicate results") ) @@ -295,7 +264,6 @@ type VM struct { builder *blockBuilder baseCodec codec.Registry - codec codec.Manager clock mockable.Clock mempool *Mempool @@ -574,8 +542,6 @@ func (vm *VM) Initialize( return fmt.Errorf("failed to verify chain config: %w", err) } - vm.codec = Codec - // TODO: read size from settings vm.mempool, err = NewMempool(chainCtx, vm.sdkMetrics, defaultMempoolSize, vm.verifyTxAtTip) if err != nil { @@ -641,7 +607,7 @@ func (vm *VM) Initialize( } // initialize atomic repository - vm.atomicTxRepository, err = NewAtomicTxRepository(vm.db, vm.codec, lastAcceptedHeight) + vm.atomicTxRepository, err = NewAtomicTxRepository(vm.db, atomic.Codec, lastAcceptedHeight) if err != nil { return fmt.Errorf("failed to create atomic repository: %w", err) } @@ -875,7 +841,7 @@ func (vm *VM) preBatchOnFinalizeAndAssemble(header *types.Header, state *state.S continue } - atomicTxBytes, err := vm.codec.Marshal(codecVersion, tx) + atomicTxBytes, err := atomic.Codec.Marshal(atomic.CodecVersion, tx) if err != nil { // Discard the transaction from the mempool and error if the transaction // cannot be marshalled. This should never happen. @@ -904,7 +870,7 @@ func (vm *VM) preBatchOnFinalizeAndAssemble(header *types.Header, state *state.S // assumes that we are in at least Apricot Phase 5. func (vm *VM) postBatchOnFinalizeAndAssemble(header *types.Header, state *state.StateDB, txs []*types.Transaction) ([]byte, *big.Int, *big.Int, error) { var ( - batchAtomicTxs []*Tx + batchAtomicTxs []*atomic.Tx batchAtomicUTXOs set.Set[ids.ID] batchContribution *big.Int = new(big.Int).Set(common.Big0) batchGasUsed *big.Int = new(big.Int).Set(common.Big0) @@ -979,7 +945,7 @@ func (vm *VM) postBatchOnFinalizeAndAssemble(header *types.Header, state *state. // If there is a non-zero number of transactions, marshal them and return the byte slice // for the block's extra data along with the contribution and gas used. if len(batchAtomicTxs) > 0 { - atomicTxBytes, err := vm.codec.Marshal(codecVersion, batchAtomicTxs) + atomicTxBytes, err := atomic.Codec.Marshal(atomic.CodecVersion, batchAtomicTxs) if err != nil { // If we fail to marshal the batch of atomic transactions for any reason, // discard the entire set of current transactions. @@ -1017,7 +983,7 @@ func (vm *VM) onExtraStateChange(block *types.Block, state *state.StateDB) (*big rules = vm.chainConfig.Rules(header.Number, header.Time) ) - txs, err := ExtractAtomicTxs(block.ExtData(), rules.IsApricotPhase5, vm.codec) + txs, err := atomic.ExtractAtomicTxs(block.ExtData(), rules.IsApricotPhase5, atomic.Codec) if err != nil { return nil, nil, err } @@ -1602,61 +1568,22 @@ func (vm *VM) CreateStaticHandlers(context.Context) (map[string]http.Handler, er ****************************************************************************** */ -// conflicts returns an error if [inputs] conflicts with any of the atomic inputs contained in [ancestor] -// or any of its ancestor blocks going back to the last accepted block in its ancestry. If [ancestor] is -// accepted, then nil will be returned immediately. -// If the ancestry of [ancestor] cannot be fetched, then [errRejectedParent] may be returned. -func (vm *VM) conflicts(inputs set.Set[ids.ID], ancestor *Block) error { - lastAcceptedBlock := vm.LastAcceptedBlock() - lastAcceptedHeight := lastAcceptedBlock.Height() - for ancestor.Height() > lastAcceptedHeight { - // If any of the atomic transactions in the ancestor conflict with [inputs] - // return an error. - for _, atomicTx := range ancestor.atomicTxs { - if inputs.Overlaps(atomicTx.InputUTXOs()) { - return errConflictingAtomicInputs - } - } - - // Move up the chain. - nextAncestorID := ancestor.Parent() - // If the ancestor is unknown, then the parent failed - // verification when it was called. - // If the ancestor is rejected, then this block shouldn't be - // inserted into the canonical chain because the parent is - // will be missing. - // If the ancestor is processing, then the block may have - // been verified. - nextAncestorIntf, err := vm.GetBlockInternal(context.TODO(), nextAncestorID) - if err != nil { - return errRejectedParent - } - nextAncestor, ok := nextAncestorIntf.(*Block) - if !ok { - return fmt.Errorf("ancestor block %s had unexpected type %T", nextAncestor.ID(), nextAncestorIntf) - } - ancestor = nextAncestor - } - - return nil -} - // getAtomicTx returns the requested transaction, status, and height. // If the status is Unknown, then the returned transaction will be nil. -func (vm *VM) getAtomicTx(txID ids.ID) (*Tx, Status, uint64, error) { +func (vm *VM) getAtomicTx(txID ids.ID) (*atomic.Tx, atomic.Status, uint64, error) { if tx, height, err := vm.atomicTxRepository.GetByTxID(txID); err == nil { - return tx, Accepted, height, nil + return tx, atomic.Accepted, height, nil } else if err != database.ErrNotFound { - return nil, Unknown, 0, err + return nil, atomic.Unknown, 0, err } tx, dropped, found := vm.mempool.GetTx(txID) switch { case found && dropped: - return tx, Dropped, 0, nil + return tx, atomic.Dropped, 0, nil case found: - return tx, Processing, 0, nil + return tx, atomic.Processing, 0, nil default: - return nil, Unknown, 0, nil + return nil, atomic.Unknown, 0, nil } } @@ -1687,7 +1614,7 @@ func (vm *VM) ParseAddress(addrStr string) (ids.ID, ids.ShortID, error) { } // verifyTxAtTip verifies that [tx] is valid to be issued on top of the currently preferred block -func (vm *VM) verifyTxAtTip(tx *Tx) error { +func (vm *VM) verifyTxAtTip(tx *atomic.Tx) error { if txByteLen := len(tx.SignedBytes()); txByteLen > targetAtomicTxsSize { return fmt.Errorf("tx size (%d) exceeds total atomic txs size target (%d)", txByteLen, targetAtomicTxsSize) } @@ -1728,7 +1655,7 @@ func (vm *VM) verifyTxAtTip(tx *Tx) error { // Note: verifyTx may modify [state]. If [state] needs to be properly maintained, the caller is responsible // for reverting to the correct snapshot after calling this function. If this function is called with a // throwaway state, then this is not necessary. -func (vm *VM) verifyTx(tx *Tx, parentHash common.Hash, baseFee *big.Int, state *state.StateDB, rules params.Rules) error { +func (vm *VM) verifyTx(tx *atomic.Tx, parentHash common.Hash, baseFee *big.Int, state *state.StateDB, rules params.Rules) error { parentIntf, err := vm.GetBlockInternal(context.TODO(), ids.ID(parentHash)) if err != nil { return fmt.Errorf("failed to get parent block: %w", err) @@ -1737,7 +1664,15 @@ func (vm *VM) verifyTx(tx *Tx, parentHash common.Hash, baseFee *big.Int, state * if !ok { return fmt.Errorf("parent block %s had unexpected type %T", parentIntf.ID(), parentIntf) } - if err := tx.UnsignedAtomicTx.SemanticVerify(vm, tx, parent, baseFee, rules); err != nil { + atomicBackend := &atomic.Backend{ + Ctx: vm.ctx, + Fx: &vm.fx, + Rules: rules, + Bootstrapped: vm.bootstrapped.Get(), + BlockFetcher: vm, + SecpCache: &vm.secpCache, + } + if err := tx.UnsignedAtomicTx.SemanticVerify(atomicBackend, tx, parent, baseFee); err != nil { return err } return tx.UnsignedAtomicTx.EVMStateTransfer(vm.ctx, state) @@ -1745,7 +1680,7 @@ func (vm *VM) verifyTx(tx *Tx, parentHash common.Hash, baseFee *big.Int, state * // verifyTxs verifies that [txs] are valid to be issued into a block with parent block [parentHash] // using [rules] as the current rule set. -func (vm *VM) verifyTxs(txs []*Tx, parentHash common.Hash, baseFee *big.Int, height uint64, rules params.Rules) error { +func (vm *VM) verifyTxs(txs []*atomic.Tx, parentHash common.Hash, baseFee *big.Int, height uint64, rules params.Rules) error { // Ensure that the parent was verified and inserted correctly. if !vm.blockChain.HasBlock(parentHash, height-1) { return errRejectedParent @@ -1768,14 +1703,22 @@ func (vm *VM) verifyTxs(txs []*Tx, parentHash common.Hash, baseFee *big.Int, hei // Ensure each tx in [txs] doesn't conflict with any other atomic tx in // a processing ancestor block. inputs := set.Set[ids.ID]{} + atomicBackend := &atomic.Backend{ + Ctx: vm.ctx, + Fx: &vm.fx, + Rules: rules, + Bootstrapped: vm.bootstrapped.Get(), + BlockFetcher: vm, + SecpCache: &vm.secpCache, + } for _, atomicTx := range txs { utx := atomicTx.UnsignedAtomicTx - if err := utx.SemanticVerify(vm, atomicTx, ancestor, baseFee, rules); err != nil { + if err := utx.SemanticVerify(atomicBackend, atomicTx, ancestor, baseFee); err != nil { return fmt.Errorf("invalid block due to failed semanatic verify: %w at height %d", err, height) } txInputs := utx.InputUTXOs() if inputs.Overlaps(txInputs) { - return errConflictingAtomicInputs + return atomic.ErrConflictingAtomicInputs } inputs.Union(txInputs) } @@ -1797,7 +1740,7 @@ func (vm *VM) GetAtomicUTXOs( return avax.GetAtomicUTXOs( vm.ctx.SharedMemory, - vm.codec, + atomic.Codec, chainID, addrs, startAddr, @@ -1806,176 +1749,6 @@ func (vm *VM) GetAtomicUTXOs( ) } -// GetSpendableFunds returns a list of EVMInputs and keys (in corresponding -// order) to total [amount] of [assetID] owned by [keys]. -// Note: we return [][]*secp256k1.PrivateKey even though each input -// corresponds to a single key, so that the signers can be passed in to -// [tx.Sign] which supports multiple keys on a single input. -func (vm *VM) GetSpendableFunds( - keys []*secp256k1.PrivateKey, - assetID ids.ID, - amount uint64, -) ([]EVMInput, [][]*secp256k1.PrivateKey, error) { - // Note: current state uses the state of the preferred block. - state, err := vm.blockChain.State() - if err != nil { - return nil, nil, err - } - inputs := []EVMInput{} - signers := [][]*secp256k1.PrivateKey{} - // Note: we assume that each key in [keys] is unique, so that iterating over - // the keys will not produce duplicated nonces in the returned EVMInput slice. - for _, key := range keys { - if amount == 0 { - break - } - addr := GetEthAddress(key) - var balance uint64 - if assetID == vm.ctx.AVAXAssetID { - // If the asset is AVAX, we divide by the x2cRate to convert back to the correct - // denomination of AVAX that can be exported. - balance = new(uint256.Int).Div(state.GetBalance(addr), x2cRate).Uint64() - } else { - balance = state.GetBalanceMultiCoin(addr, common.Hash(assetID)).Uint64() - } - if balance == 0 { - continue - } - if amount < balance { - balance = amount - } - nonce, err := vm.GetCurrentNonce(addr) - if err != nil { - return nil, nil, err - } - inputs = append(inputs, EVMInput{ - Address: addr, - Amount: balance, - AssetID: assetID, - Nonce: nonce, - }) - signers = append(signers, []*secp256k1.PrivateKey{key}) - amount -= balance - } - - if amount > 0 { - return nil, nil, errInsufficientFunds - } - - return inputs, signers, nil -} - -// GetSpendableAVAXWithFee returns a list of EVMInputs and keys (in corresponding -// order) to total [amount] + [fee] of [AVAX] owned by [keys]. -// This function accounts for the added cost of the additional inputs needed to -// create the transaction and makes sure to skip any keys with a balance that is -// insufficient to cover the additional fee. -// Note: we return [][]*secp256k1.PrivateKey even though each input -// corresponds to a single key, so that the signers can be passed in to -// [tx.Sign] which supports multiple keys on a single input. -func (vm *VM) GetSpendableAVAXWithFee( - keys []*secp256k1.PrivateKey, - amount uint64, - cost uint64, - baseFee *big.Int, -) ([]EVMInput, [][]*secp256k1.PrivateKey, error) { - // Note: current state uses the state of the preferred block. - state, err := vm.blockChain.State() - if err != nil { - return nil, nil, err - } - - initialFee, err := CalculateDynamicFee(cost, baseFee) - if err != nil { - return nil, nil, err - } - - newAmount, err := math.Add64(amount, initialFee) - if err != nil { - return nil, nil, err - } - amount = newAmount - - inputs := []EVMInput{} - signers := [][]*secp256k1.PrivateKey{} - // Note: we assume that each key in [keys] is unique, so that iterating over - // the keys will not produce duplicated nonces in the returned EVMInput slice. - for _, key := range keys { - if amount == 0 { - break - } - - prevFee, err := CalculateDynamicFee(cost, baseFee) - if err != nil { - return nil, nil, err - } - - newCost := cost + EVMInputGas - newFee, err := CalculateDynamicFee(newCost, baseFee) - if err != nil { - return nil, nil, err - } - - additionalFee := newFee - prevFee - - addr := GetEthAddress(key) - // Since the asset is AVAX, we divide by the x2cRate to convert back to - // the correct denomination of AVAX that can be exported. - balance := new(uint256.Int).Div(state.GetBalance(addr), x2cRate).Uint64() - // If the balance for [addr] is insufficient to cover the additional cost - // of adding an input to the transaction, skip adding the input altogether - if balance <= additionalFee { - continue - } - - // Update the cost for the next iteration - cost = newCost - - newAmount, err := math.Add64(amount, additionalFee) - if err != nil { - return nil, nil, err - } - amount = newAmount - - // Use the entire [balance] as an input, but if the required [amount] - // is less than the balance, update the [inputAmount] to spend the - // minimum amount to finish the transaction. - inputAmount := balance - if amount < balance { - inputAmount = amount - } - nonce, err := vm.GetCurrentNonce(addr) - if err != nil { - return nil, nil, err - } - inputs = append(inputs, EVMInput{ - Address: addr, - Amount: inputAmount, - AssetID: vm.ctx.AVAXAssetID, - Nonce: nonce, - }) - signers = append(signers, []*secp256k1.PrivateKey{key}) - amount -= inputAmount - } - - if amount > 0 { - return nil, nil, errInsufficientFunds - } - - return inputs, signers, nil -} - -// GetCurrentNonce returns the nonce associated with the address at the -// preferred block -func (vm *VM) GetCurrentNonce(address common.Address) (uint64, error) { - // Note: current state uses the state of the preferred block. - state, err := vm.blockChain.State() - if err != nil { - return 0, err - } - return state.GetNonce(address), nil -} - // currentRules returns the chain rules for the current block. func (vm *VM) currentRules() params.Rules { header := vm.eth.APIBackend.CurrentHeader() @@ -2109,3 +1882,55 @@ func (vm *VM) stateSyncEnabled(lastAcceptedHeight uint64) bool { // enable state sync by default if the chain is empty. return lastAcceptedHeight == 0 } + +func (vm *VM) newImportTx( + chainID ids.ID, // chain to import from + to common.Address, // Address of recipient + baseFee *big.Int, // fee to use post-AP3 + keys []*secp256k1.PrivateKey, // Keys to import the funds +) (*atomic.Tx, error) { + kc := secp256k1fx.NewKeychain() + for _, key := range keys { + kc.Add(key) + } + + atomicUTXOs, _, _, err := vm.GetAtomicUTXOs(chainID, kc.Addresses(), ids.ShortEmpty, ids.Empty, -1) + if err != nil { + return nil, fmt.Errorf("problem retrieving atomic UTXOs: %w", err) + } + + return atomic.NewImportTx(vm.ctx, vm.currentRules(), vm.clock, chainID, to, baseFee, kc, atomicUTXOs) +} + +// newExportTx returns a new ExportTx +func (vm *VM) newExportTx( + assetID ids.ID, // AssetID of the tokens to export + amount uint64, // Amount of tokens to export + chainID ids.ID, // Chain to send the UTXOs to + to ids.ShortID, // Address of chain recipient + baseFee *big.Int, // fee to use post-AP3 + keys []*secp256k1.PrivateKey, // Pay the fee and provide the tokens +) (*atomic.Tx, error) { + state, err := vm.blockChain.State() + if err != nil { + return nil, err + } + + // Create the transaction + tx, err := atomic.NewExportTx( + vm.ctx, // Context + vm.currentRules(), // VM rules + state, + assetID, // AssetID + amount, // Amount + chainID, // ID of the chain to send the funds to + to, // Address + baseFee, + keys, // Private keys + ) + if err != nil { + return nil, err + } + + return tx, nil +} diff --git a/plugin/evm/vm_test.go b/plugin/evm/vm_test.go index 3d60b23356..83914a038e 100644 --- a/plugin/evm/vm_test.go +++ b/plugin/evm/vm_test.go @@ -24,6 +24,7 @@ import ( "github.com/ava-labs/coreth/constants" "github.com/ava-labs/coreth/eth/filters" "github.com/ava-labs/coreth/metrics" + "github.com/ava-labs/coreth/plugin/evm/atomic" "github.com/ava-labs/coreth/trie" "github.com/ava-labs/coreth/utils" @@ -31,7 +32,7 @@ import ( "github.com/stretchr/testify/require" "github.com/ava-labs/avalanchego/api/keystore" - "github.com/ava-labs/avalanchego/chains/atomic" + avalancheatomic "github.com/ava-labs/avalanchego/chains/atomic" "github.com/ava-labs/avalanchego/database" "github.com/ava-labs/avalanchego/database/memdb" "github.com/ava-labs/avalanchego/database/prefixdb" @@ -160,7 +161,7 @@ func init() { b, _ = cb58.Decode(key) pk, _ := secp256k1.ToPrivateKey(b) testKeys = append(testKeys, pk) - testEthAddrs = append(testEthAddrs, GetEthAddress(pk)) + testEthAddrs = append(testEthAddrs, atomic.GetEthAddress(pk)) testShortIDAddrs = append(testShortIDAddrs, pk.PublicKey().Address()) } } @@ -246,7 +247,7 @@ func setupGenesis( database.Database, []byte, chan commonEng.Message, - *atomic.Memory, + *avalancheatomic.Memory, ) { if len(genesisJSON) == 0 { genesisJSON = genesisJSONLatest @@ -257,7 +258,7 @@ func setupGenesis( baseDB := memdb.New() // initialize the atomic memory - atomicMemory := atomic.NewMemory(prefixdb.New([]byte{0}, baseDB)) + atomicMemory := avalancheatomic.NewMemory(prefixdb.New([]byte{0}, baseDB)) ctx.SharedMemory = atomicMemory.NewSharedMemory(ctx.ChainID) // NB: this lock is intentionally left locked when this function returns. @@ -288,7 +289,7 @@ func GenesisVM(t *testing.T, chan commonEng.Message, *VM, database.Database, - *atomic.Memory, + *avalancheatomic.Memory, *enginetest.Sender, ) { return GenesisVMWithClock(t, finishBootstrapping, genesisJSON, configJSON, upgradeJSON, mockable.Clock{}) @@ -307,7 +308,7 @@ func GenesisVMWithClock( chan commonEng.Message, *VM, database.Database, - *atomic.Memory, + *avalancheatomic.Memory, *enginetest.Sender, ) { vm := &VM{clock: clock} @@ -336,7 +337,7 @@ func GenesisVMWithClock( return issuer, vm, dbManager, m, appSender } -func addUTXO(sharedMemory *atomic.Memory, ctx *snow.Context, txID ids.ID, index uint32, assetID ids.ID, amount uint64, addr ids.ShortID) (*avax.UTXO, error) { +func addUTXO(sharedMemory *avalancheatomic.Memory, ctx *snow.Context, txID ids.ID, index uint32, assetID ids.ID, amount uint64, addr ids.ShortID) (*avax.UTXO, error) { utxo := &avax.UTXO{ UTXOID: avax.UTXOID{ TxID: txID, @@ -351,14 +352,14 @@ func addUTXO(sharedMemory *atomic.Memory, ctx *snow.Context, txID ids.ID, index }, }, } - utxoBytes, err := Codec.Marshal(codecVersion, utxo) + utxoBytes, err := atomic.Codec.Marshal(atomic.CodecVersion, utxo) if err != nil { return nil, err } xChainSharedMemory := sharedMemory.NewSharedMemory(ctx.XChainID) inputID := utxo.InputID() - if err := xChainSharedMemory.Apply(map[ids.ID]*atomic.Requests{ctx.ChainID: {PutRequests: []*atomic.Element{{ + if err := xChainSharedMemory.Apply(map[ids.ID]*avalancheatomic.Requests{ctx.ChainID: {PutRequests: []*avalancheatomic.Element{{ Key: inputID[:], Value: utxoBytes, Traits: [][]byte{ @@ -374,7 +375,7 @@ func addUTXO(sharedMemory *atomic.Memory, ctx *snow.Context, txID ids.ID, index // GenesisVMWithUTXOs creates a GenesisVM and generates UTXOs in the X-Chain Shared Memory containing AVAX based on the [utxos] map // Generates UTXOIDs by using a hash of the address in the [utxos] map such that the UTXOs will be generated deterministically. // If [genesisJSON] is empty, defaults to using [genesisJSONLatest] -func GenesisVMWithUTXOs(t *testing.T, finishBootstrapping bool, genesisJSON string, configJSON string, upgradeJSON string, utxos map[ids.ShortID]uint64) (chan commonEng.Message, *VM, database.Database, *atomic.Memory, *enginetest.Sender) { +func GenesisVMWithUTXOs(t *testing.T, finishBootstrapping bool, genesisJSON string, configJSON string, upgradeJSON string, utxos map[ids.ShortID]uint64) (chan commonEng.Message, *VM, database.Database, *avalancheatomic.Memory, *enginetest.Sender) { issuer, vm, db, sharedMemory, sender := GenesisVM(t, finishBootstrapping, genesisJSON, configJSON, upgradeJSON) for addr, avaxAmount := range utxos { txID, err := ids.ToID(hashing.ComputeHash256(addr.Bytes())) @@ -683,13 +684,13 @@ func TestIssueAtomicTxs(t *testing.T) { // Check that both atomic transactions were indexed as expected. indexedImportTx, status, height, err := vm.getAtomicTx(importTx.ID()) assert.NoError(t, err) - assert.Equal(t, Accepted, status) + assert.Equal(t, atomic.Accepted, status) assert.Equal(t, uint64(1), height, "expected height of indexed import tx to be 1") assert.Equal(t, indexedImportTx.ID(), importTx.ID(), "expected ID of indexed import tx to match original txID") indexedExportTx, status, height, err := vm.getAtomicTx(exportTx.ID()) assert.NoError(t, err) - assert.Equal(t, Accepted, status) + assert.Equal(t, atomic.Accepted, status) assert.Equal(t, uint64(2), height, "expected height of indexed export tx to be 2") assert.Equal(t, indexedExportTx.ID(), exportTx.ID(), "expected ID of indexed import tx to match original txID") } @@ -849,8 +850,8 @@ func testConflictingImportTxs(t *testing.T, genesis string) { } }() - importTxs := make([]*Tx, 0, 3) - conflictTxs := make([]*Tx, 0, 3) + importTxs := make([]*atomic.Tx, 0, 3) + conflictTxs := make([]*atomic.Tx, 0, 3) for i, key := range testKeys { importTx, err := vm.newImportTx(vm.ctx.XChainID, testEthAddrs[i], initialBaseFee, []*secp256k1.PrivateKey{key}) if err != nil { @@ -944,9 +945,9 @@ func testConflictingImportTxs(t *testing.T, genesis string) { var extraData []byte switch { case rules.IsApricotPhase5: - extraData, err = vm.codec.Marshal(codecVersion, []*Tx{conflictTxs[1]}) + extraData, err = atomic.Codec.Marshal(atomic.CodecVersion, []*atomic.Tx{conflictTxs[1]}) default: - extraData, err = vm.codec.Marshal(codecVersion, conflictTxs[1]) + extraData, err = atomic.Codec.Marshal(atomic.CodecVersion, conflictTxs[1]) } if err != nil { t.Fatal(err) @@ -972,15 +973,15 @@ func testConflictingImportTxs(t *testing.T, genesis string) { t.Fatal(err) } - if err := parsedBlock.Verify(context.Background()); !errors.Is(err, errConflictingAtomicInputs) { - t.Fatalf("Expected to fail with err: %s, but found err: %s", errConflictingAtomicInputs, err) + if err := parsedBlock.Verify(context.Background()); !errors.Is(err, atomic.ErrConflictingAtomicInputs) { + t.Fatalf("Expected to fail with err: %s, but found err: %s", atomic.ErrConflictingAtomicInputs, err) } if !rules.IsApricotPhase5 { return } - extraData, err = vm.codec.Marshal(codecVersion, []*Tx{importTxs[2], conflictTxs[2]}) + extraData, err = atomic.Codec.Marshal(atomic.CodecVersion, []*atomic.Tx{importTxs[2], conflictTxs[2]}) if err != nil { t.Fatal(err) } @@ -1008,25 +1009,25 @@ func testConflictingImportTxs(t *testing.T, genesis string) { t.Fatal(err) } - if err := parsedBlock.Verify(context.Background()); !errors.Is(err, errConflictingAtomicInputs) { - t.Fatalf("Expected to fail with err: %s, but found err: %s", errConflictingAtomicInputs, err) + if err := parsedBlock.Verify(context.Background()); !errors.Is(err, atomic.ErrConflictingAtomicInputs) { + t.Fatalf("Expected to fail with err: %s, but found err: %s", atomic.ErrConflictingAtomicInputs, err) } } func TestReissueAtomicTxHigherGasPrice(t *testing.T) { kc := secp256k1fx.NewKeychain(testKeys...) - for name, issueTxs := range map[string]func(t *testing.T, vm *VM, sharedMemory *atomic.Memory) (issued []*Tx, discarded []*Tx){ - "single UTXO override": func(t *testing.T, vm *VM, sharedMemory *atomic.Memory) (issued []*Tx, evicted []*Tx) { + for name, issueTxs := range map[string]func(t *testing.T, vm *VM, sharedMemory *avalancheatomic.Memory) (issued []*atomic.Tx, discarded []*atomic.Tx){ + "single UTXO override": func(t *testing.T, vm *VM, sharedMemory *avalancheatomic.Memory) (issued []*atomic.Tx, evicted []*atomic.Tx) { utxo, err := addUTXO(sharedMemory, vm.ctx, ids.GenerateTestID(), 0, vm.ctx.AVAXAssetID, units.Avax, testShortIDAddrs[0]) if err != nil { t.Fatal(err) } - tx1, err := vm.newImportTxWithUTXOs(vm.ctx.XChainID, testEthAddrs[0], initialBaseFee, kc, []*avax.UTXO{utxo}) + tx1, err := atomic.NewImportTx(vm.ctx, vm.currentRules(), vm.clock, vm.ctx.XChainID, testEthAddrs[0], initialBaseFee, kc, []*avax.UTXO{utxo}) if err != nil { t.Fatal(err) } - tx2, err := vm.newImportTxWithUTXOs(vm.ctx.XChainID, testEthAddrs[0], new(big.Int).Mul(common.Big2, initialBaseFee), kc, []*avax.UTXO{utxo}) + tx2, err := atomic.NewImportTx(vm.ctx, vm.currentRules(), vm.clock, vm.ctx.XChainID, testEthAddrs[0], new(big.Int).Mul(common.Big2, initialBaseFee), kc, []*avax.UTXO{utxo}) if err != nil { t.Fatal(err) } @@ -1038,9 +1039,9 @@ func TestReissueAtomicTxHigherGasPrice(t *testing.T) { t.Fatal(err) } - return []*Tx{tx2}, []*Tx{tx1} + return []*atomic.Tx{tx2}, []*atomic.Tx{tx1} }, - "one of two UTXOs overrides": func(t *testing.T, vm *VM, sharedMemory *atomic.Memory) (issued []*Tx, evicted []*Tx) { + "one of two UTXOs overrides": func(t *testing.T, vm *VM, sharedMemory *avalancheatomic.Memory) (issued []*atomic.Tx, evicted []*atomic.Tx) { utxo1, err := addUTXO(sharedMemory, vm.ctx, ids.GenerateTestID(), 0, vm.ctx.AVAXAssetID, units.Avax, testShortIDAddrs[0]) if err != nil { t.Fatal(err) @@ -1049,11 +1050,11 @@ func TestReissueAtomicTxHigherGasPrice(t *testing.T) { if err != nil { t.Fatal(err) } - tx1, err := vm.newImportTxWithUTXOs(vm.ctx.XChainID, testEthAddrs[0], initialBaseFee, kc, []*avax.UTXO{utxo1, utxo2}) + tx1, err := atomic.NewImportTx(vm.ctx, vm.currentRules(), vm.clock, vm.ctx.XChainID, testEthAddrs[0], initialBaseFee, kc, []*avax.UTXO{utxo1, utxo2}) if err != nil { t.Fatal(err) } - tx2, err := vm.newImportTxWithUTXOs(vm.ctx.XChainID, testEthAddrs[0], new(big.Int).Mul(common.Big2, initialBaseFee), kc, []*avax.UTXO{utxo1}) + tx2, err := atomic.NewImportTx(vm.ctx, vm.currentRules(), vm.clock, vm.ctx.XChainID, testEthAddrs[0], new(big.Int).Mul(common.Big2, initialBaseFee), kc, []*avax.UTXO{utxo1}) if err != nil { t.Fatal(err) } @@ -1065,9 +1066,9 @@ func TestReissueAtomicTxHigherGasPrice(t *testing.T) { t.Fatal(err) } - return []*Tx{tx2}, []*Tx{tx1} + return []*atomic.Tx{tx2}, []*atomic.Tx{tx1} }, - "hola": func(t *testing.T, vm *VM, sharedMemory *atomic.Memory) (issued []*Tx, evicted []*Tx) { + "hola": func(t *testing.T, vm *VM, sharedMemory *avalancheatomic.Memory) (issued []*atomic.Tx, evicted []*atomic.Tx) { utxo1, err := addUTXO(sharedMemory, vm.ctx, ids.GenerateTestID(), 0, vm.ctx.AVAXAssetID, units.Avax, testShortIDAddrs[0]) if err != nil { t.Fatal(err) @@ -1077,17 +1078,17 @@ func TestReissueAtomicTxHigherGasPrice(t *testing.T) { t.Fatal(err) } - importTx1, err := vm.newImportTxWithUTXOs(vm.ctx.XChainID, testEthAddrs[0], initialBaseFee, kc, []*avax.UTXO{utxo1}) + importTx1, err := atomic.NewImportTx(vm.ctx, vm.currentRules(), vm.clock, vm.ctx.XChainID, testEthAddrs[0], initialBaseFee, kc, []*avax.UTXO{utxo1}) if err != nil { t.Fatal(err) } - importTx2, err := vm.newImportTxWithUTXOs(vm.ctx.XChainID, testEthAddrs[0], new(big.Int).Mul(big.NewInt(3), initialBaseFee), kc, []*avax.UTXO{utxo2}) + importTx2, err := atomic.NewImportTx(vm.ctx, vm.currentRules(), vm.clock, vm.ctx.XChainID, testEthAddrs[0], new(big.Int).Mul(big.NewInt(3), initialBaseFee), kc, []*avax.UTXO{utxo2}) if err != nil { t.Fatal(err) } - reissuanceTx1, err := vm.newImportTxWithUTXOs(vm.ctx.XChainID, testEthAddrs[0], new(big.Int).Mul(big.NewInt(2), initialBaseFee), kc, []*avax.UTXO{utxo1, utxo2}) + reissuanceTx1, err := atomic.NewImportTx(vm.ctx, vm.currentRules(), vm.clock, vm.ctx.XChainID, testEthAddrs[0], new(big.Int).Mul(big.NewInt(2), initialBaseFee), kc, []*avax.UTXO{utxo1, utxo2}) if err != nil { t.Fatal(err) } @@ -1107,7 +1108,7 @@ func TestReissueAtomicTxHigherGasPrice(t *testing.T) { assert.True(t, vm.mempool.Has(importTx2.ID())) assert.False(t, vm.mempool.Has(reissuanceTx1.ID())) - reissuanceTx2, err := vm.newImportTxWithUTXOs(vm.ctx.XChainID, testEthAddrs[0], new(big.Int).Mul(big.NewInt(4), initialBaseFee), kc, []*avax.UTXO{utxo1, utxo2}) + reissuanceTx2, err := atomic.NewImportTx(vm.ctx, vm.currentRules(), vm.clock, vm.ctx.XChainID, testEthAddrs[0], new(big.Int).Mul(big.NewInt(4), initialBaseFee), kc, []*avax.UTXO{utxo1, utxo2}) if err != nil { t.Fatal(err) } @@ -1115,7 +1116,7 @@ func TestReissueAtomicTxHigherGasPrice(t *testing.T) { t.Fatal(err) } - return []*Tx{reissuanceTx2}, []*Tx{importTx1, importTx2} + return []*atomic.Tx{reissuanceTx2}, []*atomic.Tx{importTx1, importTx2} }, } { t.Run(name, func(t *testing.T) { @@ -1536,14 +1537,14 @@ func TestBonusBlocksTxs(t *testing.T) { }, }, } - utxoBytes, err := vm.codec.Marshal(codecVersion, utxo) + utxoBytes, err := atomic.Codec.Marshal(atomic.CodecVersion, utxo) if err != nil { t.Fatal(err) } xChainSharedMemory := sharedMemory.NewSharedMemory(vm.ctx.XChainID) inputID := utxo.InputID() - if err := xChainSharedMemory.Apply(map[ids.ID]*atomic.Requests{vm.ctx.ChainID: {PutRequests: []*atomic.Element{{ + if err := xChainSharedMemory.Apply(map[ids.ID]*avalancheatomic.Requests{vm.ctx.ChainID: {PutRequests: []*avalancheatomic.Element{{ Key: inputID[:], Value: utxoBytes, Traits: [][]byte{ @@ -1573,7 +1574,7 @@ func TestBonusBlocksTxs(t *testing.T) { vm.atomicBackend.(*atomicBackend).bonusBlocks = map[uint64]ids.ID{blk.Height(): blk.ID()} // Remove the UTXOs from shared memory, so that non-bonus blocks will fail verification - if err := vm.ctx.SharedMemory.Apply(map[ids.ID]*atomic.Requests{vm.ctx.XChainID: {RemoveRequests: [][]byte{inputID[:]}}}); err != nil { + if err := vm.ctx.SharedMemory.Apply(map[ids.ID]*avalancheatomic.Requests{vm.ctx.XChainID: {RemoveRequests: [][]byte{inputID[:]}}}); err != nil { t.Fatal(err) } @@ -3073,10 +3074,10 @@ func TestBuildInvalidBlockHead(t *testing.T) { addr0 := key0.PublicKey().Address() // Create the transaction - utx := &UnsignedImportTx{ + utx := &atomic.UnsignedImportTx{ NetworkID: vm.ctx.NetworkID, BlockchainID: vm.ctx.ChainID, - Outs: []EVMOutput{{ + Outs: []atomic.EVMOutput{{ Address: common.Address(addr0), Amount: 1 * units.Avax, AssetID: vm.ctx.AVAXAssetID, @@ -3094,8 +3095,8 @@ func TestBuildInvalidBlockHead(t *testing.T) { }, SourceChain: vm.ctx.XChainID, } - tx := &Tx{UnsignedAtomicTx: utx} - if err := tx.Sign(vm.codec, [][]*secp256k1.PrivateKey{{key0}}); err != nil { + tx := &atomic.Tx{UnsignedAtomicTx: utx} + if err := tx.Sign(atomic.Codec, [][]*secp256k1.PrivateKey{{key0}}); err != nil { t.Fatal(err) } @@ -3231,14 +3232,14 @@ func TestBuildApricotPhase4Block(t *testing.T) { }, }, } - utxoBytes, err := vm.codec.Marshal(codecVersion, utxo) + utxoBytes, err := atomic.Codec.Marshal(atomic.CodecVersion, utxo) if err != nil { t.Fatal(err) } xChainSharedMemory := sharedMemory.NewSharedMemory(vm.ctx.XChainID) inputID := utxo.InputID() - if err := xChainSharedMemory.Apply(map[ids.ID]*atomic.Requests{vm.ctx.ChainID: {PutRequests: []*atomic.Element{{ + if err := xChainSharedMemory.Apply(map[ids.ID]*avalancheatomic.Requests{vm.ctx.ChainID: {PutRequests: []*avalancheatomic.Element{{ Key: inputID[:], Value: utxoBytes, Traits: [][]byte{ @@ -3401,14 +3402,14 @@ func TestBuildApricotPhase5Block(t *testing.T) { }, }, } - utxoBytes, err := vm.codec.Marshal(codecVersion, utxo) + utxoBytes, err := atomic.Codec.Marshal(atomic.CodecVersion, utxo) if err != nil { t.Fatal(err) } xChainSharedMemory := sharedMemory.NewSharedMemory(vm.ctx.XChainID) inputID := utxo.InputID() - if err := xChainSharedMemory.Apply(map[ids.ID]*atomic.Requests{vm.ctx.ChainID: {PutRequests: []*atomic.Element{{ + if err := xChainSharedMemory.Apply(map[ids.ID]*avalancheatomic.Requests{vm.ctx.ChainID: {PutRequests: []*avalancheatomic.Element{{ Key: inputID[:], Value: utxoBytes, Traits: [][]byte{ @@ -3678,7 +3679,7 @@ func TestBuildBlockDoesNotExceedAtomicGasLimit(t *testing.T) { utxo, err := addUTXO(sharedMemory, vm.ctx, txID, uint32(i), vm.ctx.AVAXAssetID, importAmount, testShortIDAddrs[0]) assert.NoError(t, err) - importTx, err := vm.newImportTxWithUTXOs(vm.ctx.XChainID, testEthAddrs[0], initialBaseFee, kc, []*avax.UTXO{utxo}) + importTx, err := atomic.NewImportTx(vm.ctx, vm.currentRules(), vm.clock, vm.ctx.XChainID, testEthAddrs[0], initialBaseFee, kc, []*avax.UTXO{utxo}) if err != nil { t.Fatal(err) } @@ -3756,7 +3757,7 @@ func TestExtraStateChangeAtomicGasLimitExceeded(t *testing.T) { validEthBlock := blk1.(*chain.BlockWrapper).Block.(*Block).ethBlock - extraData, err := vm2.codec.Marshal(codecVersion, []*Tx{importTx}) + extraData, err := atomic.Codec.Marshal(atomic.CodecVersion, []*atomic.Tx{importTx}) if err != nil { t.Fatal(err) } From 9c3e28448d56607c5a10ee3e81d2183b9d4b39d7 Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Wed, 11 Dec 2024 18:16:11 +0300 Subject: [PATCH 2/4] bump avago --- go.mod | 2 +- go.sum | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 2a6106661b..ea7f6cc744 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.22.8 require ( github.com/VictoriaMetrics/fastcache v1.12.1 - github.com/ava-labs/avalanchego v1.12.1-0.20241209214115-1dc4192013aa + github.com/ava-labs/avalanchego v1.12.1-0.20241211144846-f3ca1a0f8bb1 github.com/cespare/cp v0.1.0 github.com/crate-crypto/go-ipa v0.0.0-20231025140028-3c0104f4b233 github.com/davecgh/go-spew v1.1.1 diff --git a/go.sum b/go.sum index 9ddf41790c..435d1113c8 100644 --- a/go.sum +++ b/go.sum @@ -56,6 +56,8 @@ github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156/go.mod h1:Cb/ax github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/ava-labs/avalanchego v1.12.1-0.20241209214115-1dc4192013aa h1:8eSy+tegp9Kq2zft54wk0FyWU87utdrVwsj9EBIb/NA= github.com/ava-labs/avalanchego v1.12.1-0.20241209214115-1dc4192013aa/go.mod h1:256D2s2FIKo07uUeY25uDXFuqBo6TeWIJqeEA+Xchwk= +github.com/ava-labs/avalanchego v1.12.1-0.20241211144846-f3ca1a0f8bb1 h1:3Zqc3TxHt6gsdSFD/diW2f2jT2oCx0rppN7yoXxviQg= +github.com/ava-labs/avalanchego v1.12.1-0.20241211144846-f3ca1a0f8bb1/go.mod h1:Wxl57pLTlR/8pkaNtou8HiynG+xdgiF4YnzFuJyqSDg= github.com/aymerick/raymond v2.0.3-0.20180322193309-b565731e1464+incompatible/go.mod h1:osfaiScAUVup+UC9Nfq76eWqDhXlp+4UYaA8uhTBO6g= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= From ddbbab792ca1a938cb6e69ec190e3ce367761d31 Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Wed, 11 Dec 2024 19:16:18 +0300 Subject: [PATCH 3/4] bump versions --- scripts/versions.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/versions.sh b/scripts/versions.sh index 33282d011a..ce7b1cdb05 100644 --- a/scripts/versions.sh +++ b/scripts/versions.sh @@ -6,4 +6,4 @@ set -euo pipefail # Don't export them as they're used in the context of other calls -AVALANCHE_VERSION=${AVALANCHE_VERSION:-'1dc4192013aa'} +AVALANCHE_VERSION=${AVALANCHE_VERSION:-'f3ca1a0f8bb1'} From 58adf02a1cb19e1bc28b79c701d1acd5a663ee86 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Wed, 11 Dec 2024 09:36:16 -0800 Subject: [PATCH 4/4] move config type to client --- plugin/evm/admin.go | 8 +- plugin/evm/client/client.go | 16 ++-- plugin/evm/client/config.go | 175 ++++++++++++++++++++++++++++++++++++ plugin/evm/config.go | 173 ++--------------------------------- plugin/evm/config_test.go | 6 +- 5 files changed, 196 insertions(+), 182 deletions(-) create mode 100644 plugin/evm/client/config.go diff --git a/plugin/evm/admin.go b/plugin/evm/admin.go index e90be473a7..ee0d5a5bec 100644 --- a/plugin/evm/admin.go +++ b/plugin/evm/admin.go @@ -78,11 +78,7 @@ func (p *Admin) SetLogLevel(_ *http.Request, args *client.SetLogLevelArgs, reply return nil } -type ConfigReply struct { - Config *Config `json:"config"` -} - -func (p *Admin) GetVMConfig(_ *http.Request, _ *struct{}, reply *ConfigReply) error { - reply.Config = &p.vm.config +func (p *Admin) GetVMConfig(_ *http.Request, _ *struct{}, reply *client.ConfigReply) error { + reply.Config = (*client.Config)(&p.vm.config) return nil } diff --git a/plugin/evm/client/client.go b/plugin/evm/client/client.go index f92e55e59f..0957f3754f 100644 --- a/plugin/evm/client/client.go +++ b/plugin/evm/client/client.go @@ -40,7 +40,7 @@ type Client interface { MemoryProfile(ctx context.Context, options ...rpc.Option) error LockProfile(ctx context.Context, options ...rpc.Option) error SetLogLevel(ctx context.Context, level slog.Level, options ...rpc.Option) error - // GetVMConfig(ctx context.Context, options ...rpc.Option) (*Config, error) + GetVMConfig(ctx context.Context, options ...rpc.Option) (*Config, error) } // Client implementation for interacting with EVM [chain] @@ -299,9 +299,13 @@ func (c *client) SetLogLevel(ctx context.Context, level slog.Level, options ...r }, &api.EmptyReply{}, options...) } +type ConfigReply struct { + Config *Config `json:"config"` +} + // GetVMConfig returns the current config of the VM -// func (c *client) GetVMConfig(ctx context.Context, options ...rpc.Option) (*Config, error) { -// res := &ConfigReply{} -// err := c.adminRequester.SendRequest(ctx, "admin.getVMConfig", struct{}{}, res, options...) -// return res.Config, err -// } +func (c *client) GetVMConfig(ctx context.Context, options ...rpc.Option) (*Config, error) { + res := &ConfigReply{} + err := c.adminRequester.SendRequest(ctx, "admin.getVMConfig", struct{}{}, res, options...) + return res.Config, err +} diff --git a/plugin/evm/client/config.go b/plugin/evm/client/config.go new file mode 100644 index 0000000000..79f565c434 --- /dev/null +++ b/plugin/evm/client/config.go @@ -0,0 +1,175 @@ +package client + +import ( + "encoding/json" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/spf13/cast" +) + +type Duration struct { + time.Duration +} + +func (d *Duration) UnmarshalJSON(data []byte) (err error) { + var v interface{} + if err := json.Unmarshal(data, &v); err != nil { + return err + } + d.Duration, err = cast.ToDurationE(v) + return err +} + +// String implements the stringer interface. +func (d Duration) String() string { + return d.Duration.String() +} + +// String implements the stringer interface. +func (d Duration) MarshalJSON() ([]byte, error) { + return json.Marshal(d.Duration.String()) +} + +// Config ... +type Config struct { + // Coreth APIs + SnowmanAPIEnabled bool `json:"snowman-api-enabled"` + AdminAPIEnabled bool `json:"admin-api-enabled"` + AdminAPIDir string `json:"admin-api-dir"` + CorethAdminAPIEnabled bool `json:"coreth-admin-api-enabled"` // Deprecated: use AdminAPIEnabled instead + CorethAdminAPIDir string `json:"coreth-admin-api-dir"` // Deprecated: use AdminAPIDir instead + WarpAPIEnabled bool `json:"warp-api-enabled"` + + // EnabledEthAPIs is a list of Ethereum services that should be enabled + // If none is specified, then we use the default list [defaultEnabledAPIs] + EnabledEthAPIs []string `json:"eth-apis"` + + // Continuous Profiler + ContinuousProfilerDir string `json:"continuous-profiler-dir"` // If set to non-empty string creates a continuous profiler + ContinuousProfilerFrequency Duration `json:"continuous-profiler-frequency"` // Frequency to run continuous profiler if enabled + ContinuousProfilerMaxFiles int `json:"continuous-profiler-max-files"` // Maximum number of files to maintain + + // API Gas/Price Caps + RPCGasCap uint64 `json:"rpc-gas-cap"` + RPCTxFeeCap float64 `json:"rpc-tx-fee-cap"` + + // Cache settings + TrieCleanCache int `json:"trie-clean-cache"` // Size of the trie clean cache (MB) + TrieDirtyCache int `json:"trie-dirty-cache"` // Size of the trie dirty cache (MB) + TrieDirtyCommitTarget int `json:"trie-dirty-commit-target"` // Memory limit to target in the dirty cache before performing a commit (MB) + TriePrefetcherParallelism int `json:"trie-prefetcher-parallelism"` // Max concurrent disk reads trie prefetcher should perform at once + SnapshotCache int `json:"snapshot-cache"` // Size of the snapshot disk layer clean cache (MB) + + // Eth Settings + Preimages bool `json:"preimages-enabled"` + SnapshotWait bool `json:"snapshot-wait"` + SnapshotVerify bool `json:"snapshot-verification-enabled"` + + // Pruning Settings + Pruning bool `json:"pruning-enabled"` // If enabled, trie roots are only persisted every 4096 blocks + AcceptorQueueLimit int `json:"accepted-queue-limit"` // Maximum blocks to queue before blocking during acceptance + CommitInterval uint64 `json:"commit-interval"` // Specifies the commit interval at which to persist EVM and atomic tries. + AllowMissingTries bool `json:"allow-missing-tries"` // If enabled, warnings preventing an incomplete trie index are suppressed + PopulateMissingTries *uint64 `json:"populate-missing-tries,omitempty"` // Sets the starting point for re-populating missing tries. Disables re-generation if nil. + PopulateMissingTriesParallelism int `json:"populate-missing-tries-parallelism"` // Number of concurrent readers to use when re-populating missing tries on startup. + PruneWarpDB bool `json:"prune-warp-db-enabled"` // Determines if the warpDB should be cleared on startup + + // Metric Settings + MetricsExpensiveEnabled bool `json:"metrics-expensive-enabled"` // Debug-level metrics that might impact runtime performance + + // API Settings + LocalTxsEnabled bool `json:"local-txs-enabled"` + + TxPoolPriceLimit uint64 `json:"tx-pool-price-limit"` + TxPoolPriceBump uint64 `json:"tx-pool-price-bump"` + TxPoolAccountSlots uint64 `json:"tx-pool-account-slots"` + TxPoolGlobalSlots uint64 `json:"tx-pool-global-slots"` + TxPoolAccountQueue uint64 `json:"tx-pool-account-queue"` + TxPoolGlobalQueue uint64 `json:"tx-pool-global-queue"` + TxPoolLifetime Duration `json:"tx-pool-lifetime"` + + APIMaxDuration Duration `json:"api-max-duration"` + WSCPURefillRate Duration `json:"ws-cpu-refill-rate"` + WSCPUMaxStored Duration `json:"ws-cpu-max-stored"` + MaxBlocksPerRequest int64 `json:"api-max-blocks-per-request"` + AllowUnfinalizedQueries bool `json:"allow-unfinalized-queries"` + AllowUnprotectedTxs bool `json:"allow-unprotected-txs"` + AllowUnprotectedTxHashes []common.Hash `json:"allow-unprotected-tx-hashes"` + + // Keystore Settings + KeystoreDirectory string `json:"keystore-directory"` // both absolute and relative supported + KeystoreExternalSigner string `json:"keystore-external-signer"` + KeystoreInsecureUnlockAllowed bool `json:"keystore-insecure-unlock-allowed"` + + // Gossip Settings + PushGossipPercentStake float64 `json:"push-gossip-percent-stake"` + PushGossipNumValidators int `json:"push-gossip-num-validators"` + PushGossipNumPeers int `json:"push-gossip-num-peers"` + PushRegossipNumValidators int `json:"push-regossip-num-validators"` + PushRegossipNumPeers int `json:"push-regossip-num-peers"` + PushGossipFrequency Duration `json:"push-gossip-frequency"` + PullGossipFrequency Duration `json:"pull-gossip-frequency"` + RegossipFrequency Duration `json:"regossip-frequency"` + TxRegossipFrequency Duration `json:"tx-regossip-frequency"` // Deprecated: use RegossipFrequency instead + + // Log + LogLevel string `json:"log-level"` + LogJSONFormat bool `json:"log-json-format"` + + // Offline Pruning Settings + OfflinePruning bool `json:"offline-pruning-enabled"` + OfflinePruningBloomFilterSize uint64 `json:"offline-pruning-bloom-filter-size"` + OfflinePruningDataDirectory string `json:"offline-pruning-data-directory"` + + // VM2VM network + MaxOutboundActiveRequests int64 `json:"max-outbound-active-requests"` + + // Sync settings + StateSyncEnabled *bool `json:"state-sync-enabled"` // Pointer distinguishes false (no state sync) and not set (state sync only at genesis). + StateSyncSkipResume bool `json:"state-sync-skip-resume"` // Forces state sync to use the highest available summary block + StateSyncServerTrieCache int `json:"state-sync-server-trie-cache"` + StateSyncIDs string `json:"state-sync-ids"` + StateSyncCommitInterval uint64 `json:"state-sync-commit-interval"` + StateSyncMinBlocks uint64 `json:"state-sync-min-blocks"` + StateSyncRequestSize uint16 `json:"state-sync-request-size"` + + // Database Settings + InspectDatabase bool `json:"inspect-database"` // Inspects the database on startup if enabled. + + // SkipUpgradeCheck disables checking that upgrades must take place before the last + // accepted block. Skipping this check is useful when a node operator does not update + // their node before the network upgrade and their node accepts blocks that have + // identical state with the pre-upgrade ruleset. + SkipUpgradeCheck bool `json:"skip-upgrade-check"` + + // AcceptedCacheSize is the depth to keep in the accepted headers cache and the + // accepted logs cache at the accepted tip. + // + // This is particularly useful for improving the performance of eth_getLogs + // on RPC nodes. + AcceptedCacheSize int `json:"accepted-cache-size"` + + // TransactionHistory is the maximum number of blocks from head whose tx indices + // are reserved: + // * 0: means no limit + // * N: means N block limit [HEAD-N+1, HEAD] and delete extra indexes + TransactionHistory uint64 `json:"transaction-history"` + // Deprecated, use 'TransactionHistory' instead. + TxLookupLimit uint64 `json:"tx-lookup-limit"` + + // SkipTxIndexing skips indexing transactions. + // This is useful for validators that don't need to index transactions. + // TxLookupLimit can be still used to control unindexing old transactions. + SkipTxIndexing bool `json:"skip-tx-indexing"` + + // WarpOffChainMessages encodes off-chain messages (unrelated to any on-chain event ie. block or AddressedCall) + // that the node should be willing to sign. + // Note: only supports AddressedCall payloads as defined here: + // https://github.com/ava-labs/avalanchego/tree/7623ffd4be915a5185c9ed5e11fa9be15a6e1f00/vms/platformvm/warp/payload#addressedcall + WarpOffChainMessages []hexutil.Bytes `json:"warp-off-chain-messages"` + + // RPC settings + HttpBodyLimit uint64 `json:"http-body-limit"` +} diff --git a/plugin/evm/config.go b/plugin/evm/config.go index c0fa3b0386..df8d861065 100644 --- a/plugin/evm/config.go +++ b/plugin/evm/config.go @@ -4,15 +4,13 @@ package evm import ( - "encoding/json" "fmt" "time" "github.com/ava-labs/coreth/core/txpool/legacypool" "github.com/ava-labs/coreth/eth" + "github.com/ava-labs/coreth/plugin/evm/client" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/hexutil" - "github.com/spf13/cast" ) const ( @@ -77,151 +75,11 @@ var ( } ) -type Duration struct { - time.Duration -} - -// Config ... -type Config struct { - // Coreth APIs - SnowmanAPIEnabled bool `json:"snowman-api-enabled"` - AdminAPIEnabled bool `json:"admin-api-enabled"` - AdminAPIDir string `json:"admin-api-dir"` - CorethAdminAPIEnabled bool `json:"coreth-admin-api-enabled"` // Deprecated: use AdminAPIEnabled instead - CorethAdminAPIDir string `json:"coreth-admin-api-dir"` // Deprecated: use AdminAPIDir instead - WarpAPIEnabled bool `json:"warp-api-enabled"` - - // EnabledEthAPIs is a list of Ethereum services that should be enabled - // If none is specified, then we use the default list [defaultEnabledAPIs] - EnabledEthAPIs []string `json:"eth-apis"` - - // Continuous Profiler - ContinuousProfilerDir string `json:"continuous-profiler-dir"` // If set to non-empty string creates a continuous profiler - ContinuousProfilerFrequency Duration `json:"continuous-profiler-frequency"` // Frequency to run continuous profiler if enabled - ContinuousProfilerMaxFiles int `json:"continuous-profiler-max-files"` // Maximum number of files to maintain - - // API Gas/Price Caps - RPCGasCap uint64 `json:"rpc-gas-cap"` - RPCTxFeeCap float64 `json:"rpc-tx-fee-cap"` - - // Cache settings - TrieCleanCache int `json:"trie-clean-cache"` // Size of the trie clean cache (MB) - TrieDirtyCache int `json:"trie-dirty-cache"` // Size of the trie dirty cache (MB) - TrieDirtyCommitTarget int `json:"trie-dirty-commit-target"` // Memory limit to target in the dirty cache before performing a commit (MB) - TriePrefetcherParallelism int `json:"trie-prefetcher-parallelism"` // Max concurrent disk reads trie prefetcher should perform at once - SnapshotCache int `json:"snapshot-cache"` // Size of the snapshot disk layer clean cache (MB) - - // Eth Settings - Preimages bool `json:"preimages-enabled"` - SnapshotWait bool `json:"snapshot-wait"` - SnapshotVerify bool `json:"snapshot-verification-enabled"` - - // Pruning Settings - Pruning bool `json:"pruning-enabled"` // If enabled, trie roots are only persisted every 4096 blocks - AcceptorQueueLimit int `json:"accepted-queue-limit"` // Maximum blocks to queue before blocking during acceptance - CommitInterval uint64 `json:"commit-interval"` // Specifies the commit interval at which to persist EVM and atomic tries. - AllowMissingTries bool `json:"allow-missing-tries"` // If enabled, warnings preventing an incomplete trie index are suppressed - PopulateMissingTries *uint64 `json:"populate-missing-tries,omitempty"` // Sets the starting point for re-populating missing tries. Disables re-generation if nil. - PopulateMissingTriesParallelism int `json:"populate-missing-tries-parallelism"` // Number of concurrent readers to use when re-populating missing tries on startup. - PruneWarpDB bool `json:"prune-warp-db-enabled"` // Determines if the warpDB should be cleared on startup - - // Metric Settings - MetricsExpensiveEnabled bool `json:"metrics-expensive-enabled"` // Debug-level metrics that might impact runtime performance - - // API Settings - LocalTxsEnabled bool `json:"local-txs-enabled"` - - TxPoolPriceLimit uint64 `json:"tx-pool-price-limit"` - TxPoolPriceBump uint64 `json:"tx-pool-price-bump"` - TxPoolAccountSlots uint64 `json:"tx-pool-account-slots"` - TxPoolGlobalSlots uint64 `json:"tx-pool-global-slots"` - TxPoolAccountQueue uint64 `json:"tx-pool-account-queue"` - TxPoolGlobalQueue uint64 `json:"tx-pool-global-queue"` - TxPoolLifetime Duration `json:"tx-pool-lifetime"` - - APIMaxDuration Duration `json:"api-max-duration"` - WSCPURefillRate Duration `json:"ws-cpu-refill-rate"` - WSCPUMaxStored Duration `json:"ws-cpu-max-stored"` - MaxBlocksPerRequest int64 `json:"api-max-blocks-per-request"` - AllowUnfinalizedQueries bool `json:"allow-unfinalized-queries"` - AllowUnprotectedTxs bool `json:"allow-unprotected-txs"` - AllowUnprotectedTxHashes []common.Hash `json:"allow-unprotected-tx-hashes"` - - // Keystore Settings - KeystoreDirectory string `json:"keystore-directory"` // both absolute and relative supported - KeystoreExternalSigner string `json:"keystore-external-signer"` - KeystoreInsecureUnlockAllowed bool `json:"keystore-insecure-unlock-allowed"` - - // Gossip Settings - PushGossipPercentStake float64 `json:"push-gossip-percent-stake"` - PushGossipNumValidators int `json:"push-gossip-num-validators"` - PushGossipNumPeers int `json:"push-gossip-num-peers"` - PushRegossipNumValidators int `json:"push-regossip-num-validators"` - PushRegossipNumPeers int `json:"push-regossip-num-peers"` - PushGossipFrequency Duration `json:"push-gossip-frequency"` - PullGossipFrequency Duration `json:"pull-gossip-frequency"` - RegossipFrequency Duration `json:"regossip-frequency"` - TxRegossipFrequency Duration `json:"tx-regossip-frequency"` // Deprecated: use RegossipFrequency instead - - // Log - LogLevel string `json:"log-level"` - LogJSONFormat bool `json:"log-json-format"` - - // Offline Pruning Settings - OfflinePruning bool `json:"offline-pruning-enabled"` - OfflinePruningBloomFilterSize uint64 `json:"offline-pruning-bloom-filter-size"` - OfflinePruningDataDirectory string `json:"offline-pruning-data-directory"` - - // VM2VM network - MaxOutboundActiveRequests int64 `json:"max-outbound-active-requests"` - - // Sync settings - StateSyncEnabled *bool `json:"state-sync-enabled"` // Pointer distinguishes false (no state sync) and not set (state sync only at genesis). - StateSyncSkipResume bool `json:"state-sync-skip-resume"` // Forces state sync to use the highest available summary block - StateSyncServerTrieCache int `json:"state-sync-server-trie-cache"` - StateSyncIDs string `json:"state-sync-ids"` - StateSyncCommitInterval uint64 `json:"state-sync-commit-interval"` - StateSyncMinBlocks uint64 `json:"state-sync-min-blocks"` - StateSyncRequestSize uint16 `json:"state-sync-request-size"` - - // Database Settings - InspectDatabase bool `json:"inspect-database"` // Inspects the database on startup if enabled. - - // SkipUpgradeCheck disables checking that upgrades must take place before the last - // accepted block. Skipping this check is useful when a node operator does not update - // their node before the network upgrade and their node accepts blocks that have - // identical state with the pre-upgrade ruleset. - SkipUpgradeCheck bool `json:"skip-upgrade-check"` - - // AcceptedCacheSize is the depth to keep in the accepted headers cache and the - // accepted logs cache at the accepted tip. - // - // This is particularly useful for improving the performance of eth_getLogs - // on RPC nodes. - AcceptedCacheSize int `json:"accepted-cache-size"` - - // TransactionHistory is the maximum number of blocks from head whose tx indices - // are reserved: - // * 0: means no limit - // * N: means N block limit [HEAD-N+1, HEAD] and delete extra indexes - TransactionHistory uint64 `json:"transaction-history"` - // Deprecated, use 'TransactionHistory' instead. - TxLookupLimit uint64 `json:"tx-lookup-limit"` - - // SkipTxIndexing skips indexing transactions. - // This is useful for validators that don't need to index transactions. - // TxLookupLimit can be still used to control unindexing old transactions. - SkipTxIndexing bool `json:"skip-tx-indexing"` - - // WarpOffChainMessages encodes off-chain messages (unrelated to any on-chain event ie. block or AddressedCall) - // that the node should be willing to sign. - // Note: only supports AddressedCall payloads as defined here: - // https://github.com/ava-labs/avalanchego/tree/7623ffd4be915a5185c9ed5e11fa9be15a6e1f00/vms/platformvm/warp/payload#addressedcall - WarpOffChainMessages []hexutil.Bytes `json:"warp-off-chain-messages"` - - // RPC settings - HttpBodyLimit uint64 `json:"http-body-limit"` -} +// Use the same type for configuration as the client for serialization compatibility +type ( + Config client.Config + Duration = client.Duration +) // EthAPIs returns an array of strings representing the Eth APIs that should be enabled func (c Config) EthAPIs() []string { @@ -282,25 +140,6 @@ func (c *Config) SetDefaults() { c.AcceptedCacheSize = defaultAcceptedCacheSize } -func (d *Duration) UnmarshalJSON(data []byte) (err error) { - var v interface{} - if err := json.Unmarshal(data, &v); err != nil { - return err - } - d.Duration, err = cast.ToDurationE(v) - return err -} - -// String implements the stringer interface. -func (d Duration) String() string { - return d.Duration.String() -} - -// String implements the stringer interface. -func (d Duration) MarshalJSON() ([]byte, error) { - return json.Marshal(d.Duration.String()) -} - // Validate returns an error if this is an invalid config. func (c *Config) Validate() error { if c.PopulateMissingTries != nil && (c.OfflinePruning || c.Pruning) { diff --git a/plugin/evm/config_test.go b/plugin/evm/config_test.go index 9a8384bf5d..7d89b50b1c 100644 --- a/plugin/evm/config_test.go +++ b/plugin/evm/config_test.go @@ -29,19 +29,19 @@ func TestUnmarshalConfig(t *testing.T) { { "string durations parsed", []byte(`{"api-max-duration": "1m", "continuous-profiler-frequency": "2m"}`), - Config{APIMaxDuration: Duration{1 * time.Minute}, ContinuousProfilerFrequency: Duration{2 * time.Minute}}, + Config{APIMaxDuration: Duration{Duration: 1 * time.Minute}, ContinuousProfilerFrequency: Duration{Duration: 2 * time.Minute}}, false, }, { "integer durations parsed", []byte(fmt.Sprintf(`{"api-max-duration": "%v", "continuous-profiler-frequency": "%v"}`, 1*time.Minute, 2*time.Minute)), - Config{APIMaxDuration: Duration{1 * time.Minute}, ContinuousProfilerFrequency: Duration{2 * time.Minute}}, + Config{APIMaxDuration: Duration{Duration: 1 * time.Minute}, ContinuousProfilerFrequency: Duration{Duration: 2 * time.Minute}}, false, }, { "nanosecond durations parsed", []byte(`{"api-max-duration": 5000000000, "continuous-profiler-frequency": 5000000000}`), - Config{APIMaxDuration: Duration{5 * time.Second}, ContinuousProfilerFrequency: Duration{5 * time.Second}}, + Config{APIMaxDuration: Duration{Duration: 5 * time.Second}, ContinuousProfilerFrequency: Duration{Duration: 5 * time.Second}}, false, }, {