From bc26c83012c4d72ed0c62eeb72988685f67f8659 Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Wed, 11 Dec 2024 16:57:04 +0300 Subject: [PATCH 01/27] 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 02/27] 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 03/27] 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 0ecd789f5e552af0aa85e9e048a8652a4e8c9849 Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Sat, 14 Dec 2024 01:18:08 +0300 Subject: [PATCH 04/27] move attomic gossip --- peer/network.go | 22 +-- peer/network_test.go | 71 +++----- plugin/evm/admin.go | 6 +- plugin/evm/atomic/gossip.go | 35 ++++ plugin/evm/atomic/gossip_test.go | 120 ++++++++++++++ plugin/evm/{ => atomic}/mempool.go | 121 +++++++------- plugin/evm/{ => atomic}/mempool_test.go | 7 +- plugin/evm/{ => atomic}/test_tx.go | 88 ++++++---- plugin/evm/atomic/tx_heap.go | 163 +++++++++++++++++++ plugin/evm/atomic/tx_heap_test.go | 142 ++++++++++++++++ plugin/evm/atomic_syncer_test.go | 5 +- plugin/evm/atomic_trie_iterator_test.go | 16 +- plugin/evm/atomic_trie_test.go | 36 ++-- plugin/evm/atomic_tx_repository_test.go | 14 +- plugin/evm/block.go | 3 +- plugin/evm/block_builder.go | 3 +- plugin/evm/client/client.go | 17 +- plugin/evm/{ => config}/config.go | 19 ++- plugin/evm/{ => config}/config_test.go | 2 +- plugin/evm/config/constants.go | 8 + plugin/evm/export_tx_test.go | 2 +- plugin/evm/gossip.go | 47 ++---- plugin/evm/gossip_stats.go | 67 -------- plugin/evm/gossip_test.go | 109 ------------- plugin/evm/gossiper_atomic_gossiping_test.go | 54 ++++-- plugin/evm/handler.go | 140 ---------------- plugin/evm/mempool_atomic_gossiping_test.go | 44 +++-- plugin/evm/message/codec.go | 10 +- plugin/evm/message/handler.go | 25 +-- plugin/evm/message/handler_test.go | 62 ------- plugin/evm/message/message.go | 78 --------- plugin/evm/message/message_test.go | 78 --------- plugin/evm/service.go | 6 +- plugin/evm/tx_gossip_test.go | 27 ++- plugin/evm/vm.go | 55 +++---- plugin/evm/vm_test.go | 19 ++- 36 files changed, 811 insertions(+), 910 deletions(-) create mode 100644 plugin/evm/atomic/gossip.go create mode 100644 plugin/evm/atomic/gossip_test.go rename plugin/evm/{ => atomic}/mempool.go (86%) rename plugin/evm/{ => atomic}/mempool_test.go (92%) rename plugin/evm/{ => atomic}/test_tx.go (74%) create mode 100644 plugin/evm/atomic/tx_heap.go create mode 100644 plugin/evm/atomic/tx_heap_test.go rename plugin/evm/{ => config}/config.go (95%) rename plugin/evm/{ => config}/config_test.go (99%) create mode 100644 plugin/evm/config/constants.go delete mode 100644 plugin/evm/gossip_stats.go delete mode 100644 plugin/evm/handler.go delete mode 100644 plugin/evm/message/handler_test.go delete mode 100644 plugin/evm/message/message.go delete mode 100644 plugin/evm/message/message_test.go diff --git a/peer/network.go b/peer/network.go index 7739e279bc..ebe067fe0c 100644 --- a/peer/network.go +++ b/peer/network.go @@ -56,9 +56,6 @@ type Network interface { // by calling OnPeerConnected for each peer Shutdown() - // SetGossipHandler sets the provided gossip handler as the gossip handler - SetGossipHandler(handler message.GossipHandler) - // SetRequestHandler sets the provided request handler as the request handler SetRequestHandler(handler message.RequestHandler) @@ -87,7 +84,6 @@ type network struct { appSender common.AppSender // avalanchego AppSender for sending messages codec codec.Manager // Codec used for parsing messages appRequestHandler message.RequestHandler // maps request type => handler - gossipHandler message.GossipHandler // maps gossip type => handler peers *peerTracker // tracking of peers & bandwidth appStats stats.RequestHandlerStats // Provide request handler metrics @@ -110,7 +106,6 @@ func NewNetwork(p2pNetwork *p2p.Network, appSender common.AppSender, codec codec outstandingRequestHandlers: make(map[uint32]message.ResponseHandler), activeAppRequests: semaphore.NewWeighted(maxActiveAppRequests), p2pNetwork: p2pNetwork, - gossipHandler: message.NoopMempoolGossipHandler{}, appRequestHandler: message.NoopRequestHandler{}, peers: NewPeerTracker(), appStats: stats.NewRequestHandlerStats(), @@ -345,14 +340,8 @@ func (n *network) markRequestFulfilled(requestID uint32) (message.ResponseHandle // from a peer. An error returned by this function is treated as fatal by the // engine. func (n *network) AppGossip(ctx context.Context, nodeID ids.NodeID, gossipBytes []byte) error { - var gossipMsg message.GossipMessage - if _, err := n.codec.Unmarshal(gossipBytes, &gossipMsg); err != nil { - log.Debug("forwarding AppGossip to SDK network", "nodeID", nodeID, "gossipLen", len(gossipBytes), "err", err) - return n.p2pNetwork.AppGossip(ctx, nodeID, gossipBytes) - } - - log.Debug("processing AppGossip from node", "nodeID", nodeID, "msg", gossipMsg) - return gossipMsg.Handle(n.gossipHandler, nodeID) + log.Debug("forwarding AppGossip to SDK network", "nodeID", nodeID, "gossipLen", len(gossipBytes)) + return n.p2pNetwork.AppGossip(ctx, nodeID, gossipBytes) } // Connected adds the given nodeID to the peer list so that it can receive messages @@ -407,13 +396,6 @@ func (n *network) Shutdown() { n.closed.Set(true) // mark network as closed } -func (n *network) SetGossipHandler(handler message.GossipHandler) { - n.lock.Lock() - defer n.lock.Unlock() - - n.gossipHandler = handler -} - func (n *network) SetRequestHandler(handler message.RequestHandler) { n.lock.Lock() defer n.lock.Unlock() diff --git a/peer/network_test.go b/peer/network_test.go index c792cf9064..3d13c6e679 100644 --- a/peer/network_test.go +++ b/peer/network_test.go @@ -8,7 +8,7 @@ import ( "errors" "fmt" "sync" - "sync/atomic" + syncatomic "sync/atomic" "testing" "time" @@ -37,6 +37,8 @@ var ( Patch: 0, } + codecVersion uint16 = 0 + _ message.Request = &HelloRequest{} _ = &HelloResponse{} _ = &GreetingRequest{} @@ -46,9 +48,7 @@ var ( _ message.RequestHandler = &HelloGreetingRequestHandler{} _ message.RequestHandler = &testRequestHandler{} - _ common.AppSender = testAppSender{} - _ message.GossipMessage = HelloGossip{} - _ message.GossipHandler = &testGossipHandler{} + _ common.AppSender = testAppSender{} _ p2p.Handler = &testSDKHandler{} ) @@ -85,7 +85,7 @@ func TestRequestAnyRequestsRoutingAndResponse(t *testing.T) { if err := net.AppResponse(context.Background(), nodeID, requestID, responseBytes); err != nil { panic(err) } - atomic.AddUint32(&callNum, 1) + syncatomic.AddUint32(&callNum, 1) }() return nil }, @@ -130,7 +130,7 @@ func TestRequestAnyRequestsRoutingAndResponse(t *testing.T) { requestWg.Wait() senderWg.Wait() - assert.Equal(t, totalCalls, int(atomic.LoadUint32(&callNum))) + assert.Equal(t, totalCalls, int(syncatomic.LoadUint32(&callNum))) } func TestAppRequestOnCtxCancellation(t *testing.T) { @@ -190,7 +190,7 @@ func TestRequestRequestsRoutingAndResponse(t *testing.T) { if err := net.AppResponse(context.Background(), nodeID, requestID, responseBytes); err != nil { panic(err) } - atomic.AddUint32(&callNum, 1) + syncatomic.AddUint32(&callNum, 1) }() return nil }, @@ -245,7 +245,7 @@ func TestRequestRequestsRoutingAndResponse(t *testing.T) { requestWg.Wait() senderWg.Wait() - assert.Equal(t, totalCalls, int(atomic.LoadUint32(&callNum))) + assert.Equal(t, totalCalls, int(syncatomic.LoadUint32(&callNum))) for _, nodeID := range nodes { if _, exists := contactedNodes[nodeID]; !exists { t.Fatalf("expected nodeID %s to be contacted but was not", nodeID) @@ -386,14 +386,14 @@ func TestRequestMinVersion(t *testing.T) { var net Network sender := testAppSender{ sendAppRequestFn: func(_ context.Context, nodes set.Set[ids.NodeID], reqID uint32, messageBytes []byte) error { - atomic.AddUint32(&callNum, 1) + syncatomic.AddUint32(&callNum, 1) assert.True(t, nodes.Contains(nodeID), "request nodes should contain expected nodeID") assert.Len(t, nodes, 1, "request nodes should contain exactly one node") go func() { time.Sleep(200 * time.Millisecond) - atomic.AddUint32(&callNum, 1) - responseBytes, err := codecManager.Marshal(message.Version, TestMessage{Message: "this is a response"}) + syncatomic.AddUint32(&callNum, 1) + responseBytes, err := codecManager.Marshal(codecVersion, TestMessage{Message: "this is a response"}) if err != nil { panic(err) } @@ -503,7 +503,6 @@ func TestHandleInvalidMessages(t *testing.T) { p2pNetwork, err := p2p.NewNetwork(logging.NoLog{}, sender, prometheus.NewRegistry(), "") require.NoError(t, err) clientNetwork := NewNetwork(p2pNetwork, sender, codecManager, ids.EmptyNodeID, 1) - clientNetwork.SetGossipHandler(message.NoopMempoolGossipHandler{}) clientNetwork.SetRequestHandler(&testRequestHandler{}) assert.NoError(t, clientNetwork.Connected(context.Background(), nodeID, defaultPeerVersion)) @@ -511,7 +510,8 @@ func TestHandleInvalidMessages(t *testing.T) { defer clientNetwork.Shutdown() // Ensure a valid gossip message sent as any App specific message type does not trigger a fatal error - gossipMsg, err := buildGossip(codecManager, HelloGossip{Msg: "hello there!"}) + marshaller := helloGossipMarshaller{codec: codecManager} + gossipMsg, err := marshaller.MarshalGossip(&HelloGossip{Msg: "hello there!"}) assert.NoError(t, err) // Ensure a valid request message sent as any App specific message type does not trigger a fatal error @@ -552,7 +552,6 @@ func TestNetworkPropagatesRequestHandlerError(t *testing.T) { p2pNetwork, err := p2p.NewNetwork(logging.NoLog{}, nil, prometheus.NewRegistry(), "") require.NoError(t, err) clientNetwork := NewNetwork(p2pNetwork, sender, codecManager, ids.EmptyNodeID, 1) - clientNetwork.SetGossipHandler(message.NoopMempoolGossipHandler{}) clientNetwork.SetRequestHandler(&testRequestHandler{err: errors.New("fail")}) // Return an error from the request handler assert.NoError(t, clientNetwork.Connected(context.Background(), nodeID, defaultPeerVersion)) @@ -615,18 +614,14 @@ func buildCodec(t *testing.T, types ...interface{}) codec.Manager { for _, typ := range types { assert.NoError(t, c.RegisterType(typ)) } - assert.NoError(t, codecManager.RegisterCodec(message.Version, c)) + assert.NoError(t, codecManager.RegisterCodec(codecVersion, c)) return codecManager } // marshalStruct is a helper method used to marshal an object as `interface{}` // so that the codec is able to include the TypeID in the resulting bytes func marshalStruct(codec codec.Manager, obj interface{}) ([]byte, error) { - return codec.Marshal(message.Version, &obj) -} - -func buildGossip(codec codec.Manager, msg message.GossipMessage) ([]byte, error) { - return codec.Marshal(message.Version, &msg) + return codec.Marshal(codecVersion, &obj) } type testAppSender struct { @@ -696,11 +691,11 @@ type HelloGreetingRequestHandler struct { } func (h *HelloGreetingRequestHandler) HandleHelloRequest(ctx context.Context, nodeID ids.NodeID, requestID uint32, request *HelloRequest) ([]byte, error) { - return h.codec.Marshal(message.Version, HelloResponse{Response: "Hi"}) + return h.codec.Marshal(codecVersion, HelloResponse{Response: "Hi"}) } func (h *HelloGreetingRequestHandler) HandleGreetingRequest(ctx context.Context, nodeID ids.NodeID, requestID uint32, request *GreetingRequest) ([]byte, error) { - return h.codec.Marshal(message.Version, GreetingResponse{Greet: "Hey there"}) + return h.codec.Marshal(codecVersion, GreetingResponse{Greet: "Hey there"}) } type TestMessage struct { @@ -719,34 +714,22 @@ type HelloGossip struct { Msg string `serialize:"true"` } -func (h HelloGossip) Handle(handler message.GossipHandler, nodeID ids.NodeID) error { - return handler.HandleEthTxs(nodeID, message.EthTxsGossip{}) -} - -func (h HelloGossip) String() string { - return fmt.Sprintf("HelloGossip(%s)", h.Msg) -} - -func (h HelloGossip) Bytes() []byte { - // no op - return nil +func (tx *HelloGossip) GossipID() ids.ID { + return ids.FromStringOrPanic(tx.Msg) } -type testGossipHandler struct { - received bool - nodeID ids.NodeID +type helloGossipMarshaller struct { + codec codec.Manager } -func (t *testGossipHandler) HandleAtomicTx(nodeID ids.NodeID, msg message.AtomicTxGossip) error { - t.received = true - t.nodeID = nodeID - return nil +func (g helloGossipMarshaller) MarshalGossip(tx *HelloGossip) ([]byte, error) { + return g.codec.Marshal(0, tx) } -func (t *testGossipHandler) HandleEthTxs(nodeID ids.NodeID, msg message.EthTxsGossip) error { - t.received = true - t.nodeID = nodeID - return nil +func (g helloGossipMarshaller) UnmarshalGossip(bytes []byte) (*HelloGossip, error) { + h := &HelloGossip{} + _, err := g.codec.Unmarshal(bytes, h) + return h, err } type testRequestHandler struct { diff --git a/plugin/evm/admin.go b/plugin/evm/admin.go index e90be473a7..34595a0b0e 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 { +func (p *Admin) GetVMConfig(_ *http.Request, _ *struct{}, reply *client.ConfigReply) error { reply.Config = &p.vm.config return nil } diff --git a/plugin/evm/atomic/gossip.go b/plugin/evm/atomic/gossip.go new file mode 100644 index 0000000000..2c6cb35da2 --- /dev/null +++ b/plugin/evm/atomic/gossip.go @@ -0,0 +1,35 @@ +// (c) 2019-2025, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package atomic + +import ( + "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/avalanchego/network/p2p/gossip" +) + +var ( + _ gossip.Gossipable = (*GossipAtomicTx)(nil) + _ gossip.Marshaller[*GossipAtomicTx] = (*GossipAtomicTxMarshaller)(nil) +) + +type GossipAtomicTxMarshaller struct{} + +func (g GossipAtomicTxMarshaller) MarshalGossip(tx *GossipAtomicTx) ([]byte, error) { + return tx.Tx.SignedBytes(), nil +} + +func (g GossipAtomicTxMarshaller) UnmarshalGossip(bytes []byte) (*GossipAtomicTx, error) { + tx, err := ExtractAtomicTx(bytes, Codec) + return &GossipAtomicTx{ + Tx: tx, + }, err +} + +type GossipAtomicTx struct { + Tx *Tx +} + +func (tx *GossipAtomicTx) GossipID() ids.ID { + return tx.Tx.ID() +} diff --git a/plugin/evm/atomic/gossip_test.go b/plugin/evm/atomic/gossip_test.go new file mode 100644 index 0000000000..edd88bae18 --- /dev/null +++ b/plugin/evm/atomic/gossip_test.go @@ -0,0 +1,120 @@ +// (c) 2019-2025, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package atomic + +import ( + "testing" + + "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/avalanchego/snow" + "github.com/ava-labs/avalanchego/utils/crypto/secp256k1" + "github.com/ava-labs/avalanchego/vms/components/verify" + "github.com/prometheus/client_golang/prometheus" + "github.com/stretchr/testify/require" +) + +func TestGossipAtomicTxMarshaller(t *testing.T) { + require := require.New(t) + + want := &GossipAtomicTx{ + Tx: &Tx{ + UnsignedAtomicTx: &UnsignedImportTx{}, + Creds: []verify.Verifiable{}, + }, + } + marshaller := GossipAtomicTxMarshaller{} + + key0, err := secp256k1.NewPrivateKey() + require.NoError(err) + require.NoError(want.Tx.Sign(Codec, [][]*secp256k1.PrivateKey{{key0}})) + + bytes, err := marshaller.MarshalGossip(want) + require.NoError(err) + + got, err := marshaller.UnmarshalGossip(bytes) + require.NoError(err) + require.Equal(want.GossipID(), got.GossipID()) +} + +func TestAtomicMempoolIterate(t *testing.T) { + txs := []*GossipAtomicTx{ + { + Tx: &Tx{ + UnsignedAtomicTx: &TestUnsignedTx{ + IDV: ids.GenerateTestID(), + }, + }, + }, + { + Tx: &Tx{ + UnsignedAtomicTx: &TestUnsignedTx{ + IDV: ids.GenerateTestID(), + }, + }, + }, + } + + tests := []struct { + name string + add []*GossipAtomicTx + f func(tx *GossipAtomicTx) bool + possibleValues []*GossipAtomicTx + expectedLen int + }{ + { + name: "func matches nothing", + add: txs, + f: func(*GossipAtomicTx) bool { + return false + }, + possibleValues: nil, + }, + { + name: "func matches all", + add: txs, + f: func(*GossipAtomicTx) bool { + return true + }, + possibleValues: txs, + expectedLen: 2, + }, + { + name: "func matches subset", + add: txs, + f: func(tx *GossipAtomicTx) bool { + return tx.Tx == txs[0].Tx + }, + possibleValues: txs, + expectedLen: 1, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + require := require.New(t) + m, err := NewMempool(&snow.Context{}, prometheus.NewRegistry(), 10, nil) + require.NoError(err) + + for _, add := range tt.add { + require.NoError(m.Add(add)) + } + + matches := make([]*GossipAtomicTx, 0) + f := func(tx *GossipAtomicTx) bool { + match := tt.f(tx) + + if match { + matches = append(matches, tx) + } + + return match + } + + m.Iterate(f) + + require.Len(matches, tt.expectedLen) + require.Subset(tt.possibleValues, matches) + }) + } +} diff --git a/plugin/evm/mempool.go b/plugin/evm/atomic/mempool.go similarity index 86% rename from plugin/evm/mempool.go rename to plugin/evm/atomic/mempool.go index acb8db4e3f..69e1e509b6 100644 --- a/plugin/evm/mempool.go +++ b/plugin/evm/atomic/mempool.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 ( "errors" @@ -15,7 +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/ava-labs/coreth/plugin/evm/config" "github.com/ethereum/go-ethereum/log" ) @@ -24,8 +24,11 @@ const ( ) var ( - errTxAlreadyKnown = errors.New("tx already known") - errNoGasUsed = errors.New("no gas used") + errTxAlreadyKnown = errors.New("tx already known") + errNoGasUsed = errors.New("no gas used") + ErrConflictingAtomicTx = errors.New("conflicting atomic tx present") + ErrInsufficientAtomicTxFee = errors.New("atomic tx fee too low for atomic mempool") + ErrTooManyAtomicTx = errors.New("too many atomic tx") _ gossip.Set[*GossipAtomicTx] = (*Mempool)(nil) ) @@ -59,12 +62,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]*atomic.Tx + currentTxs map[ids.ID]*Tx // issuedTxs is the set of transactions that have been issued into a new block - issuedTxs map[ids.ID]*atomic.Tx + issuedTxs map[ids.ID]*Tx // discardedTxs is an LRU Cache of transactions that have been discarded after failing // verification. - discardedTxs *cache.LRU[ids.ID, *atomic.Tx] + discardedTxs *cache.LRU[ids.ID, *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{} @@ -72,31 +75,35 @@ 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]*atomic.Tx + utxoSpenders map[ids.ID]*Tx // bloom is a bloom filter containing the txs in the mempool bloom *gossip.BloomFilter metrics *mempoolMetrics - verify func(tx *atomic.Tx) error + verify func(tx *Tx) error } // NewMempool returns a Mempool with [maxSize] -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) +func NewMempool(ctx *snow.Context, registerer prometheus.Registerer, maxSize int, verify func(tx *Tx) error) (*Mempool, error) { + bloom, err := gossip.NewBloomFilter(registerer, "atomic_mempool_bloom_filter", + config.TxGossipBloomMinTargetElements, + config.TxGossipBloomTargetFalsePositiveRate, + config.TxGossipBloomResetFalsePositiveRate, + ) if err != nil { return nil, fmt.Errorf("failed to initialize bloom filter: %w", err) } return &Mempool{ ctx: ctx, - issuedTxs: make(map[ids.ID]*atomic.Tx), - discardedTxs: &cache.LRU[ids.ID, *atomic.Tx]{Size: discardedTxsCacheSize}, - currentTxs: make(map[ids.ID]*atomic.Tx), + issuedTxs: make(map[ids.ID]*Tx), + discardedTxs: &cache.LRU[ids.ID, *Tx]{Size: discardedTxsCacheSize}, + currentTxs: make(map[ids.ID]*Tx), Pending: make(chan struct{}, 1), txHeap: newTxHeap(maxSize), maxSize: maxSize, - utxoSpenders: make(map[ids.ID]*atomic.Tx), + utxoSpenders: make(map[ids.ID]*Tx), bloom: bloom, metrics: newMempoolMetrics(), verify: verify, @@ -118,7 +125,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 *atomic.Tx) (uint64, error) { +func (m *Mempool) atomicTxGasPrice(tx *Tx) (uint64, error) { gasUsed, err := tx.GasUsed(true) if err != nil { return 0, err @@ -137,35 +144,19 @@ func (m *Mempool) Add(tx *GossipAtomicTx) error { m.ctx.Lock.RLock() defer m.ctx.Lock.RUnlock() - m.lock.Lock() - defer m.lock.Unlock() - - err := m.addTx(tx.Tx, false) - if errors.Is(err, errTxAlreadyKnown) { - return err - } - - if err != nil { - txID := tx.Tx.ID() - m.discardedTxs.Put(txID, tx.Tx) - log.Debug("failed to issue remote tx to mempool", - "txID", txID, - "err", err, - ) - } - - return err + return m.AddRemoteTx(tx.Tx) } -// AddTx attempts to add [tx] to the mempool and returns an error if +// AddRemoteTx 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 *atomic.Tx) error { +func (m *Mempool) AddRemoteTx(tx *Tx) error { m.lock.Lock() defer m.lock.Unlock() - err := m.addTx(tx, false) + err := m.addTx(tx, false, false) + // Do not attempt to discard the tx if it was already known if errors.Is(err, errTxAlreadyKnown) { - return nil + return err } if err != nil { @@ -181,11 +172,11 @@ func (m *Mempool) AddTx(tx *atomic.Tx) error { return err } -func (m *Mempool) AddLocalTx(tx *atomic.Tx) error { +func (m *Mempool) AddLocalTx(tx *Tx) error { m.lock.Lock() defer m.lock.Unlock() - err := m.addTx(tx, false) + err := m.addTx(tx, true, false) if errors.Is(err, errTxAlreadyKnown) { return nil } @@ -193,29 +184,24 @@ func (m *Mempool) AddLocalTx(tx *atomic.Tx) error { return err } -// forceAddTx forcibly adds a *atomic.Tx to the mempool and bypasses all verification. -func (m *Mempool) ForceAddTx(tx *atomic.Tx) error { +// forceAddTx forcibly adds a *Tx to the mempool and bypasses all verification. +func (m *Mempool) ForceAddTx(tx *Tx) error { m.lock.Lock() defer m.lock.Unlock() - err := m.addTx(tx, true) - if errors.Is(err, errTxAlreadyKnown) { - return nil - } - - return nil + return m.addTx(tx, true, true) } // 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 *atomic.Tx) (uint64, ids.ID, []*atomic.Tx, error) { +func (m *Mempool) checkConflictTx(tx *Tx) (uint64, ids.ID, []*Tx, error) { utxoSet := tx.InputUTXOs() var ( - highestGasPrice uint64 = 0 - conflictingTxs []*atomic.Tx = make([]*atomic.Tx, 0) - highestGasPriceConflictTxID ids.ID = ids.ID{} + highestGasPrice uint64 = 0 + conflictingTxs []*Tx = make([]*Tx, 0) + highestGasPriceConflictTxID ids.ID = ids.ID{} ) for utxoID := range utxoSet { // Get current gas price of the existing tx in the mempool @@ -240,7 +226,7 @@ func (m *Mempool) checkConflictTx(tx *atomic.Tx) (uint64, ids.ID, []*atomic.Tx, // addTx adds [tx] to the mempool. Assumes [m.lock] is held. // If [force], skips conflict checks within the mempool. -func (m *Mempool) addTx(tx *atomic.Tx, force bool) error { +func (m *Mempool) addTx(tx *Tx, local bool, 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. @@ -253,6 +239,11 @@ func (m *Mempool) addTx(tx *atomic.Tx, force bool) error { if _, exists := m.txHeap.Get(txID); exists { return fmt.Errorf("%w: tx %s is pending", errTxAlreadyKnown, tx.ID()) } + if !local { + if _, exists := m.discardedTxs.Get(txID); exists { + return fmt.Errorf("%w: tx %s was discarded", errTxAlreadyKnown, tx.ID()) + } + } if !force && m.verify != nil { if err := m.verify(tx); err != nil { return err @@ -271,7 +262,7 @@ func (m *Mempool) addTx(tx *atomic.Tx, force bool) error { if highestGasPrice >= gasPrice { return fmt.Errorf( "%w: issued tx (%s) gas price %d <= conflict tx (%s) gas price %d (%d total conflicts in mempool)", - errConflictingAtomicTx, + ErrConflictingAtomicTx, txID, gasPrice, highestGasPriceConflictTxID, @@ -296,7 +287,7 @@ func (m *Mempool) addTx(tx *atomic.Tx, force bool) error { if minGasPrice >= gasPrice { return fmt.Errorf( "%w currentMin=%d provided=%d", - errInsufficientAtomicTxFee, + ErrInsufficientAtomicTxFee, minGasPrice, gasPrice, ) @@ -306,7 +297,7 @@ func (m *Mempool) addTx(tx *atomic.Tx, force bool) error { } else { // This could occur if we have used our entire size allowance on // transactions that are currently processing. - return errTooManyAtomicTx + return ErrTooManyAtomicTx } } @@ -330,7 +321,7 @@ func (m *Mempool) addTx(tx *atomic.Tx, force bool) error { } m.bloom.Add(&GossipAtomicTx{Tx: tx}) - reset, err := gossip.ResetBloomFilterIfNeeded(m.bloom, m.length()*txGossipBloomChurnMultiplier) + reset, err := gossip.ResetBloomFilterIfNeeded(m.bloom, m.length()*config.TxGossipBloomChurnMultiplier) if err != nil { return err } @@ -372,7 +363,7 @@ func (m *Mempool) GetFilter() ([]byte, []byte) { } // NextTx returns a transaction to be issued from the mempool. -func (m *Mempool) NextTx() (*atomic.Tx, bool) { +func (m *Mempool) NextTx() (*Tx, bool) { m.lock.Lock() defer m.lock.Unlock() @@ -392,7 +383,7 @@ func (m *Mempool) NextTx() (*atomic.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) (*atomic.Tx, bool) { +func (m *Mempool) GetPendingTx(txID ids.ID) (*Tx, bool) { m.lock.RLock() defer m.lock.RUnlock() @@ -402,7 +393,7 @@ func (m *Mempool) GetPendingTx(txID ids.ID) (*atomic.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) (*atomic.Tx, bool, bool) { +func (m *Mempool) GetTx(txID ids.ID) (*Tx, bool, bool) { m.lock.RLock() defer m.lock.RUnlock() @@ -485,7 +476,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 *atomic.Tx) { +func (m *Mempool) cancelTx(tx *Tx) { // Add tx to heap sorted by gasPrice gasPrice, err := m.atomicTxGasPrice(tx) if err == nil { @@ -527,7 +518,7 @@ func (m *Mempool) DiscardCurrentTxs() { // discardCurrentTx discards [tx] from the set of current transactions. // Assumes the lock is held. -func (m *Mempool) discardCurrentTx(tx *atomic.Tx) { +func (m *Mempool) discardCurrentTx(tx *Tx) { m.removeSpenders(tx) m.discardedTxs.Put(tx.ID(), tx) delete(m.currentTxs, tx.ID()) @@ -541,7 +532,7 @@ func (m *Mempool) discardCurrentTx(tx *atomic.Tx) { // removeTx must be called for all conflicts before overwriting the utxoSpenders // map. // Assumes lock is held. -func (m *Mempool) removeTx(tx *atomic.Tx, discard bool) { +func (m *Mempool) removeTx(tx *Tx, discard bool) { txID := tx.ID() // Remove from [currentTxs], [txHeap], and [issuedTxs]. @@ -566,7 +557,7 @@ func (m *Mempool) removeTx(tx *atomic.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 *atomic.Tx) { +func (m *Mempool) removeSpenders(tx *Tx) { for utxoID := range tx.InputUTXOs() { delete(m.utxoSpenders, utxoID) } @@ -574,7 +565,7 @@ func (m *Mempool) removeSpenders(tx *atomic.Tx) { // RemoveTx removes [txID] from the mempool completely. // Evicts [tx] from the discarded cache if present. -func (m *Mempool) RemoveTx(tx *atomic.Tx) { +func (m *Mempool) RemoveTx(tx *Tx) { m.lock.Lock() defer m.lock.Unlock() diff --git a/plugin/evm/mempool_test.go b/plugin/evm/atomic/mempool_test.go similarity index 92% rename from plugin/evm/mempool_test.go rename to plugin/evm/atomic/mempool_test.go index 8129edc577..9334853a5e 100644 --- a/plugin/evm/mempool_test.go +++ b/plugin/evm/atomic/mempool_test.go @@ -1,14 +1,13 @@ // Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. -package evm +package atomic import ( "testing" "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" ) @@ -21,7 +20,7 @@ func TestMempoolAddTx(t *testing.T) { txs := make([]*GossipAtomicTx, 0) for i := 0; i < 3_000; i++ { tx := &GossipAtomicTx{ - Tx: &atomic.Tx{ + Tx: &Tx{ UnsignedAtomicTx: &TestUnsignedTx{ IDV: ids.GenerateTestID(), }, @@ -44,7 +43,7 @@ func TestMempoolAdd(t *testing.T) { require.NoError(err) tx := &GossipAtomicTx{ - Tx: &atomic.Tx{ + Tx: &Tx{ UnsignedAtomicTx: &TestUnsignedTx{ IDV: ids.GenerateTestID(), }, diff --git a/plugin/evm/test_tx.go b/plugin/evm/atomic/test_tx.go similarity index 74% rename from plugin/evm/test_tx.go rename to plugin/evm/atomic/test_tx.go index e001cb4dda..50af59e09f 100644 --- a/plugin/evm/test_tx.go +++ b/plugin/evm/atomic/test_tx.go @@ -1,25 +1,44 @@ // (c) 2020-2021, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. -package evm +package atomic import ( "math/big" "math/rand" + "github.com/ava-labs/avalanchego/codec" + "github.com/ava-labs/avalanchego/codec/linearcodec" "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/params" - "github.com/ava-labs/coreth/plugin/evm/atomic" ) +var TestTxCodec codec.Manager + +func init() { + TestTxCodec = codec.NewDefaultManager() + c := linearcodec.NewDefault() + + errs := wrappers.Errs{} + errs.Add( + c.RegisterType(&TestUnsignedTx{}), + c.RegisterType(&avalancheatomic.Element{}), + c.RegisterType(&avalancheatomic.Requests{}), + TestTxCodec.RegisterCodec(atomic.CodecVersion, c), + ) + + if errs.Errored() { + panic(errs.Err) + } +} + type TestUnsignedTx struct { GasUsedV uint64 `serialize:"true"` AcceptRequestsBlockchainIDV ids.ID `serialize:"true"` @@ -34,7 +53,7 @@ type TestUnsignedTx struct { EVMStateTransferV error } -var _ atomic.UnsignedAtomicTx = &TestUnsignedTx{} +var _ UnsignedAtomicTx = &TestUnsignedTx{} // GasUsed implements the UnsignedAtomicTx interface func (t *TestUnsignedTx) GasUsed(fixedFee bool) (uint64, error) { return t.GasUsedV, nil } @@ -66,40 +85,39 @@ 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(backend *atomic.Backend, stx *atomic.Tx, parent atomic.AtomicBlockContext, baseFee *big.Int) error { +func (t *TestUnsignedTx) SemanticVerify(backend *Backend, stx *Tx, parent AtomicBlockContext, baseFee *big.Int) error { return t.SemanticVerifyV } // EVMStateTransfer implements the UnsignedAtomicTx interface -func (t *TestUnsignedTx) EVMStateTransfer(ctx *snow.Context, state atomic.StateDB) error { +func (t *TestUnsignedTx) EVMStateTransfer(ctx *snow.Context, state StateDB) error { return t.EVMStateTransferV } -func testTxCodec() codec.Manager { - codec := codec.NewDefaultManager() - c := linearcodec.NewDefault() +var TestBlockchainID = ids.GenerateTestID() - errs := wrappers.Errs{} - errs.Add( - c.RegisterType(&TestUnsignedTx{}), - c.RegisterType(&avalancheatomic.Element{}), - c.RegisterType(&avalancheatomic.Requests{}), - codec.RegisterCodec(atomic.CodecVersion, c), - ) - - if errs.Errored() { - panic(errs.Err) +func GenerateTestImportTxWithGas(gasUsed uint64, burned uint64) *Tx { + return &Tx{ + UnsignedAtomicTx: &TestUnsignedTx{ + IDV: ids.GenerateTestID(), + GasUsedV: gasUsed, + BurnedV: burned, + AcceptRequestsBlockchainIDV: TestBlockchainID, + AcceptRequestsV: &avalancheatomic.Requests{ + RemoveRequests: [][]byte{ + utils.RandomBytes(32), + utils.RandomBytes(32), + }, + }, + }, } - return codec } -var blockChainID = ids.GenerateTestID() - -func testDataImportTx() *atomic.Tx { - return &atomic.Tx{ +func GenerateTestImportTx() *Tx { + return &Tx{ UnsignedAtomicTx: &TestUnsignedTx{ IDV: ids.GenerateTestID(), - AcceptRequestsBlockchainIDV: blockChainID, + AcceptRequestsBlockchainIDV: TestBlockchainID, AcceptRequestsV: &avalancheatomic.Requests{ RemoveRequests: [][]byte{ utils.RandomBytes(32), @@ -110,11 +128,11 @@ func testDataImportTx() *atomic.Tx { } } -func testDataExportTx() *atomic.Tx { - return &atomic.Tx{ +func GenerateTestExportTx() *Tx { + return &Tx{ UnsignedAtomicTx: &TestUnsignedTx{ IDV: ids.GenerateTestID(), - AcceptRequestsBlockchainIDV: blockChainID, + AcceptRequestsBlockchainIDV: TestBlockchainID, AcceptRequestsV: &avalancheatomic.Requests{ PutRequests: []*avalancheatomic.Element{ { @@ -131,22 +149,22 @@ func testDataExportTx() *atomic.Tx { } } -func newTestTx() *atomic.Tx { +func NewTestTx() *Tx { txType := rand.Intn(2) switch txType { case 0: - return testDataImportTx() + return GenerateTestImportTx() case 1: - return testDataExportTx() + return GenerateTestExportTx() default: panic("rng generated unexpected value for tx type") } } -func newTestTxs(numTxs int) []*atomic.Tx { - txs := make([]*atomic.Tx, 0, numTxs) +func NewTestTxs(numTxs int) []*Tx { + txs := make([]*Tx, 0, numTxs) for i := 0; i < numTxs; i++ { - txs = append(txs, newTestTx()) + txs = append(txs, NewTestTx()) } return txs diff --git a/plugin/evm/atomic/tx_heap.go b/plugin/evm/atomic/tx_heap.go new file mode 100644 index 0000000000..58cbcf0c0b --- /dev/null +++ b/plugin/evm/atomic/tx_heap.go @@ -0,0 +1,163 @@ +// (c) 2020-2021, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package atomic + +import ( + "container/heap" + + "github.com/ava-labs/avalanchego/ids" +) + +// txEntry is used to track the [gasPrice] transactions pay to be included in +// the mempool. +type txEntry struct { + id ids.ID + gasPrice uint64 + tx *Tx + index int +} + +// internalTxHeap is used to track pending atomic transactions by [gasPrice] +type internalTxHeap struct { + isMinHeap bool + items []*txEntry + lookup map[ids.ID]*txEntry +} + +func newInternalTxHeap(items int, isMinHeap bool) *internalTxHeap { + return &internalTxHeap{ + isMinHeap: isMinHeap, + items: make([]*txEntry, 0, items), + lookup: map[ids.ID]*txEntry{}, + } +} + +func (th internalTxHeap) Len() int { return len(th.items) } + +func (th internalTxHeap) Less(i, j int) bool { + if th.isMinHeap { + return th.items[i].gasPrice < th.items[j].gasPrice + } + return th.items[i].gasPrice > th.items[j].gasPrice +} + +func (th internalTxHeap) Swap(i, j int) { + th.items[i], th.items[j] = th.items[j], th.items[i] + th.items[i].index = i + th.items[j].index = j +} + +func (th *internalTxHeap) Push(x interface{}) { + entry := x.(*txEntry) + if th.Has(entry.id) { + return + } + th.items = append(th.items, entry) + th.lookup[entry.id] = entry +} + +func (th *internalTxHeap) Pop() interface{} { + n := len(th.items) + item := th.items[n-1] + th.items[n-1] = nil // avoid memory leak + th.items = th.items[0 : n-1] + delete(th.lookup, item.id) + return item +} + +func (th *internalTxHeap) Get(id ids.ID) (*txEntry, bool) { + entry, ok := th.lookup[id] + if !ok { + return nil, false + } + return entry, true +} + +func (th *internalTxHeap) Has(id ids.ID) bool { + _, has := th.Get(id) + return has +} + +type txHeap struct { + maxHeap *internalTxHeap + minHeap *internalTxHeap +} + +func newTxHeap(maxSize int) *txHeap { + return &txHeap{ + maxHeap: newInternalTxHeap(maxSize, false), + minHeap: newInternalTxHeap(maxSize, true), + } +} + +func (th *txHeap) Push(tx *Tx, gasPrice uint64) { + txID := tx.ID() + oldLen := th.Len() + heap.Push(th.maxHeap, &txEntry{ + id: txID, + gasPrice: gasPrice, + tx: tx, + index: oldLen, + }) + heap.Push(th.minHeap, &txEntry{ + id: txID, + gasPrice: gasPrice, + tx: tx, + index: oldLen, + }) +} + +// Assumes there is non-zero items in [txHeap] +func (th *txHeap) PeekMax() (*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) { + txEntry := th.minHeap.items[0] + return txEntry.tx, txEntry.gasPrice +} + +// Assumes there is non-zero items in [txHeap] +func (th *txHeap) PopMax() *Tx { + return th.Remove(th.maxHeap.items[0].id) +} + +// Assumes there is non-zero items in [txHeap] +func (th *txHeap) PopMin() *Tx { + return th.Remove(th.minHeap.items[0].id) +} + +func (th *txHeap) Remove(id ids.ID) *Tx { + maxEntry, ok := th.maxHeap.Get(id) + if !ok { + return nil + } + heap.Remove(th.maxHeap, maxEntry.index) + + minEntry, ok := th.minHeap.Get(id) + if !ok { + // This should never happen, as that would mean the heaps are out of + // sync. + return nil + } + return heap.Remove(th.minHeap, minEntry.index).(*txEntry).tx +} + +func (th *txHeap) Len() int { + return th.maxHeap.Len() +} + +func (th *txHeap) Get(id ids.ID) (*Tx, bool) { + txEntry, ok := th.maxHeap.Get(id) + if !ok { + return nil, false + } + return txEntry.tx, true +} + +func (th *txHeap) Has(id ids.ID) bool { + return th.maxHeap.Has(id) +} diff --git a/plugin/evm/atomic/tx_heap_test.go b/plugin/evm/atomic/tx_heap_test.go new file mode 100644 index 0000000000..c9f602ccea --- /dev/null +++ b/plugin/evm/atomic/tx_heap_test.go @@ -0,0 +1,142 @@ +// (c) 2019-2021, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package atomic + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestTxHeap(t *testing.T) { + var ( + tx0 = &Tx{ + UnsignedAtomicTx: &UnsignedImportTx{ + NetworkID: 0, + }, + } + tx0Bytes = []byte{0} + + tx1 = &Tx{ + UnsignedAtomicTx: &UnsignedImportTx{ + NetworkID: 1, + }, + } + tx1Bytes = []byte{1} + + tx2 = &Tx{ + UnsignedAtomicTx: &UnsignedImportTx{ + NetworkID: 2, + }, + } + tx2Bytes = []byte{2} + ) + tx0.Initialize(tx0Bytes, tx0Bytes) + tx1.Initialize(tx1Bytes, tx1Bytes) + tx2.Initialize(tx2Bytes, tx2Bytes) + + id0 := tx0.ID() + id1 := tx1.ID() + id2 := tx2.ID() + + t.Run("add/remove single entry", func(t *testing.T) { + h := newTxHeap(3) + assert.Zero(t, h.Len()) + + assert := assert.New(t) + h.Push(tx0, 5) + assert.True(h.Has(id0)) + gTx0, gHas0 := h.Get(id0) + assert.Equal(tx0, gTx0) + assert.True(gHas0) + h.Remove(id0) + assert.False(h.Has(id0)) + assert.Zero(h.Len()) + h.Push(tx0, 5) + assert.True(h.Has(id0)) + assert.Equal(1, h.Len()) + }) + + t.Run("add other items", func(t *testing.T) { + h := newTxHeap(3) + assert.Zero(t, h.Len()) + + assert := assert.New(t) + h.Push(tx1, 10) + assert.True(h.Has(id1)) + gTx1, gHas1 := h.Get(id1) + assert.Equal(tx1, gTx1) + assert.True(gHas1) + + h.Push(tx2, 2) + assert.True(h.Has(id2)) + gTx2, gHas2 := h.Get(id2) + assert.Equal(tx2, gTx2) + assert.True(gHas2) + + assert.Equal(id1, h.PopMax().ID()) + assert.Equal(id2, h.PopMax().ID()) + + assert.False(h.Has(id0)) + gTx0, gHas0 := h.Get(id0) + assert.Nil(gTx0) + assert.False(gHas0) + + assert.False(h.Has(id1)) + gTx1, gHas1 = h.Get(id1) + assert.Nil(gTx1) + assert.False(gHas1) + + assert.False(h.Has(id2)) + gTx2, gHas2 = h.Get(id2) + assert.Nil(gTx2) + assert.False(gHas2) + }) + + verifyRemovalOrder := func(t *testing.T, h *txHeap) { + t.Helper() + + assert := assert.New(t) + assert.Equal(id2, h.PopMin().ID()) + assert.True(h.Has(id0)) + assert.True(h.Has(id1)) + assert.False(h.Has(id2)) + assert.Equal(id0, h.PopMin().ID()) + assert.False(h.Has(id0)) + assert.True(h.Has(id1)) + assert.False(h.Has(id2)) + assert.Equal(id1, h.PopMin().ID()) + assert.False(h.Has(id0)) + assert.False(h.Has(id1)) + assert.False(h.Has(id2)) + } + + t.Run("drop", func(t *testing.T) { + h := newTxHeap(3) + assert.Zero(t, h.Len()) + + h.Push(tx0, 5) + h.Push(tx1, 10) + h.Push(tx2, 2) + verifyRemovalOrder(t, h) + }) + t.Run("drop (alt order)", func(t *testing.T) { + h := newTxHeap(3) + assert.Zero(t, h.Len()) + + h.Push(tx0, 5) + h.Push(tx2, 2) + h.Push(tx1, 10) + verifyRemovalOrder(t, h) + }) + t.Run("drop (alt order 2)", func(t *testing.T) { + h := newTxHeap(3) + assert.Zero(t, h.Len()) + + h.Push(tx2, 2) + h.Push(tx0, 5) + h.Push(tx1, 10) + verifyRemovalOrder(t, h) + }) +} diff --git a/plugin/evm/atomic_syncer_test.go b/plugin/evm/atomic_syncer_test.go index 7540be1a32..86589cc4d8 100644 --- a/plugin/evm/atomic_syncer_test.go +++ b/plugin/evm/atomic_syncer_test.go @@ -17,6 +17,7 @@ import ( "github.com/ava-labs/avalanchego/database/versiondb" "github.com/ava-labs/coreth/core/rawdb" + "github.com/ava-labs/coreth/plugin/evm/config" "github.com/ava-labs/coreth/plugin/evm/message" syncclient "github.com/ava-labs/coreth/sync/client" "github.com/ava-labs/coreth/sync/handlers" @@ -64,7 +65,7 @@ func testAtomicSyncer(t *testing.T, serverTrieDB *triedb.Database, targetHeight // next trie. for i, checkpoint := range checkpoints { // Create syncer targeting the current [syncTrie]. - syncer, err := atomicBackend.Syncer(mockClient, targetRoot, targetHeight, defaultStateSyncRequestSize) + syncer, err := atomicBackend.Syncer(mockClient, targetRoot, targetHeight, config.DefaultStateSyncRequestSize) if err != nil { t.Fatal(err) } @@ -91,7 +92,7 @@ func testAtomicSyncer(t *testing.T, serverTrieDB *triedb.Database, targetHeight } // Create syncer targeting the current [targetRoot]. - syncer, err := atomicBackend.Syncer(mockClient, targetRoot, targetHeight, defaultStateSyncRequestSize) + syncer, err := atomicBackend.Syncer(mockClient, targetRoot, targetHeight, config.DefaultStateSyncRequestSize) if err != nil { t.Fatal(err) } diff --git a/plugin/evm/atomic_trie_iterator_test.go b/plugin/evm/atomic_trie_iterator_test.go index 922aed4cfc..50ba586ffd 100644 --- a/plugin/evm/atomic_trie_iterator_test.go +++ b/plugin/evm/atomic_trie_iterator_test.go @@ -6,7 +6,7 @@ package evm import ( "testing" - "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/database/versiondb" "github.com/ava-labs/avalanchego/ids" @@ -14,24 +14,26 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/ava-labs/coreth/plugin/evm/atomic" ) -func testSharedMemory() atomic.SharedMemory { - m := atomic.NewMemory(memdb.New()) +func testSharedMemory() avalancheatomic.SharedMemory { + m := avalancheatomic.NewMemory(memdb.New()) return m.NewSharedMemory(testCChainID) } func TestIteratorCanIterate(t *testing.T) { lastAcceptedHeight := uint64(1000) db := versiondb.New(memdb.New()) - codec := testTxCodec() + codec := atomic.TestTxCodec repo, err := NewAtomicTxRepository(db, codec, lastAcceptedHeight) assert.NoError(t, err) // create state with multiple transactions // since each test transaction generates random ID for blockchainID we should get // multiple blockchain IDs per block in the overall combined atomic operation map - operationsMap := make(map[uint64]map[ids.ID]*atomic.Requests) + operationsMap := make(map[uint64]map[ids.ID]*avalancheatomic.Requests) writeTxs(t, repo, 1, lastAcceptedHeight, constTxsPerHeight(3), nil, operationsMap) // create an atomic trie @@ -64,14 +66,14 @@ func TestIteratorHandlesInvalidData(t *testing.T) { require := require.New(t) lastAcceptedHeight := uint64(1000) db := versiondb.New(memdb.New()) - codec := testTxCodec() + codec := atomic.TestTxCodec repo, err := NewAtomicTxRepository(db, codec, lastAcceptedHeight) require.NoError(err) // create state with multiple transactions // since each test transaction generates random ID for blockchainID we should get // multiple blockchain IDs per block in the overall combined atomic operation map - operationsMap := make(map[uint64]map[ids.ID]*atomic.Requests) + operationsMap := make(map[uint64]map[ids.ID]*avalancheatomic.Requests) writeTxs(t, repo, 1, lastAcceptedHeight, constTxsPerHeight(3), nil, operationsMap) // create an atomic trie diff --git a/plugin/evm/atomic_trie_test.go b/plugin/evm/atomic_trie_test.go index 193226f588..2a82964e94 100644 --- a/plugin/evm/atomic_trie_test.go +++ b/plugin/evm/atomic_trie_test.go @@ -139,7 +139,7 @@ func TestAtomicTrieInitialize(t *testing.T) { } { t.Run(name, func(t *testing.T) { db := versiondb.New(memdb.New()) - codec := testTxCodec() + codec := atomic.TestTxCodec repo, err := NewAtomicTxRepository(db, codec, test.lastAcceptedHeight) if err != nil { t.Fatal(err) @@ -188,7 +188,7 @@ func TestAtomicTrieInitialize(t *testing.T) { // during the initialization phase will cause an invalid root when indexing continues. nextCommitHeight := nearestCommitHeight(test.lastAcceptedHeight+test.commitInterval, test.commitInterval) for i := test.lastAcceptedHeight + 1; i <= nextCommitHeight; i++ { - txs := newTestTxs(test.numTxsPerBlock(i)) + txs := atomic.NewTestTxs(test.numTxsPerBlock(i)) if err := repo.Write(i, txs); err != nil { t.Fatal(err) } @@ -228,7 +228,7 @@ func TestAtomicTrieInitialize(t *testing.T) { func TestIndexerInitializesOnlyOnce(t *testing.T) { lastAcceptedHeight := uint64(25) db := versiondb.New(memdb.New()) - codec := testTxCodec() + codec := atomic.TestTxCodec repo, err := NewAtomicTxRepository(db, codec, lastAcceptedHeight) assert.NoError(t, err) operationsMap := make(map[uint64]map[ids.ID]*avalancheatomic.Requests) @@ -247,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, []*atomic.Tx{testDataExportTx()}) + err = repo.Write(15, []*atomic.Tx{atomic.GenerateTestExportTx()}) assert.NoError(t, err) // Re-initialize the atomic trie @@ -262,7 +262,7 @@ func TestIndexerInitializesOnlyOnce(t *testing.T) { func newTestAtomicTrie(t *testing.T) AtomicTrie { db := versiondb.New(memdb.New()) - repo, err := NewAtomicTxRepository(db, testTxCodec(), 0) + repo, err := NewAtomicTxRepository(db, atomic.TestTxCodec, 0) if err != nil { t.Fatal(err) } @@ -282,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 := mustAtomicOps(testDataImportTx()) + atomicRequests := mustAtomicOps(atomic.GenerateTestImportTx()) err := indexAtomicTxs(atomicTrie, height, atomicRequests) assert.NoError(t, err) if height%testCommitInterval == 0 { @@ -313,8 +313,8 @@ func TestAtomicOpsAreNotTxOrderDependent(t *testing.T) { atomicTrie2 := newTestAtomicTrie(t) for height := uint64(0); height <= testCommitInterval; /*=205*/ height++ { - tx1 := testDataImportTx() - tx2 := testDataImportTx() + tx1 := atomic.GenerateTestImportTx() + tx2 := atomic.GenerateTestImportTx() atomicRequests1, err := mergeAtomicOps([]*atomic.Tx{tx1, tx2}) assert.NoError(t, err) atomicRequests2, err := mergeAtomicOps([]*atomic.Tx{tx2, tx1}) @@ -339,7 +339,7 @@ func TestAtomicTrieDoesNotSkipBonusBlocks(t *testing.T) { commitInterval := uint64(10) expectedCommitHeight := uint64(100) db := versiondb.New(memdb.New()) - codec := testTxCodec() + codec := atomic.TestTxCodec repo, err := NewAtomicTxRepository(db, codec, lastAcceptedHeight) if err != nil { t.Fatal(err) @@ -371,7 +371,7 @@ func TestIndexingNilShouldNotImpactTrie(t *testing.T) { // operations to index ops := make([]map[ids.ID]*avalancheatomic.Requests, 0) for i := 0; i <= testCommitInterval; i++ { - ops = append(ops, mustAtomicOps(testDataImportTx())) + ops = append(ops, mustAtomicOps(atomic.GenerateTestImportTx())) } // without nils @@ -511,9 +511,9 @@ func TestApplyToSharedMemory(t *testing.T) { commitInterval: 10, lastAcceptedHeight: 25, setMarker: func(a *atomicBackend) error { - cursor := make([]byte, wrappers.LongLen+len(blockChainID[:])) + cursor := make([]byte, wrappers.LongLen+len(atomic.TestBlockchainID[:])) binary.BigEndian.PutUint64(cursor, 10) - copy(cursor[wrappers.LongLen:], blockChainID[:]) + copy(cursor[wrappers.LongLen:], atomic.TestBlockchainID[:]) return a.metadataDB.Put(appliedSharedMemoryCursorKey, cursor) }, expectOpsApplied: func(height uint64) bool { return height > 10 && height <= 20 }, @@ -527,7 +527,7 @@ func TestApplyToSharedMemory(t *testing.T) { } { t.Run(name, func(t *testing.T) { db := versiondb.New(memdb.New()) - codec := testTxCodec() + codec := atomic.TestTxCodec repo, err := NewAtomicTxRepository(db, codec, test.lastAcceptedHeight) assert.NoError(t, err) operationsMap := make(map[uint64]map[ids.ID]*avalancheatomic.Requests) @@ -535,7 +535,7 @@ func TestApplyToSharedMemory(t *testing.T) { // Initialize atomic repository m := avalancheatomic.NewMemory(db) - sharedMemories := newSharedMemories(m, testCChainID, blockChainID) + sharedMemories := newSharedMemories(m, testCChainID, atomic.TestBlockchainID) backend, err := NewAtomicBackend(db, sharedMemories.thisChain, test.bonusBlockHeights, repo, test.lastAcceptedHeight, common.Hash{}, test.commitInterval) assert.NoError(t, err) atomicTrie := backend.AtomicTrie().(*atomicTrie) @@ -593,7 +593,7 @@ func TestApplyToSharedMemory(t *testing.T) { func BenchmarkAtomicTrieInit(b *testing.B) { db := versiondb.New(memdb.New()) - codec := testTxCodec() + codec := atomic.TestTxCodec operationsMap := make(map[uint64]map[ids.ID]*avalancheatomic.Requests) @@ -628,7 +628,7 @@ func BenchmarkAtomicTrieInit(b *testing.B) { func BenchmarkAtomicTrieIterate(b *testing.B) { db := versiondb.New(memdb.New()) - codec := testTxCodec() + codec := atomic.TestTxCodec operationsMap := make(map[uint64]map[ids.ID]*avalancheatomic.Requests) @@ -707,7 +707,7 @@ func BenchmarkApplyToSharedMemory(b *testing.B) { func benchmarkApplyToSharedMemory(b *testing.B, disk database.Database, blocks uint64) { db := versiondb.New(disk) - codec := testTxCodec() + codec := atomic.TestTxCodec sharedMemory := testSharedMemory() lastAcceptedHeight := blocks @@ -720,7 +720,7 @@ func benchmarkApplyToSharedMemory(b *testing.B, disk database.Database, blocks u } trie := backend.AtomicTrie() for height := uint64(1); height <= lastAcceptedHeight; height++ { - txs := newTestTxs(constTxsPerHeight(3)(height)) + txs := atomic.NewTestTxs(constTxsPerHeight(3)(height)) ops, err := mergeAtomicOps(txs) assert.NoError(b, err) assert.NoError(b, indexAtomicTxs(trie, height, ops)) diff --git a/plugin/evm/atomic_tx_repository_test.go b/plugin/evm/atomic_tx_repository_test.go index 091bcd8f56..224f8fa726 100644 --- a/plugin/evm/atomic_tx_repository_test.go +++ b/plugin/evm/atomic_tx_repository_test.go @@ -32,7 +32,7 @@ func addTxs(t testing.TB, codec codec.Manager, acceptedAtomicTxDB database.Datab for height := fromHeight; height < toHeight; height++ { txs := make([]*atomic.Tx, 0, txsPerHeight) for i := 0; i < txsPerHeight; i++ { - tx := newTestTx() + tx := atomic.NewTestTx() txs = append(txs, tx) txBytes, err := codec.Marshal(atomic.CodecVersion, tx) assert.NoError(t, err) @@ -74,7 +74,7 @@ func writeTxs(t testing.TB, repo AtomicTxRepository, fromHeight uint64, toHeight 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)) + txs := atomic.NewTestTxs(txsPerHeight(height)) if err := repo.Write(height, txs); err != nil { t.Fatal(err) } @@ -183,7 +183,7 @@ func verifyOperations(t testing.TB, atomicTrie AtomicTrie, codec codec.Manager, func TestAtomicRepositoryReadWriteSingleTx(t *testing.T) { db := versiondb.New(memdb.New()) - codec := testTxCodec() + codec := atomic.TestTxCodec repo, err := NewAtomicTxRepository(db, codec, 0) if err != nil { t.Fatal(err) @@ -196,7 +196,7 @@ func TestAtomicRepositoryReadWriteSingleTx(t *testing.T) { func TestAtomicRepositoryReadWriteMultipleTxs(t *testing.T) { db := versiondb.New(memdb.New()) - codec := testTxCodec() + codec := atomic.TestTxCodec repo, err := NewAtomicTxRepository(db, codec, 0) if err != nil { t.Fatal(err) @@ -209,7 +209,7 @@ func TestAtomicRepositoryReadWriteMultipleTxs(t *testing.T) { func TestAtomicRepositoryPreAP5Migration(t *testing.T) { db := versiondb.New(memdb.New()) - codec := testTxCodec() + codec := atomic.TestTxCodec acceptedAtomicTxDB := prefixdb.New(atomicTxIDDBPrefix, db) txMap := make(map[uint64][]*atomic.Tx) @@ -234,7 +234,7 @@ func TestAtomicRepositoryPreAP5Migration(t *testing.T) { func TestAtomicRepositoryPostAP5Migration(t *testing.T) { db := versiondb.New(memdb.New()) - codec := testTxCodec() + codec := atomic.TestTxCodec acceptedAtomicTxDB := prefixdb.New(atomicTxIDDBPrefix, db) txMap := make(map[uint64][]*atomic.Tx) @@ -259,7 +259,7 @@ func TestAtomicRepositoryPostAP5Migration(t *testing.T) { func benchAtomicRepositoryIndex10_000(b *testing.B, maxHeight uint64, txsPerHeight int) { db := versiondb.New(memdb.New()) - codec := testTxCodec() + codec := atomic.TestTxCodec acceptedAtomicTxDB := prefixdb.New(atomicTxIDDBPrefix, db) txMap := make(map[uint64][]*atomic.Tx) diff --git a/plugin/evm/block.go b/plugin/evm/block.go index 99451cb071..9a2de32601 100644 --- a/plugin/evm/block.go +++ b/plugin/evm/block.go @@ -225,8 +225,9 @@ func (b *Block) handlePrecompileAccept(rules params.Rules) error { func (b *Block) Reject(context.Context) error { log.Debug(fmt.Sprintf("Rejecting block %s (%s) at height %d", b.ID().Hex(), b.ID(), b.Height())) for _, tx := range b.atomicTxs { + // Re-issue the transaction in the mempool, continue even if it fails b.vm.mempool.RemoveTx(tx) - if err := b.vm.mempool.AddTx(tx); err != nil { + if err := b.vm.mempool.AddRemoteTx(tx); err != nil { log.Debug("Failed to re-issue transaction in rejected block", "txID", tx.ID(), "err", err) } } diff --git a/plugin/evm/block_builder.go b/plugin/evm/block_builder.go index d8a1d07024..721561ff40 100644 --- a/plugin/evm/block_builder.go +++ b/plugin/evm/block_builder.go @@ -11,6 +11,7 @@ import ( "github.com/ava-labs/coreth/core" "github.com/ava-labs/coreth/core/txpool" "github.com/ava-labs/coreth/params" + "github.com/ava-labs/coreth/plugin/evm/atomic" "github.com/holiman/uint256" "github.com/ava-labs/avalanchego/snow" @@ -29,7 +30,7 @@ type blockBuilder struct { chainConfig *params.ChainConfig txPool *txpool.TxPool - mempool *Mempool + mempool *atomic.Mempool shutdownChan <-chan struct{} shutdownWg *sync.WaitGroup diff --git a/plugin/evm/client/client.go b/plugin/evm/client/client.go index f92e55e59f..110036904e 100644 --- a/plugin/evm/client/client.go +++ b/plugin/evm/client/client.go @@ -19,6 +19,7 @@ import ( "github.com/ava-labs/avalanchego/utils/json" "github.com/ava-labs/avalanchego/utils/rpc" "github.com/ava-labs/coreth/plugin/evm/atomic" + "github.com/ava-labs/coreth/plugin/evm/config" ) // Interface compliance @@ -40,7 +41,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.Config, error) } // Client implementation for interacting with EVM [chain] @@ -299,9 +300,13 @@ func (c *client) SetLogLevel(ctx context.Context, level slog.Level, options ...r }, &api.EmptyReply{}, options...) } +type ConfigReply struct { + Config *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.Config, error) { + res := &ConfigReply{} + err := c.adminRequester.SendRequest(ctx, "admin.getVMConfig", struct{}{}, res, options...) + return res.Config, err +} diff --git a/plugin/evm/config.go b/plugin/evm/config/config.go similarity index 95% rename from plugin/evm/config.go rename to plugin/evm/config/config.go index c0fa3b0386..748f9e115a 100644 --- a/plugin/evm/config.go +++ b/plugin/evm/config/config.go @@ -1,13 +1,14 @@ // (c) 2019-2020, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. -package evm +package config import ( "encoding/json" "fmt" "time" + "github.com/ava-labs/avalanchego/utils/constants" "github.com/ava-labs/coreth/core/txpool/legacypool" "github.com/ava-labs/coreth/eth" "github.com/ethereum/go-ethereum/common" @@ -59,7 +60,7 @@ const ( // - normal bootstrap processing time: ~14 blocks / second // - state sync time: ~6 hrs. defaultStateSyncMinBlocks = 300_000 - defaultStateSyncRequestSize = 1024 // the number of key/values to ask peers for per request + DefaultStateSyncRequestSize = 1024 // the number of key/values to ask peers for per request ) var ( @@ -277,7 +278,7 @@ func (c *Config) SetDefaults() { c.StateSyncServerTrieCache = defaultStateSyncServerTrieCache c.StateSyncCommitInterval = defaultSyncableCommitInterval c.StateSyncMinBlocks = defaultStateSyncMinBlocks - c.StateSyncRequestSize = defaultStateSyncRequestSize + c.StateSyncRequestSize = DefaultStateSyncRequestSize c.AllowUnprotectedTxHashes = defaultAllowUnprotectedTxHashes c.AcceptedCacheSize = defaultAcceptedCacheSize } @@ -302,7 +303,17 @@ func (d Duration) MarshalJSON() ([]byte, error) { } // Validate returns an error if this is an invalid config. -func (c *Config) Validate() error { +func (c *Config) Validate(networkID uint32) error { + // Ensure that non-standard commit interval is not allowed for production networks + if constants.ProductionNetworkIDs.Contains(networkID) { + if c.CommitInterval != defaultCommitInterval { + return fmt.Errorf("cannot start non-local network with commit interval %d", c.CommitInterval) + } + if c.StateSyncCommitInterval != defaultSyncableCommitInterval { + return fmt.Errorf("cannot start non-local network with syncable interval %d", c.StateSyncCommitInterval) + } + } + if c.PopulateMissingTries != nil && (c.OfflinePruning || c.Pruning) { return fmt.Errorf("cannot enable populate missing tries while offline pruning (enabled: %t)/pruning (enabled: %t) are enabled", c.OfflinePruning, c.Pruning) } diff --git a/plugin/evm/config_test.go b/plugin/evm/config/config_test.go similarity index 99% rename from plugin/evm/config_test.go rename to plugin/evm/config/config_test.go index 9a8384bf5d..ad13ebdfed 100644 --- a/plugin/evm/config_test.go +++ b/plugin/evm/config/config_test.go @@ -1,7 +1,7 @@ // (c) 2019-2020, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. -package evm +package config import ( "encoding/json" diff --git a/plugin/evm/config/constants.go b/plugin/evm/config/constants.go new file mode 100644 index 0000000000..2e47489f1c --- /dev/null +++ b/plugin/evm/config/constants.go @@ -0,0 +1,8 @@ +package config + +const ( + TxGossipBloomMinTargetElements = 8 * 1024 + TxGossipBloomTargetFalsePositiveRate = 0.01 + TxGossipBloomResetFalsePositiveRate = 0.05 + TxGossipBloomChurnMultiplier = 3 +) diff --git a/plugin/evm/export_tx_test.go b/plugin/evm/export_tx_test.go index 3e2b0d2160..9bc1f498a9 100644 --- a/plugin/evm/export_tx_test.go +++ b/plugin/evm/export_tx_test.go @@ -1944,7 +1944,7 @@ func TestNewExportTxMulticoin(t *testing.T) { t.Fatal(err) } - if err := vm.mempool.AddTx(tx); err != nil { + if err := vm.mempool.AddRemoteTx(tx); err != nil { t.Fatal(err) } diff --git a/plugin/evm/gossip.go b/plugin/evm/gossip.go index d6b377d13a..16d632bd94 100644 --- a/plugin/evm/gossip.go +++ b/plugin/evm/gossip.go @@ -1,13 +1,15 @@ // Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. +// TODO: move to network + package evm import ( "context" "fmt" "sync" - syncatomic "sync/atomic" + "sync/atomic" "time" ethcommon "github.com/ethereum/go-ethereum/common" @@ -24,7 +26,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" + "github.com/ava-labs/coreth/plugin/evm/config" ) const pendingTxsBuffer = 10 @@ -32,11 +34,9 @@ const pendingTxsBuffer = 10 var ( _ p2p.Handler = (*txGossipHandler)(nil) - _ gossip.Gossipable = (*GossipEthTx)(nil) - _ gossip.Gossipable = (*GossipAtomicTx)(nil) - _ gossip.Marshaller[*GossipAtomicTx] = (*GossipAtomicTxMarshaller)(nil) - _ gossip.Marshaller[*GossipEthTx] = (*GossipEthTxMarshaller)(nil) - _ gossip.Set[*GossipEthTx] = (*GossipEthTxPool)(nil) + _ gossip.Gossipable = (*GossipEthTx)(nil) + _ gossip.Marshaller[*GossipEthTx] = (*GossipEthTxMarshaller)(nil) + _ gossip.Set[*GossipEthTx] = (*GossipEthTxPool)(nil) _ eth.PushGossiper = (*EthPushGossiper)(nil) ) @@ -91,29 +91,14 @@ func (t txGossipHandler) AppRequest(ctx context.Context, nodeID ids.NodeID, dead return t.appRequestHandler.AppRequest(ctx, nodeID, deadline, requestBytes) } -type GossipAtomicTxMarshaller struct{} - -func (g GossipAtomicTxMarshaller) MarshalGossip(tx *GossipAtomicTx) ([]byte, error) { - return tx.Tx.SignedBytes(), nil -} - -func (g GossipAtomicTxMarshaller) UnmarshalGossip(bytes []byte) (*GossipAtomicTx, error) { - tx, err := atomic.ExtractAtomicTx(bytes, atomic.Codec) - return &GossipAtomicTx{ - Tx: tx, - }, err -} - -type GossipAtomicTx struct { - Tx *atomic.Tx -} - -func (tx *GossipAtomicTx) GossipID() ids.ID { - return tx.Tx.ID() -} - func NewGossipEthTxPool(mempool *txpool.TxPool, registerer prometheus.Registerer) (*GossipEthTxPool, error) { - bloom, err := gossip.NewBloomFilter(registerer, "eth_tx_bloom_filter", txGossipBloomMinTargetElements, txGossipBloomTargetFalsePositiveRate, txGossipBloomResetFalsePositiveRate) + bloom, err := gossip.NewBloomFilter( + registerer, + "eth_tx_bloom_filter", + config.TxGossipBloomMinTargetElements, + config.TxGossipBloomTargetFalsePositiveRate, + config.TxGossipBloomResetFalsePositiveRate, + ) if err != nil { return nil, fmt.Errorf("failed to initialize bloom filter: %w", err) } @@ -134,7 +119,7 @@ type GossipEthTxPool struct { // subscribed is set to true when the gossip subscription is active // mostly used for testing - subscribed syncatomic.Bool + subscribed atomic.Bool } // IsSubscribed returns whether or not the gossip subscription is active. @@ -161,7 +146,7 @@ func (g *GossipEthTxPool) Subscribe(ctx context.Context) { return case pendingTxs := <-g.pendingTxs: g.lock.Lock() - optimalElements := (g.mempool.PendingSize(txpool.PendingFilter{}) + len(pendingTxs.Txs)) * txGossipBloomChurnMultiplier + optimalElements := (g.mempool.PendingSize(txpool.PendingFilter{}) + len(pendingTxs.Txs)) * config.TxGossipBloomChurnMultiplier for _, pendingTx := range pendingTxs.Txs { tx := &GossipEthTx{Tx: pendingTx} g.bloom.Add(tx) diff --git a/plugin/evm/gossip_stats.go b/plugin/evm/gossip_stats.go deleted file mode 100644 index 9805c7f1ff..0000000000 --- a/plugin/evm/gossip_stats.go +++ /dev/null @@ -1,67 +0,0 @@ -// (c) 2022, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package evm - -import "github.com/ava-labs/coreth/metrics" - -var _ GossipStats = &gossipStats{} - -// GossipStats contains methods for updating incoming and outgoing gossip stats. -type GossipStats interface { - IncAtomicGossipReceived() - IncEthTxsGossipReceived() - - // new vs. known txs received - IncAtomicGossipReceivedDropped() - IncAtomicGossipReceivedError() - IncAtomicGossipReceivedKnown() - IncAtomicGossipReceivedNew() - IncEthTxsGossipReceivedError() - IncEthTxsGossipReceivedKnown() - IncEthTxsGossipReceivedNew() -} - -// gossipStats implements stats for incoming and outgoing gossip stats. -type gossipStats struct { - // messages - atomicGossipReceived metrics.Counter - ethTxsGossipReceived metrics.Counter - - // new vs. known txs received - atomicGossipReceivedDropped metrics.Counter - atomicGossipReceivedError metrics.Counter - atomicGossipReceivedKnown metrics.Counter - atomicGossipReceivedNew metrics.Counter - ethTxsGossipReceivedError metrics.Counter - ethTxsGossipReceivedKnown metrics.Counter - ethTxsGossipReceivedNew metrics.Counter -} - -func NewGossipStats() GossipStats { - return &gossipStats{ - atomicGossipReceived: metrics.GetOrRegisterCounter("gossip_atomic_received", nil), - ethTxsGossipReceived: metrics.GetOrRegisterCounter("gossip_eth_txs_received", nil), - - atomicGossipReceivedDropped: metrics.GetOrRegisterCounter("gossip_atomic_received_dropped", nil), - atomicGossipReceivedError: metrics.GetOrRegisterCounter("gossip_atomic_received_error", nil), - atomicGossipReceivedKnown: metrics.GetOrRegisterCounter("gossip_atomic_received_known", nil), - atomicGossipReceivedNew: metrics.GetOrRegisterCounter("gossip_atomic_received_new", nil), - ethTxsGossipReceivedError: metrics.GetOrRegisterCounter("gossip_eth_txs_received_error", nil), - ethTxsGossipReceivedKnown: metrics.GetOrRegisterCounter("gossip_eth_txs_received_known", nil), - ethTxsGossipReceivedNew: metrics.GetOrRegisterCounter("gossip_eth_txs_received_new", nil), - } -} - -// incoming messages -func (g *gossipStats) IncAtomicGossipReceived() { g.atomicGossipReceived.Inc(1) } -func (g *gossipStats) IncEthTxsGossipReceived() { g.ethTxsGossipReceived.Inc(1) } - -// new vs. known txs received -func (g *gossipStats) IncAtomicGossipReceivedDropped() { g.atomicGossipReceivedDropped.Inc(1) } -func (g *gossipStats) IncAtomicGossipReceivedError() { g.atomicGossipReceivedError.Inc(1) } -func (g *gossipStats) IncAtomicGossipReceivedKnown() { g.atomicGossipReceivedKnown.Inc(1) } -func (g *gossipStats) IncAtomicGossipReceivedNew() { g.atomicGossipReceivedNew.Inc(1) } -func (g *gossipStats) IncEthTxsGossipReceivedError() { g.ethTxsGossipReceivedError.Inc(1) } -func (g *gossipStats) IncEthTxsGossipReceivedKnown() { g.ethTxsGossipReceivedKnown.Inc(1) } -func (g *gossipStats) IncEthTxsGossipReceivedNew() { g.ethTxsGossipReceivedNew.Inc(1) } diff --git a/plugin/evm/gossip_test.go b/plugin/evm/gossip_test.go index 15ebd15871..84380001dc 100644 --- a/plugin/evm/gossip_test.go +++ b/plugin/evm/gossip_test.go @@ -9,11 +9,7 @@ import ( "testing" "time" - "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/network/p2p/gossip" - "github.com/ava-labs/avalanchego/snow" - "github.com/ava-labs/avalanchego/utils/crypto/secp256k1" - "github.com/ava-labs/avalanchego/vms/components/verify" "github.com/ava-labs/coreth/consensus/dummy" "github.com/ava-labs/coreth/core" "github.com/ava-labs/coreth/core/rawdb" @@ -22,7 +18,6 @@ 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" @@ -30,110 +25,6 @@ import ( "github.com/stretchr/testify/require" ) -func TestGossipAtomicTxMarshaller(t *testing.T) { - require := require.New(t) - - want := &GossipAtomicTx{ - Tx: &atomic.Tx{ - UnsignedAtomicTx: &atomic.UnsignedImportTx{}, - Creds: []verify.Verifiable{}, - }, - } - marshaller := GossipAtomicTxMarshaller{} - - key0 := testKeys[0] - require.NoError(want.Tx.Sign(atomic.Codec, [][]*secp256k1.PrivateKey{{key0}})) - - bytes, err := marshaller.MarshalGossip(want) - require.NoError(err) - - got, err := marshaller.UnmarshalGossip(bytes) - require.NoError(err) - require.Equal(want.GossipID(), got.GossipID()) -} - -func TestAtomicMempoolIterate(t *testing.T) { - txs := []*GossipAtomicTx{ - { - Tx: &atomic.Tx{ - UnsignedAtomicTx: &TestUnsignedTx{ - IDV: ids.GenerateTestID(), - }, - }, - }, - { - Tx: &atomic.Tx{ - UnsignedAtomicTx: &TestUnsignedTx{ - IDV: ids.GenerateTestID(), - }, - }, - }, - } - - tests := []struct { - name string - add []*GossipAtomicTx - f func(tx *GossipAtomicTx) bool - possibleValues []*GossipAtomicTx - expectedLen int - }{ - { - name: "func matches nothing", - add: txs, - f: func(*GossipAtomicTx) bool { - return false - }, - possibleValues: nil, - }, - { - name: "func matches all", - add: txs, - f: func(*GossipAtomicTx) bool { - return true - }, - possibleValues: txs, - expectedLen: 2, - }, - { - name: "func matches subset", - add: txs, - f: func(tx *GossipAtomicTx) bool { - return tx.Tx == txs[0].Tx - }, - possibleValues: txs, - expectedLen: 1, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - require := require.New(t) - m, err := NewMempool(&snow.Context{}, prometheus.NewRegistry(), 10, nil) - require.NoError(err) - - for _, add := range tt.add { - require.NoError(m.Add(add)) - } - - matches := make([]*GossipAtomicTx, 0) - f := func(tx *GossipAtomicTx) bool { - match := tt.f(tx) - - if match { - matches = append(matches, tx) - } - - return match - } - - m.Iterate(f) - - require.Len(matches, tt.expectedLen) - require.Subset(tt.possibleValues, matches) - }) - } -} - func TestGossipEthTxMarshaller(t *testing.T) { require := require.New(t) diff --git a/plugin/evm/gossiper_atomic_gossiping_test.go b/plugin/evm/gossiper_atomic_gossiping_test.go index 0974b50638..33405d2ace 100644 --- a/plugin/evm/gossiper_atomic_gossiping_test.go +++ b/plugin/evm/gossiper_atomic_gossiping_test.go @@ -5,17 +5,21 @@ package evm import ( "context" + "encoding/binary" "sync" "testing" "time" "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/avalanchego/network/p2p" + "github.com/ava-labs/avalanchego/proto/pb/sdk" "github.com/ava-labs/avalanchego/utils/set" "github.com/stretchr/testify/assert" + "google.golang.org/protobuf/proto" commonEng "github.com/ava-labs/avalanchego/snow/engine/common" - "github.com/ava-labs/coreth/plugin/evm/message" + "github.com/ava-labs/coreth/plugin/evm/atomic" ) // show that a txID discovered from gossip is requested to the same node only if @@ -53,14 +57,17 @@ func TestMempoolAtmTxsAppGossipHandling(t *testing.T) { tx, conflictingTx := importTxs[0], importTxs[1] // gossip tx and check it is accepted and gossiped - msg := message.AtomicTxGossip{ - Tx: tx.SignedBytes(), + msg := atomic.GossipAtomicTx{ + Tx: tx, } - msgBytes, err := message.BuildGossipMessage(vm.networkCodec, msg) + marshaller := atomic.GossipAtomicTxMarshaller{} + txBytes, err := marshaller.MarshalGossip(&msg) assert.NoError(err) - vm.ctx.Lock.Unlock() + msgBytes, err := buildAtomicPushGossip(txBytes) + assert.NoError(err) + // show that no txID is requested assert.NoError(vm.AppGossip(context.Background(), nodeID, msgBytes)) time.Sleep(500 * time.Millisecond) @@ -85,14 +92,17 @@ func TestMempoolAtmTxsAppGossipHandling(t *testing.T) { txGossipedLock.Unlock() // show that conflicting tx is not added to mempool - msg = message.AtomicTxGossip{ - Tx: conflictingTx.SignedBytes(), + msg = atomic.GossipAtomicTx{ + Tx: conflictingTx, } - msgBytes, err = message.BuildGossipMessage(vm.networkCodec, msg) + marshaller = atomic.GossipAtomicTxMarshaller{} + txBytes, err = marshaller.MarshalGossip(&msg) assert.NoError(err) vm.ctx.Lock.Unlock() + msgBytes, err = buildAtomicPushGossip(txBytes) + assert.NoError(err) assert.NoError(vm.AppGossip(context.Background(), nodeID, msgBytes)) vm.ctx.Lock.Lock() @@ -137,7 +147,7 @@ func TestMempoolAtmTxsAppGossipHandlingDiscardedTx(t *testing.T) { tx, conflictingTx := importTxs[0], importTxs[1] txID := tx.ID() - mempool.AddTx(tx) + mempool.AddRemoteTx(tx) mempool.NextTx() mempool.DiscardCurrentTx(txID) @@ -147,14 +157,17 @@ func TestMempoolAtmTxsAppGossipHandlingDiscardedTx(t *testing.T) { // Gossip the transaction to the VM and ensure that it is not added to the mempool // and is not re-gossipped. nodeID := ids.GenerateTestNodeID() - msg := message.AtomicTxGossip{ - Tx: tx.SignedBytes(), + msg := atomic.GossipAtomicTx{ + Tx: tx, } - msgBytes, err := message.BuildGossipMessage(vm.networkCodec, msg) + marshaller := atomic.GossipAtomicTxMarshaller{} + txBytes, err := marshaller.MarshalGossip(&msg) assert.NoError(err) vm.ctx.Lock.Unlock() + msgBytes, err := buildAtomicPushGossip(txBytes) + assert.NoError(err) assert.NoError(vm.AppGossip(context.Background(), nodeID, msgBytes)) vm.ctx.Lock.Lock() @@ -171,8 +184,8 @@ func TestMempoolAtmTxsAppGossipHandlingDiscardedTx(t *testing.T) { // Conflicting tx must be submitted over the API to be included in push gossip. // (i.e., txs received via p2p are not included in push gossip) // This test adds it directly to the mempool + gossiper to simulate that. - vm.mempool.AddTx(conflictingTx) - vm.atomicTxPushGossiper.Add(&GossipAtomicTx{conflictingTx}) + vm.mempool.AddRemoteTx(conflictingTx) + vm.atomicTxPushGossiper.Add(&atomic.GossipAtomicTx{conflictingTx}) time.Sleep(500 * time.Millisecond) vm.ctx.Lock.Lock() @@ -185,3 +198,16 @@ func TestMempoolAtmTxsAppGossipHandlingDiscardedTx(t *testing.T) { assert.False(mempool.Has(txID)) assert.True(mempool.Has(conflictingTx.ID())) } + +func buildAtomicPushGossip(txBytes []byte) ([]byte, error) { + inboundGossip := &sdk.PushGossip{ + Gossip: [][]byte{txBytes}, + } + inboundGossipBytes, err := proto.Marshal(inboundGossip) + if err != nil { + return nil, err + } + + inboundGossipMsg := append(binary.AppendUvarint(nil, p2p.AtomicTxGossipHandlerID), inboundGossipBytes...) + return inboundGossipMsg, nil +} diff --git a/plugin/evm/handler.go b/plugin/evm/handler.go deleted file mode 100644 index c4b41a85e7..0000000000 --- a/plugin/evm/handler.go +++ /dev/null @@ -1,140 +0,0 @@ -// (c) 2019-2021, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package evm - -import ( - "github.com/ava-labs/avalanchego/ids" - - "github.com/ethereum/go-ethereum/log" - "github.com/ethereum/go-ethereum/rlp" - - "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" -) - -// GossipHandler handles incoming gossip messages -type GossipHandler struct { - vm *VM - atomicMempool *Mempool - txPool *txpool.TxPool - stats GossipStats -} - -func NewGossipHandler(vm *VM, stats GossipStats) *GossipHandler { - return &GossipHandler{ - vm: vm, - atomicMempool: vm.mempool, - txPool: vm.txPool, - stats: stats, - } -} - -func (h *GossipHandler) HandleAtomicTx(nodeID ids.NodeID, msg message.AtomicTxGossip) error { - log.Trace( - "AppGossip called with AtomicTxGossip", - "peerID", nodeID, - ) - - if len(msg.Tx) == 0 { - log.Trace( - "AppGossip received empty AtomicTxGossip Message", - "peerID", nodeID, - ) - return nil - } - - // In the case that the gossip message contains a transaction, - // attempt to parse it and add it as a remote. - 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 := atomic.Codec.Marshal(atomic.CodecVersion, &tx.UnsignedAtomicTx) - if err != nil { - log.Trace( - "AppGossip failed to marshal unsigned tx", - "err", err, - ) - return nil - } - tx.Initialize(unsignedBytes, msg.Tx) - - txID := tx.ID() - h.stats.IncAtomicGossipReceived() - if _, dropped, found := h.atomicMempool.GetTx(txID); found { - h.stats.IncAtomicGossipReceivedKnown() - return nil - } else if dropped { - h.stats.IncAtomicGossipReceivedDropped() - return nil - } - - h.stats.IncAtomicGossipReceivedNew() - - h.vm.ctx.Lock.RLock() - defer h.vm.ctx.Lock.RUnlock() - - if err := h.vm.mempool.AddTx(&tx); err != nil { - log.Trace( - "AppGossip provided invalid transaction", - "peerID", nodeID, - "err", err, - ) - h.stats.IncAtomicGossipReceivedError() - } - - return nil -} - -func (h *GossipHandler) HandleEthTxs(nodeID ids.NodeID, msg message.EthTxsGossip) error { - log.Trace( - "AppGossip called with EthTxsGossip", - "peerID", nodeID, - "size(txs)", len(msg.Txs), - ) - - if len(msg.Txs) == 0 { - log.Trace( - "AppGossip received empty EthTxsGossip Message", - "peerID", nodeID, - ) - return nil - } - - // The maximum size of this encoded object is enforced by the codec. - txs := make([]*types.Transaction, 0) - if err := rlp.DecodeBytes(msg.Txs, &txs); err != nil { - log.Trace( - "AppGossip provided invalid txs", - "peerID", nodeID, - "err", err, - ) - return nil - } - h.stats.IncEthTxsGossipReceived() - errs := h.txPool.Add(txs, false, false) - for i, err := range errs { - if err != nil { - log.Trace( - "AppGossip failed to add to mempool", - "err", err, - "tx", txs[i].Hash(), - ) - if err == txpool.ErrAlreadyKnown { - h.stats.IncEthTxsGossipReceivedKnown() - } else { - h.stats.IncEthTxsGossipReceivedError() - } - continue - } - h.stats.IncEthTxsGossipReceivedNew() - } - return nil -} diff --git a/plugin/evm/mempool_atomic_gossiping_test.go b/plugin/evm/mempool_atomic_gossiping_test.go index 3e22fef486..f35d2749f1 100644 --- a/plugin/evm/mempool_atomic_gossiping_test.go +++ b/plugin/evm/mempool_atomic_gossiping_test.go @@ -9,9 +9,11 @@ import ( "testing" "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/avalanchego/snow" "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/prometheus/client_golang/prometheus" "github.com/stretchr/testify/assert" ) @@ -53,7 +55,7 @@ func TestMempoolAddLocallyCreateAtomicTx(t *testing.T) { // try to add a conflicting tx err = vm.mempool.AddLocalTx(conflictingTx) - assert.ErrorIs(err, errConflictingAtomicTx) + assert.ErrorIs(err, atomic.ErrConflictingAtomicTx) has = mempool.Has(conflictingTxID) assert.False(has, "conflicting tx in mempool") @@ -92,27 +94,22 @@ func TestMempoolAddLocallyCreateAtomicTx(t *testing.T) { func TestMempoolMaxMempoolSizeHandling(t *testing.T) { assert := assert.New(t) - _, vm, _, sharedMemory, _ := GenesisVM(t, true, "", "", "") - defer func() { - err := vm.Shutdown(context.Background()) - assert.NoError(err) - }() - mempool := vm.mempool - + mempool, err := atomic.NewMempool(&snow.Context{}, prometheus.NewRegistry(), 1, nil) + assert.NoError(err) // create candidate tx (we will drop before validation) - tx := createImportTxOptions(t, vm, sharedMemory)[0] + tx := atomic.GenerateTestImportTx() - // shortcut to simulated almost filled mempool - mempool.maxSize = 0 - - assert.ErrorIs(mempool.AddTx(tx), errTooManyAtomicTx) - assert.False(mempool.Has(tx.ID())) - - // shortcut to simulated empty mempool - mempool.maxSize = defaultMempoolSize - - assert.NoError(mempool.AddTx(tx)) + assert.NoError(mempool.AddRemoteTx(tx)) assert.True(mempool.Has(tx.ID())) + // promote tx to be issued + _, ok := mempool.NextTx() + assert.True(ok) + mempool.IssueCurrentTxs() + + // try to add one more tx + tx2 := atomic.GenerateTestImportTx() + assert.ErrorIs(mempool.AddRemoteTx(tx2), atomic.ErrTooManyAtomicTx) + assert.False(mempool.Has(tx2.ID())) } // mempool will drop transaction with the lowest fee @@ -129,21 +126,20 @@ func TestMempoolPriorityDrop(t *testing.T) { err := vm.Shutdown(context.Background()) assert.NoError(err) }() - mempool := vm.mempool - mempool.maxSize = 1 + mempool, err := atomic.NewMempool(vm.ctx, prometheus.NewRegistry(), 1, vm.verifyTxAtTip) tx1, err := vm.newImportTx(vm.ctx.XChainID, testEthAddrs[0], initialBaseFee, []*secp256k1.PrivateKey{testKeys[0]}) if err != nil { t.Fatal(err) } - assert.NoError(mempool.AddTx(tx1)) + assert.NoError(mempool.AddRemoteTx(tx1)) assert.True(mempool.Has(tx1.ID())) tx2, err := vm.newImportTx(vm.ctx.XChainID, testEthAddrs[1], initialBaseFee, []*secp256k1.PrivateKey{testKeys[1]}) if err != nil { t.Fatal(err) } - assert.ErrorIs(mempool.AddTx(tx2), errInsufficientAtomicTxFee) + assert.ErrorIs(mempool.AddRemoteTx(tx2), atomic.ErrInsufficientAtomicTxFee) assert.True(mempool.Has(tx1.ID())) assert.False(mempool.Has(tx2.ID())) @@ -151,7 +147,7 @@ func TestMempoolPriorityDrop(t *testing.T) { if err != nil { t.Fatal(err) } - assert.NoError(mempool.AddTx(tx3)) + assert.NoError(mempool.AddRemoteTx(tx3)) assert.False(mempool.Has(tx1.ID())) assert.False(mempool.Has(tx2.ID())) assert.True(mempool.Has(tx3.ID())) diff --git a/plugin/evm/message/codec.go b/plugin/evm/message/codec.go index de3603b9c2..664c9252bb 100644 --- a/plugin/evm/message/codec.go +++ b/plugin/evm/message/codec.go @@ -15,20 +15,16 @@ const ( maxMessageSize = 2*units.MiB - 64*units.KiB // Subtract 64 KiB from p2p network cap to leave room for encoding overhead from AvalancheGo ) -var ( - Codec codec.Manager -) +var Codec codec.Manager func init() { Codec = codec.NewManager(maxMessageSize) c := linearcodec.NewDefault() errs := wrappers.Errs{} + // Gossip types removed from codec + c.SkipRegistrations(2) errs.Add( - // Gossip types - c.RegisterType(AtomicTxGossip{}), - c.RegisterType(EthTxsGossip{}), - // Types for state sync frontier consensus c.RegisterType(SyncSummary{}), diff --git a/plugin/evm/message/handler.go b/plugin/evm/message/handler.go index 9b94828509..1b910e3826 100644 --- a/plugin/evm/message/handler.go +++ b/plugin/evm/message/handler.go @@ -6,33 +6,10 @@ package message import ( "context" - "github.com/ethereum/go-ethereum/log" - "github.com/ava-labs/avalanchego/ids" ) -var ( - _ GossipHandler = NoopMempoolGossipHandler{} - _ RequestHandler = NoopRequestHandler{} -) - -// GossipHandler handles incoming gossip messages -type GossipHandler interface { - HandleAtomicTx(nodeID ids.NodeID, msg AtomicTxGossip) error - HandleEthTxs(nodeID ids.NodeID, msg EthTxsGossip) error -} - -type NoopMempoolGossipHandler struct{} - -func (NoopMempoolGossipHandler) HandleAtomicTx(nodeID ids.NodeID, msg AtomicTxGossip) error { - log.Debug("dropping unexpected AtomicTxGossip message", "peerID", nodeID) - return nil -} - -func (NoopMempoolGossipHandler) HandleEthTxs(nodeID ids.NodeID, msg EthTxsGossip) error { - log.Debug("dropping unexpected EthTxsGossip message", "peerID", nodeID) - return nil -} +var _ RequestHandler = NoopRequestHandler{} // RequestHandler interface handles incoming requests from peers // Must have methods in format of handleType(context.Context, ids.NodeID, uint32, request Type) error diff --git a/plugin/evm/message/handler_test.go b/plugin/evm/message/handler_test.go deleted file mode 100644 index a27b1f9d4f..0000000000 --- a/plugin/evm/message/handler_test.go +++ /dev/null @@ -1,62 +0,0 @@ -// (c) 2019-2021, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package message - -import ( - "testing" - - "github.com/ava-labs/avalanchego/ids" - - "github.com/stretchr/testify/assert" -) - -type CounterHandler struct { - AtomicTx, EthTxs int -} - -func (h *CounterHandler) HandleAtomicTx(ids.NodeID, AtomicTxGossip) error { - h.AtomicTx++ - return nil -} - -func (h *CounterHandler) HandleEthTxs(ids.NodeID, EthTxsGossip) error { - h.EthTxs++ - return nil -} - -func TestHandleAtomicTx(t *testing.T) { - assert := assert.New(t) - - handler := CounterHandler{} - msg := AtomicTxGossip{} - - err := msg.Handle(&handler, ids.EmptyNodeID) - assert.NoError(err) - assert.Equal(1, handler.AtomicTx) - assert.Zero(handler.EthTxs) -} - -func TestHandleEthTxs(t *testing.T) { - assert := assert.New(t) - - handler := CounterHandler{} - msg := EthTxsGossip{} - - err := msg.Handle(&handler, ids.EmptyNodeID) - assert.NoError(err) - assert.Zero(handler.AtomicTx) - assert.Equal(1, handler.EthTxs) -} - -func TestNoopHandler(t *testing.T) { - assert := assert.New(t) - - handler := NoopMempoolGossipHandler{} - - err := handler.HandleEthTxs(ids.EmptyNodeID, EthTxsGossip{}) - assert.NoError(err) - - err = handler.HandleAtomicTx(ids.EmptyNodeID, AtomicTxGossip{}) - assert.NoError(err) -} diff --git a/plugin/evm/message/message.go b/plugin/evm/message/message.go deleted file mode 100644 index c8c80a0343..0000000000 --- a/plugin/evm/message/message.go +++ /dev/null @@ -1,78 +0,0 @@ -// (c) 2019-2021, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package message - -import ( - "errors" - "fmt" - - "github.com/ava-labs/avalanchego/codec" - - "github.com/ava-labs/avalanchego/ids" - "github.com/ava-labs/avalanchego/utils/units" -) - -const ( - // EthMsgSoftCapSize is the ideal size of encoded transaction bytes we send in - // any [EthTxsGossip] or [AtomicTxGossip] message. We do not limit inbound messages to - // this size, however. Max inbound message size is enforced by the codec - // (512KB). - EthMsgSoftCapSize = 64 * units.KiB -) - -var ( - _ GossipMessage = AtomicTxGossip{} - _ GossipMessage = EthTxsGossip{} - - errUnexpectedCodecVersion = errors.New("unexpected codec version") -) - -type GossipMessage interface { - // types implementing GossipMessage should also implement fmt.Stringer for logging purposes. - fmt.Stringer - - // Handle this gossip message with the gossip handler. - Handle(handler GossipHandler, nodeID ids.NodeID) error -} - -type AtomicTxGossip struct { - Tx []byte `serialize:"true"` -} - -func (msg AtomicTxGossip) Handle(handler GossipHandler, nodeID ids.NodeID) error { - return handler.HandleAtomicTx(nodeID, msg) -} - -func (msg AtomicTxGossip) String() string { - return fmt.Sprintf("AtomicTxGossip(Len=%d)", len(msg.Tx)) -} - -type EthTxsGossip struct { - Txs []byte `serialize:"true"` -} - -func (msg EthTxsGossip) Handle(handler GossipHandler, nodeID ids.NodeID) error { - return handler.HandleEthTxs(nodeID, msg) -} - -func (msg EthTxsGossip) String() string { - return fmt.Sprintf("EthTxsGossip(Len=%d)", len(msg.Txs)) -} - -func ParseGossipMessage(codec codec.Manager, bytes []byte) (GossipMessage, error) { - var msg GossipMessage - version, err := codec.Unmarshal(bytes, &msg) - if err != nil { - return nil, err - } - if version != Version { - return nil, errUnexpectedCodecVersion - } - return msg, nil -} - -func BuildGossipMessage(codec codec.Manager, msg GossipMessage) ([]byte, error) { - bytes, err := codec.Marshal(Version, &msg) - return bytes, err -} diff --git a/plugin/evm/message/message_test.go b/plugin/evm/message/message_test.go deleted file mode 100644 index dbcdea2d75..0000000000 --- a/plugin/evm/message/message_test.go +++ /dev/null @@ -1,78 +0,0 @@ -// (c) 2019-2021, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package message - -import ( - "encoding/base64" - "testing" - - "github.com/ava-labs/avalanchego/utils" - "github.com/ava-labs/avalanchego/utils/units" - - "github.com/stretchr/testify/assert" -) - -// TestMarshalAtomicTx asserts that the structure or serialization logic hasn't changed, primarily to -// ensure compatibility with the network. -func TestMarshalAtomicTx(t *testing.T) { - assert := assert.New(t) - - base64AtomicTxGossip := "AAAAAAAAAAAABGJsYWg=" - msg := []byte("blah") - builtMsg := AtomicTxGossip{ - Tx: msg, - } - builtMsgBytes, err := BuildGossipMessage(Codec, builtMsg) - assert.NoError(err) - assert.Equal(base64AtomicTxGossip, base64.StdEncoding.EncodeToString(builtMsgBytes)) - - parsedMsgIntf, err := ParseGossipMessage(Codec, builtMsgBytes) - assert.NoError(err) - - parsedMsg, ok := parsedMsgIntf.(AtomicTxGossip) - assert.True(ok) - - assert.Equal(msg, parsedMsg.Tx) -} - -// TestMarshalEthTxs asserts that the structure or serialization logic hasn't changed, primarily to -// ensure compatibility with the network. -func TestMarshalEthTxs(t *testing.T) { - assert := assert.New(t) - - base64EthTxGossip := "AAAAAAABAAAABGJsYWg=" - msg := []byte("blah") - builtMsg := EthTxsGossip{ - Txs: msg, - } - builtMsgBytes, err := BuildGossipMessage(Codec, builtMsg) - assert.NoError(err) - assert.Equal(base64EthTxGossip, base64.StdEncoding.EncodeToString(builtMsgBytes)) - - parsedMsgIntf, err := ParseGossipMessage(Codec, builtMsgBytes) - assert.NoError(err) - - parsedMsg, ok := parsedMsgIntf.(EthTxsGossip) - assert.True(ok) - - assert.Equal(msg, parsedMsg.Txs) -} - -func TestEthTxsTooLarge(t *testing.T) { - assert := assert.New(t) - - builtMsg := EthTxsGossip{ - Txs: utils.RandomBytes(maxMessageSize), - } - _, err := BuildGossipMessage(Codec, builtMsg) - assert.Error(err) -} - -func TestParseGibberish(t *testing.T) { - assert := assert.New(t) - - randomBytes := utils.RandomBytes(256 * units.KiB) - _, err := ParseGossipMessage(Codec, randomBytes) - assert.Error(err) -} diff --git a/plugin/evm/service.go b/plugin/evm/service.go index 59fddb1ea4..c53a1da4c1 100644 --- a/plugin/evm/service.go +++ b/plugin/evm/service.go @@ -196,7 +196,7 @@ func (service *AvaxAPI) Import(_ *http.Request, args *client.ImportArgs, respons if err := service.vm.mempool.AddLocalTx(tx); err != nil { return err } - service.vm.atomicTxPushGossiper.Add(&GossipAtomicTx{tx}) + service.vm.atomicTxPushGossiper.Add(&atomic.GossipAtomicTx{tx}) return nil } @@ -280,7 +280,7 @@ func (service *AvaxAPI) Export(_ *http.Request, args *client.ExportArgs, respons if err := service.vm.mempool.AddLocalTx(tx); err != nil { return err } - service.vm.atomicTxPushGossiper.Add(&GossipAtomicTx{tx}) + service.vm.atomicTxPushGossiper.Add(&atomic.GossipAtomicTx{tx}) return nil } @@ -390,7 +390,7 @@ func (service *AvaxAPI) IssueTx(r *http.Request, args *api.FormattedTx, response if err := service.vm.mempool.AddLocalTx(tx); err != nil { return err } - service.vm.atomicTxPushGossiper.Add(&GossipAtomicTx{tx}) + service.vm.atomicTxPushGossiper.Add(&atomic.GossipAtomicTx{tx}) return nil } diff --git a/plugin/evm/tx_gossip_test.go b/plugin/evm/tx_gossip_test.go index fb1d7388d9..ad9f4667a8 100644 --- a/plugin/evm/tx_gossip_test.go +++ b/plugin/evm/tx_gossip_test.go @@ -36,6 +36,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/plugin/evm/config" "github.com/ava-labs/coreth/utils" ) @@ -104,7 +105,13 @@ func TestEthTxGossip(t *testing.T) { } // Ask the VM for any new transactions. We should get nothing at first. - emptyBloomFilter, err := gossip.NewBloomFilter(prometheus.NewRegistry(), "", txGossipBloomMinTargetElements, txGossipBloomTargetFalsePositiveRate, txGossipBloomResetFalsePositiveRate) + emptyBloomFilter, err := gossip.NewBloomFilter( + prometheus.NewRegistry(), + "", + config.TxGossipBloomMinTargetElements, + config.TxGossipBloomTargetFalsePositiveRate, + config.TxGossipBloomResetFalsePositiveRate, + ) require.NoError(err) emptyBloomFilterBytes, _ := emptyBloomFilter.Marshal() request := &sdk.PullGossipRequest{ @@ -231,7 +238,13 @@ func TestAtomicTxGossip(t *testing.T) { } // Ask the VM for any new transactions. We should get nothing at first. - emptyBloomFilter, err := gossip.NewBloomFilter(prometheus.NewRegistry(), "", txGossipBloomMinTargetElements, txGossipBloomTargetFalsePositiveRate, txGossipBloomResetFalsePositiveRate) + emptyBloomFilter, err := gossip.NewBloomFilter( + prometheus.NewRegistry(), + "", + config.TxGossipBloomMinTargetElements, + config.TxGossipBloomTargetFalsePositiveRate, + config.TxGossipBloomResetFalsePositiveRate, + ) require.NoError(err) emptyBloomFilterBytes, _ := emptyBloomFilter.Marshal() request := &sdk.PullGossipRequest{ @@ -278,7 +291,7 @@ func TestAtomicTxGossip(t *testing.T) { // Ask the VM for new transactions. We should get the newly issued tx. wg.Add(1) - marshaller := GossipAtomicTxMarshaller{} + marshaller := atomic.GossipAtomicTxMarshaller{} onResponse = func(_ context.Context, nodeID ids.NodeID, responseBytes []byte, err error) { require.NoError(err) @@ -479,7 +492,7 @@ func TestAtomicTxPushGossipOutbound(t *testing.T) { 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}) + vm.atomicTxPushGossiper.Add(&atomic.GossipAtomicTx{tx}) gossipedBytes := <-sender.SentAppGossip require.Equal(byte(p2p.AtomicTxGossipHandlerID), gossipedBytes[0]) @@ -488,7 +501,7 @@ func TestAtomicTxPushGossipOutbound(t *testing.T) { require.NoError(proto.Unmarshal(gossipedBytes[1:], outboundGossipMsg)) require.Len(outboundGossipMsg.Gossip, 1) - marshaller := GossipAtomicTxMarshaller{} + marshaller := atomic.GossipAtomicTxMarshaller{} gossipedTx, err := marshaller.UnmarshalGossip(outboundGossipMsg.Gossip[0]) require.NoError(err) require.Equal(tx.ID(), gossipedTx.Tx.ID()) @@ -551,8 +564,8 @@ func TestAtomicTxPushGossipInbound(t *testing.T) { require.NoError(err) require.NoError(vm.mempool.AddLocalTx(tx)) - marshaller := GossipAtomicTxMarshaller{} - gossipedTx := &GossipAtomicTx{ + marshaller := atomic.GossipAtomicTxMarshaller{} + gossipedTx := &atomic.GossipAtomicTx{ Tx: tx, } gossipBytes, err := marshaller.MarshalGossip(gossipedTx) diff --git a/plugin/evm/vm.go b/plugin/evm/vm.go index 4adab936bc..c1b9426bea 100644 --- a/plugin/evm/vm.go +++ b/plugin/evm/vm.go @@ -41,6 +41,7 @@ import ( "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/config" "github.com/ava-labs/coreth/plugin/evm/message" "github.com/ava-labs/coreth/triedb" "github.com/ava-labs/coreth/triedb/hashdb" @@ -129,16 +130,12 @@ const ( targetAtomicTxsSize = 40 * units.KiB // gossip constants - pushGossipDiscardedElements = 16_384 - txGossipBloomMinTargetElements = 8 * 1024 - txGossipBloomTargetFalsePositiveRate = 0.01 - txGossipBloomResetFalsePositiveRate = 0.05 - txGossipBloomChurnMultiplier = 3 - txGossipTargetMessageSize = 20 * units.KiB - maxValidatorSetStaleness = time.Minute - txGossipThrottlingPeriod = 10 * time.Second - txGossipThrottlingLimit = 2 - txGossipPollSize = 1 + pushGossipDiscardedElements = 16_384 + txGossipTargetMessageSize = 20 * units.KiB + maxValidatorSetStaleness = time.Minute + txGossipThrottlingPeriod = 10 * time.Second + txGossipThrottlingLimit = 2 + txGossipPollSize = 1 ) // Define the API endpoints for the VM @@ -168,15 +165,12 @@ var ( errEmptyBlock = errors.New("empty block") errUnsupportedFXs = errors.New("unsupported feature extensions") errInvalidBlock = errors.New("invalid block") - errInsufficientAtomicTxFee = errors.New("atomic tx fee too low for atomic mempool") errInvalidNonce = errors.New("invalid nonce") errUnclesUnsupported = errors.New("uncles unsupported") errRejectedParent = errors.New("rejected parent") 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") errInvalidHeaderPredicateResults = errors.New("invalid header predicate results") ) @@ -217,7 +211,7 @@ type VM struct { // with an efficient caching layer. *chain.State - config Config + config config.Config chainID *big.Int networkID uint64 @@ -265,7 +259,7 @@ type VM struct { baseCodec codec.Registry clock mockable.Clock - mempool *Mempool + mempool *atomic.Mempool shutdownChan chan struct{} shutdownWg sync.WaitGroup @@ -303,7 +297,7 @@ type VM struct { ethTxPushGossiper avalancheUtils.Atomic[*gossip.PushGossiper[*GossipEthTx]] ethTxPullGossiper gossip.Gossiper atomicTxGossipHandler p2p.Handler - atomicTxPushGossiper *gossip.PushGossiper[*GossipAtomicTx] + atomicTxPushGossiper *gossip.PushGossiper[*atomic.GossipAtomicTx] atomicTxPullGossiper gossip.Gossiper chainAlias string @@ -349,7 +343,9 @@ func (vm *VM) Initialize( return fmt.Errorf("failed to unmarshal config %s: %w", string(configBytes), err) } } - if err := vm.config.Validate(); err != nil { + vm.ctx = chainCtx + + if err := vm.config.Validate(vm.ctx.NetworkID); err != nil { return err } // We should deprecate config flags as the first thing, before we do anything else @@ -357,8 +353,6 @@ func (vm *VM) Initialize( // initialized the logger. deprecateMsg := vm.config.Deprecate() - vm.ctx = chainCtx - // Create logger alias, err := vm.ctx.BCLookup.PrimaryAlias(vm.ctx.ChainID) if err != nil { @@ -452,16 +446,6 @@ func (vm *VM) Initialize( } vm.syntacticBlockValidator = NewBlockValidator(extDataHashes) - // Ensure that non-standard commit interval is not allowed for production networks - if avalanchegoConstants.ProductionNetworkIDs.Contains(chainCtx.NetworkID) { - if vm.config.CommitInterval != defaultCommitInterval { - return fmt.Errorf("cannot start non-local network with commit interval %d", vm.config.CommitInterval) - } - if vm.config.StateSyncCommitInterval != defaultSyncableCommitInterval { - return fmt.Errorf("cannot start non-local network with syncable interval %d", vm.config.StateSyncCommitInterval) - } - } - // Free the memory of the extDataHash map that is not used (i.e. if mainnet // config, free fuji) fujiExtDataHashes = nil @@ -543,7 +527,7 @@ func (vm *VM) Initialize( } // TODO: read size from settings - vm.mempool, err = NewMempool(chainCtx, vm.sdkMetrics, defaultMempoolSize, vm.verifyTxAtTip) + vm.mempool, err = atomic.NewMempool(chainCtx, vm.sdkMetrics, defaultMempoolSize, vm.verifyTxAtTip) if err != nil { return fmt.Errorf("failed to initialize mempool: %w", err) } @@ -553,6 +537,7 @@ func (vm *VM) Initialize( vm.p2pSender = appSender } + // TODO: move all network stuff to peer.NewNetwork p2pNetwork, err := p2p.NewNetwork(vm.ctx.Log, vm.p2pSender, vm.sdkMetrics, "p2p") if err != nil { return fmt.Errorf("failed to initialize p2p network: %w", err) @@ -1106,7 +1091,7 @@ func (vm *VM) initBlockBuilding() error { vm.shutdownWg.Done() }() - atomicTxGossipMarshaller := GossipAtomicTxMarshaller{} + atomicTxGossipMarshaller := atomic.GossipAtomicTxMarshaller{} atomicTxGossipClient := vm.Network.NewClient(p2p.AtomicTxGossipHandlerID, p2p.WithValidatorSampling(vm.validators)) atomicTxGossipMetrics, err := gossip.NewMetrics(vm.sdkMetrics, atomicTxGossipNamespace) if err != nil { @@ -1144,7 +1129,7 @@ func (vm *VM) initBlockBuilding() error { } if vm.atomicTxPushGossiper == nil { - vm.atomicTxPushGossiper, err = gossip.NewPushGossiper[*GossipAtomicTx]( + vm.atomicTxPushGossiper, err = gossip.NewPushGossiper[*atomic.GossipAtomicTx]( atomicTxGossipMarshaller, vm.mempool, vm.validators, @@ -1162,10 +1147,8 @@ func (vm *VM) initBlockBuilding() error { } // NOTE: gossip network must be initialized first otherwise ETH tx gossip will not work. - gossipStats := NewGossipStats() vm.builder = vm.NewBlockBuilder(vm.toEngine) vm.builder.awaitSubmittedTxs() - vm.Network.SetGossipHandler(NewGossipHandler(vm, gossipStats)) if vm.ethTxGossipHandler == nil { vm.ethTxGossipHandler = newTxGossipHandler[*GossipEthTx]( @@ -1185,7 +1168,7 @@ func (vm *VM) initBlockBuilding() error { } if vm.atomicTxGossipHandler == nil { - vm.atomicTxGossipHandler = newTxGossipHandler[*GossipAtomicTx]( + vm.atomicTxGossipHandler = newTxGossipHandler[*atomic.GossipAtomicTx]( vm.ctx.Log, atomicTxGossipMarshaller, vm.mempool, @@ -1229,7 +1212,7 @@ func (vm *VM) initBlockBuilding() error { }() if vm.atomicTxPullGossiper == nil { - atomicTxPullGossiper := gossip.NewPullGossiper[*GossipAtomicTx]( + atomicTxPullGossiper := gossip.NewPullGossiper[*atomic.GossipAtomicTx]( vm.ctx.Log, atomicTxGossipMarshaller, vm.mempool, diff --git a/plugin/evm/vm_test.go b/plugin/evm/vm_test.go index cc9c7d6f7b..4ec59de0bc 100644 --- a/plugin/evm/vm_test.go +++ b/plugin/evm/vm_test.go @@ -25,6 +25,7 @@ import ( "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/plugin/evm/config" "github.com/ava-labs/coreth/trie" "github.com/ava-labs/coreth/utils" @@ -406,7 +407,7 @@ func TestVMConfigDefaults(t *testing.T) { configJSON := fmt.Sprintf(`{"rpc-tx-fee-cap": %g,"eth-apis": %s}`, txFeeCap, fmt.Sprintf("[%q]", enabledEthAPIs[0])) _, vm, _, _, _ := GenesisVM(t, false, "", configJSON, "") - var vmConfig Config + var vmConfig config.Config vmConfig.SetDefaults() vmConfig.RPCTxFeeCap = txFeeCap vmConfig.EnabledEthAPIs = enabledEthAPIs @@ -418,7 +419,7 @@ func TestVMNilConfig(t *testing.T) { _, vm, _, _, _ := GenesisVM(t, false, "", "", "") // VM Config should match defaults if no config is passed in - var vmConfig Config + var vmConfig config.Config vmConfig.SetDefaults() require.Equal(t, vmConfig, vm.config, "VM Config should match default config") require.NoError(t, vm.Shutdown(context.Background())) @@ -1100,8 +1101,8 @@ func TestReissueAtomicTxHigherGasPrice(t *testing.T) { t.Fatal(err) } - if err := vm.mempool.AddLocalTx(reissuanceTx1); !errors.Is(err, errConflictingAtomicTx) { - t.Fatalf("Expected to fail with err: %s, but found err: %s", errConflictingAtomicTx, err) + if err := vm.mempool.AddLocalTx(reissuanceTx1); !errors.Is(err, atomic.ErrConflictingAtomicTx) { + t.Fatalf("Expected to fail with err: %s, but found err: %s", atomic.ErrConflictingAtomicTx, err) } assert.True(t, vm.mempool.Has(importTx1.ID())) @@ -1124,12 +1125,12 @@ func TestReissueAtomicTxHigherGasPrice(t *testing.T) { issuedTxs, evictedTxs := issueTxs(t, vm, sharedMemory) for i, tx := range issuedTxs { - _, issued := vm.mempool.txHeap.Get(tx.ID()) + _, issued := vm.mempool.GetPendingTx(tx.ID()) assert.True(t, issued, "expected issued tx at index %d to be issued", i) } for i, tx := range evictedTxs { - _, discarded := vm.mempool.discardedTxs.Get(tx.ID()) + _, discarded, _ := vm.mempool.GetTx(tx.ID()) assert.True(t, discarded, "expected discarded tx at index %d to be discarded", i) } }) @@ -3045,7 +3046,7 @@ func TestAtomicTxFailsEVMStateTransferBuildBlock(t *testing.T) { t.Fatal("Should have failed to issue due to an invalid export tx") } - if err := vm.mempool.AddTx(exportTx2); err == nil { + if err := vm.mempool.AddRemoteTx(exportTx2); err == nil { t.Fatal("Should have failed to add because conflicting") } @@ -3582,8 +3583,8 @@ func TestConsecutiveAtomicTransactionsRevertSnapshot(t *testing.T) { // Add the two conflicting transactions directly to the mempool, so that two consecutive transactions // will fail verification when build block is called. - vm.mempool.AddTx(importTxs[1]) - vm.mempool.AddTx(importTxs[2]) + vm.mempool.AddRemoteTx(importTxs[1]) + vm.mempool.AddRemoteTx(importTxs[2]) if _, err := vm.BuildBlock(context.Background()); err == nil { t.Fatal("Expected build block to fail due to empty block") From 019bb048a552b8c212bef7f74ec0e9db2e825c0d Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Sat, 14 Dec 2024 23:33:25 +0300 Subject: [PATCH 05/27] lint --- plugin/evm/gossiper_atomic_gossiping_test.go | 2 +- plugin/evm/mempool_atomic_gossiping_test.go | 1 + plugin/evm/service.go | 6 +++--- plugin/evm/tx_gossip_test.go | 2 +- 4 files changed, 6 insertions(+), 5 deletions(-) diff --git a/plugin/evm/gossiper_atomic_gossiping_test.go b/plugin/evm/gossiper_atomic_gossiping_test.go index 33405d2ace..e2c97167f8 100644 --- a/plugin/evm/gossiper_atomic_gossiping_test.go +++ b/plugin/evm/gossiper_atomic_gossiping_test.go @@ -185,7 +185,7 @@ func TestMempoolAtmTxsAppGossipHandlingDiscardedTx(t *testing.T) { // (i.e., txs received via p2p are not included in push gossip) // This test adds it directly to the mempool + gossiper to simulate that. vm.mempool.AddRemoteTx(conflictingTx) - vm.atomicTxPushGossiper.Add(&atomic.GossipAtomicTx{conflictingTx}) + vm.atomicTxPushGossiper.Add(&atomic.GossipAtomicTx{Tx: conflictingTx}) time.Sleep(500 * time.Millisecond) vm.ctx.Lock.Lock() diff --git a/plugin/evm/mempool_atomic_gossiping_test.go b/plugin/evm/mempool_atomic_gossiping_test.go index f35d2749f1..9f2cc89535 100644 --- a/plugin/evm/mempool_atomic_gossiping_test.go +++ b/plugin/evm/mempool_atomic_gossiping_test.go @@ -127,6 +127,7 @@ func TestMempoolPriorityDrop(t *testing.T) { assert.NoError(err) }() mempool, err := atomic.NewMempool(vm.ctx, prometheus.NewRegistry(), 1, vm.verifyTxAtTip) + assert.NoError(err) tx1, err := vm.newImportTx(vm.ctx.XChainID, testEthAddrs[0], initialBaseFee, []*secp256k1.PrivateKey{testKeys[0]}) if err != nil { diff --git a/plugin/evm/service.go b/plugin/evm/service.go index c53a1da4c1..39baf3eed1 100644 --- a/plugin/evm/service.go +++ b/plugin/evm/service.go @@ -196,7 +196,7 @@ func (service *AvaxAPI) Import(_ *http.Request, args *client.ImportArgs, respons if err := service.vm.mempool.AddLocalTx(tx); err != nil { return err } - service.vm.atomicTxPushGossiper.Add(&atomic.GossipAtomicTx{tx}) + service.vm.atomicTxPushGossiper.Add(&atomic.GossipAtomicTx{Tx: tx}) return nil } @@ -280,7 +280,7 @@ func (service *AvaxAPI) Export(_ *http.Request, args *client.ExportArgs, respons if err := service.vm.mempool.AddLocalTx(tx); err != nil { return err } - service.vm.atomicTxPushGossiper.Add(&atomic.GossipAtomicTx{tx}) + service.vm.atomicTxPushGossiper.Add(&atomic.GossipAtomicTx{Tx: tx}) return nil } @@ -390,7 +390,7 @@ func (service *AvaxAPI) IssueTx(r *http.Request, args *api.FormattedTx, response if err := service.vm.mempool.AddLocalTx(tx); err != nil { return err } - service.vm.atomicTxPushGossiper.Add(&atomic.GossipAtomicTx{tx}) + service.vm.atomicTxPushGossiper.Add(&atomic.GossipAtomicTx{Tx: tx}) return nil } diff --git a/plugin/evm/tx_gossip_test.go b/plugin/evm/tx_gossip_test.go index ad9f4667a8..bfe9ba4b84 100644 --- a/plugin/evm/tx_gossip_test.go +++ b/plugin/evm/tx_gossip_test.go @@ -492,7 +492,7 @@ func TestAtomicTxPushGossipOutbound(t *testing.T) { 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(&atomic.GossipAtomicTx{tx}) + vm.atomicTxPushGossiper.Add(&atomic.GossipAtomicTx{Tx: tx}) gossipedBytes := <-sender.SentAppGossip require.Equal(byte(p2p.AtomicTxGossipHandlerID), gossipedBytes[0]) From aa50ce6a1dcbb34e86c925e929723fd02ac9d88f Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Sun, 15 Dec 2024 13:09:17 +0300 Subject: [PATCH 06/27] change newimport clk to time --- plugin/evm/atomic/import_tx.go | 6 ++---- plugin/evm/tx_gossip_test.go | 6 +++--- plugin/evm/vm.go | 2 +- plugin/evm/vm_test.go | 18 +++++++++--------- 4 files changed, 15 insertions(+), 17 deletions(-) diff --git a/plugin/evm/atomic/import_tx.go b/plugin/evm/atomic/import_tx.go index 0d4d367d4e..9213299d8c 100644 --- a/plugin/evm/atomic/import_tx.go +++ b/plugin/evm/atomic/import_tx.go @@ -20,7 +20,6 @@ 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" @@ -291,7 +290,7 @@ func (utx *UnsignedImportTx) AtomicOps() (ids.ID, *atomic.Requests, error) { func NewImportTx( ctx *snow.Context, rules params.Rules, - clk mockable.Clock, + time uint64, chainID ids.ID, // chain to import from to common.Address, // Address of recipient baseFee *big.Int, // fee to use post-AP3 @@ -302,9 +301,8 @@ func NewImportTx( signers := [][]*secp256k1.PrivateKey{} importedAmount := make(map[ids.ID]uint64) - now := clk.Unix() for _, utxo := range atomicUTXOs { - inputIntf, utxoSigners, err := kc.Spend(utxo.Out, now) + inputIntf, utxoSigners, err := kc.Spend(utxo.Out, time) if err != nil { continue } diff --git a/plugin/evm/tx_gossip_test.go b/plugin/evm/tx_gossip_test.go index fb1d7388d9..0e46d61ea0 100644 --- a/plugin/evm/tx_gossip_test.go +++ b/plugin/evm/tx_gossip_test.go @@ -268,7 +268,7 @@ func TestAtomicTxGossip(t *testing.T) { pk.PublicKey().Address(), ) require.NoError(err) - tx, err := atomic.NewImportTx(vm.ctx, vm.currentRules(), vm.clock, vm.ctx.XChainID, address, initialBaseFee, secp256k1fx.NewKeychain(pk), []*avax.UTXO{utxo}) + tx, err := atomic.NewImportTx(vm.ctx, vm.currentRules(), vm.clock.Unix(), vm.ctx.XChainID, address, initialBaseFee, secp256k1fx.NewKeychain(pk), []*avax.UTXO{utxo}) require.NoError(err) require.NoError(vm.mempool.AddLocalTx(tx)) @@ -476,7 +476,7 @@ func TestAtomicTxPushGossipOutbound(t *testing.T) { pk.PublicKey().Address(), ) require.NoError(err) - tx, err := atomic.NewImportTx(vm.ctx, vm.currentRules(), vm.clock, vm.ctx.XChainID, address, initialBaseFee, secp256k1fx.NewKeychain(pk), []*avax.UTXO{utxo}) + tx, err := atomic.NewImportTx(vm.ctx, vm.currentRules(), vm.clock.Unix(), 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}) @@ -547,7 +547,7 @@ func TestAtomicTxPushGossipInbound(t *testing.T) { pk.PublicKey().Address(), ) require.NoError(err) - tx, err := atomic.NewImportTx(vm.ctx, vm.currentRules(), vm.clock, vm.ctx.XChainID, address, initialBaseFee, secp256k1fx.NewKeychain(pk), []*avax.UTXO{utxo}) + tx, err := atomic.NewImportTx(vm.ctx, vm.currentRules(), vm.clock.Unix(), 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/vm.go b/plugin/evm/vm.go index 4adab936bc..ad28908b6b 100644 --- a/plugin/evm/vm.go +++ b/plugin/evm/vm.go @@ -1899,7 +1899,7 @@ func (vm *VM) newImportTx( return nil, fmt.Errorf("problem retrieving atomic UTXOs: %w", err) } - return atomic.NewImportTx(vm.ctx, vm.currentRules(), vm.clock, chainID, to, baseFee, kc, atomicUTXOs) + return atomic.NewImportTx(vm.ctx, vm.currentRules(), vm.clock.Unix(), chainID, to, baseFee, kc, atomicUTXOs) } // newExportTx returns a new ExportTx diff --git a/plugin/evm/vm_test.go b/plugin/evm/vm_test.go index cc9c7d6f7b..7c0a682324 100644 --- a/plugin/evm/vm_test.go +++ b/plugin/evm/vm_test.go @@ -1023,11 +1023,11 @@ func TestReissueAtomicTxHigherGasPrice(t *testing.T) { if err != nil { t.Fatal(err) } - tx1, err := atomic.NewImportTx(vm.ctx, vm.currentRules(), vm.clock, vm.ctx.XChainID, testEthAddrs[0], initialBaseFee, kc, []*avax.UTXO{utxo}) + tx1, err := atomic.NewImportTx(vm.ctx, vm.currentRules(), vm.clock.Unix(), vm.ctx.XChainID, testEthAddrs[0], initialBaseFee, kc, []*avax.UTXO{utxo}) if err != nil { t.Fatal(err) } - 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}) + tx2, err := atomic.NewImportTx(vm.ctx, vm.currentRules(), vm.clock.Unix(), vm.ctx.XChainID, testEthAddrs[0], new(big.Int).Mul(common.Big2, initialBaseFee), kc, []*avax.UTXO{utxo}) if err != nil { t.Fatal(err) } @@ -1050,11 +1050,11 @@ func TestReissueAtomicTxHigherGasPrice(t *testing.T) { if err != nil { t.Fatal(err) } - tx1, err := atomic.NewImportTx(vm.ctx, vm.currentRules(), vm.clock, vm.ctx.XChainID, testEthAddrs[0], initialBaseFee, kc, []*avax.UTXO{utxo1, utxo2}) + tx1, err := atomic.NewImportTx(vm.ctx, vm.currentRules(), vm.clock.Unix(), vm.ctx.XChainID, testEthAddrs[0], initialBaseFee, kc, []*avax.UTXO{utxo1, utxo2}) if err != nil { t.Fatal(err) } - 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}) + tx2, err := atomic.NewImportTx(vm.ctx, vm.currentRules(), vm.clock.Unix(), vm.ctx.XChainID, testEthAddrs[0], new(big.Int).Mul(common.Big2, initialBaseFee), kc, []*avax.UTXO{utxo1}) if err != nil { t.Fatal(err) } @@ -1078,17 +1078,17 @@ func TestReissueAtomicTxHigherGasPrice(t *testing.T) { t.Fatal(err) } - importTx1, err := atomic.NewImportTx(vm.ctx, vm.currentRules(), vm.clock, vm.ctx.XChainID, testEthAddrs[0], initialBaseFee, kc, []*avax.UTXO{utxo1}) + importTx1, err := atomic.NewImportTx(vm.ctx, vm.currentRules(), vm.clock.Unix(), vm.ctx.XChainID, testEthAddrs[0], initialBaseFee, kc, []*avax.UTXO{utxo1}) if err != nil { t.Fatal(err) } - 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}) + importTx2, err := atomic.NewImportTx(vm.ctx, vm.currentRules(), vm.clock.Unix(), 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 := 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}) + reissuanceTx1, err := atomic.NewImportTx(vm.ctx, vm.currentRules(), vm.clock.Unix(), vm.ctx.XChainID, testEthAddrs[0], new(big.Int).Mul(big.NewInt(2), initialBaseFee), kc, []*avax.UTXO{utxo1, utxo2}) if err != nil { t.Fatal(err) } @@ -1108,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 := 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}) + reissuanceTx2, err := atomic.NewImportTx(vm.ctx, vm.currentRules(), vm.clock.Unix(), vm.ctx.XChainID, testEthAddrs[0], new(big.Int).Mul(big.NewInt(4), initialBaseFee), kc, []*avax.UTXO{utxo1, utxo2}) if err != nil { t.Fatal(err) } @@ -3679,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 := atomic.NewImportTx(vm.ctx, vm.currentRules(), vm.clock, vm.ctx.XChainID, testEthAddrs[0], initialBaseFee, kc, []*avax.UTXO{utxo}) + importTx, err := atomic.NewImportTx(vm.ctx, vm.currentRules(), vm.clock.Unix(), vm.ctx.XChainID, testEthAddrs[0], initialBaseFee, kc, []*avax.UTXO{utxo}) if err != nil { t.Fatal(err) } From 52081a9d5f45f5b49794696b0415e4c7de9e23d0 Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Sun, 15 Dec 2024 13:30:37 +0300 Subject: [PATCH 07/27] move utils --- plugin/evm/atomic/export_tx.go | 11 ++++++----- plugin/evm/client/client.go | 3 ++- plugin/evm/export_tx_test.go | 9 +++++---- plugin/evm/import_tx_test.go | 9 +++++---- plugin/evm/service.go | 5 +++-- plugin/evm/tx_gossip_test.go | 12 ++++++------ plugin/evm/user.go | 3 ++- plugin/evm/vm_test.go | 2 +- {plugin/evm/atomic => utils}/utils.go | 3 +-- 9 files changed, 31 insertions(+), 26 deletions(-) rename {plugin/evm/atomic => utils}/utils.go (91%) diff --git a/plugin/evm/atomic/export_tx.go b/plugin/evm/atomic/export_tx.go index 26307cface..906753265c 100644 --- a/plugin/evm/atomic/export_tx.go +++ b/plugin/evm/atomic/export_tx.go @@ -10,12 +10,13 @@ import ( "math/big" "github.com/ava-labs/coreth/params" + "github.com/ava-labs/coreth/utils" "github.com/holiman/uint256" "github.com/ava-labs/avalanchego/chains/atomic" "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/snow" - "github.com/ava-labs/avalanchego/utils" + avalancheutils "github.com/ava-labs/avalanchego/utils" "github.com/ava-labs/avalanchego/utils/constants" "github.com/ava-labs/avalanchego/utils/crypto/secp256k1" "github.com/ava-labs/avalanchego/utils/math" @@ -123,7 +124,7 @@ func (utx *UnsignedExportTx) Verify( if !avax.IsSortedTransferableOutputs(utx.ExportedOutputs, Codec) { return ErrOutputsNotSorted } - if rules.IsApricotPhase1 && !utils.IsSortedAndUnique(utx.Ins) { + if rules.IsApricotPhase1 && !avalancheutils.IsSortedAndUnique(utx.Ins) { return ErrInputsNotSortedUnique } @@ -240,7 +241,7 @@ func (utx *UnsignedExportTx) SemanticVerify( if err != nil { return err } - if input.Address != PublicKeyToEthAddress(pubKey) { + if input.Address != utils.PublicKeyToEthAddress(pubKey) { return errPublicKeySignatureMismatch } } @@ -431,7 +432,7 @@ func GetSpendableFunds( if amount == 0 { break } - addr := GetEthAddress(key) + addr := utils.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 @@ -514,7 +515,7 @@ func GetSpendableAVAXWithFee( additionalFee := newFee - prevFee - addr := GetEthAddress(key) + addr := utils.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() diff --git a/plugin/evm/client/client.go b/plugin/evm/client/client.go index f92e55e59f..93ac27ed5b 100644 --- a/plugin/evm/client/client.go +++ b/plugin/evm/client/client.go @@ -19,6 +19,7 @@ import ( "github.com/ava-labs/avalanchego/utils/json" "github.com/ava-labs/avalanchego/utils/rpc" "github.com/ava-labs/coreth/plugin/evm/atomic" + "github.com/ava-labs/coreth/utils" ) // Interface compliance @@ -179,7 +180,7 @@ func (c *client) ImportKey(ctx context.Context, user api.UserPass, privateKey *s if err != nil { return common.Address{}, err } - return atomic.ParseEthAddress(res.Address) + return utils.ParseEthAddress(res.Address) } // ImportArgs are arguments for passing into Import requests diff --git a/plugin/evm/export_tx_test.go b/plugin/evm/export_tx_test.go index 3e2b0d2160..61c80fb7dd 100644 --- a/plugin/evm/export_tx_test.go +++ b/plugin/evm/export_tx_test.go @@ -19,6 +19,7 @@ import ( "github.com/ava-labs/avalanchego/vms/secp256k1fx" "github.com/ava-labs/coreth/params" "github.com/ava-labs/coreth/plugin/evm/atomic" + "github.com/ava-labs/coreth/utils" "github.com/ethereum/go-ethereum/common" "github.com/holiman/uint256" ) @@ -104,7 +105,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 := atomic.GetEthAddress(key) + ethAddr := utils.GetEthAddress(key) avaxAmount := 50 * units.MilliAvax avaxUTXOID := avax.UTXOID{ @@ -1822,7 +1823,7 @@ func TestNewExportTx(t *testing.T) { t.Fatal(err) } - addr := atomic.GetEthAddress(testKeys[0]) + addr := utils.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)) } @@ -1970,7 +1971,7 @@ func TestNewExportTxMulticoin(t *testing.T) { parent = vm.LastAcceptedBlockInternal().(*Block) exportAmount := uint64(5000000) - testKeys0Addr := atomic.GetEthAddress(testKeys[0]) + testKeys0Addr := utils.GetEthAddress(testKeys[0]) exportId, err := ids.ToShortID(testKeys0Addr[:]) if err != nil { t.Fatal(err) @@ -2022,7 +2023,7 @@ func TestNewExportTxMulticoin(t *testing.T) { t.Fatal(err) } - addr := atomic.GetEthAddress(testKeys[0]) + addr := utils.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/import_tx_test.go b/plugin/evm/import_tx_test.go index d254153712..58f7baa6fc 100644 --- a/plugin/evm/import_tx_test.go +++ b/plugin/evm/import_tx_test.go @@ -9,12 +9,13 @@ import ( "github.com/ava-labs/coreth/params" "github.com/ava-labs/coreth/plugin/evm/atomic" + "github.com/ava-labs/coreth/utils" "github.com/ethereum/go-ethereum/common" "github.com/holiman/uint256" avalancheatomic "github.com/ava-labs/avalanchego/chains/atomic" "github.com/ava-labs/avalanchego/ids" - "github.com/ava-labs/avalanchego/utils" + avalancheutils "github.com/ava-labs/avalanchego/utils" "github.com/ava-labs/avalanchego/utils/constants" "github.com/ava-labs/avalanchego/utils/crypto/secp256k1" "github.com/ava-labs/avalanchego/utils/set" @@ -117,8 +118,8 @@ func TestImportTxVerify(t *testing.T) { } // Sort the inputs and outputs to ensure the transaction is canonical - utils.Sort(importTx.ImportedInputs) - utils.Sort(importTx.Outs) + avalancheutils.Sort(importTx.ImportedInputs) + avalancheutils.Sort(importTx.Outs) tests := map[string]atomicTxVerifyTest{ "nil tx": { @@ -498,7 +499,7 @@ func TestNewImportTx(t *testing.T) { expectedRemainingBalance := new(uint256.Int).Mul( uint256.NewInt(importAmount-actualAVAXBurned), atomic.X2CRate) - addr := atomic.GetEthAddress(testKeys[0]) + addr := utils.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) } diff --git a/plugin/evm/service.go b/plugin/evm/service.go index 59fddb1ea4..92529e32c2 100644 --- a/plugin/evm/service.go +++ b/plugin/evm/service.go @@ -18,6 +18,7 @@ import ( "github.com/ava-labs/coreth/params" "github.com/ava-labs/coreth/plugin/evm/atomic" "github.com/ava-labs/coreth/plugin/evm/client" + "github.com/ava-labs/coreth/utils" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/log" @@ -96,7 +97,7 @@ func (service *AvaxAPI) Version(r *http.Request, _ *struct{}, reply *VersionRepl func (service *AvaxAPI) ExportKey(r *http.Request, args *client.ExportKeyArgs, reply *client.ExportKeyReply) error { log.Info("EVM: ExportKey called") - address, err := atomic.ParseEthAddress(args.Address) + address, err := utils.ParseEthAddress(args.Address) if err != nil { return fmt.Errorf("couldn't parse %s to address: %s", args.Address, err) } @@ -127,7 +128,7 @@ func (service *AvaxAPI) ImportKey(r *http.Request, args *client.ImportKeyArgs, r return errMissingPrivateKey } - reply.Address = atomic.GetEthAddress(args.PrivateKey).Hex() + reply.Address = utils.GetEthAddress(args.PrivateKey).Hex() service.vm.ctx.Lock.Lock() defer service.vm.ctx.Lock.Unlock() diff --git a/plugin/evm/tx_gossip_test.go b/plugin/evm/tx_gossip_test.go index 0e46d61ea0..99cef8beb3 100644 --- a/plugin/evm/tx_gossip_test.go +++ b/plugin/evm/tx_gossip_test.go @@ -48,7 +48,7 @@ func TestEthTxGossip(t *testing.T) { pk, err := secp256k1.NewPrivateKey() require.NoError(err) - address := atomic.GetEthAddress(pk) + address := utils.GetEthAddress(pk) genesis := newPrefundedGenesis(100_000_000_000_000_000, address) genesisBytes, err := genesis.MarshalJSON() require.NoError(err) @@ -176,7 +176,7 @@ func TestAtomicTxGossip(t *testing.T) { pk, err := secp256k1.NewPrivateKey() require.NoError(err) - address := atomic.GetEthAddress(pk) + address := utils.GetEthAddress(pk) genesis := newPrefundedGenesis(100_000_000_000_000_000, address) genesisBytes, err := genesis.MarshalJSON() require.NoError(err) @@ -315,7 +315,7 @@ func TestEthTxPushGossipOutbound(t *testing.T) { pk, err := secp256k1.NewPrivateKey() require.NoError(err) - address := atomic.GetEthAddress(pk) + address := utils.GetEthAddress(pk) genesis := newPrefundedGenesis(100_000_000_000_000_000, address) genesisBytes, err := genesis.MarshalJSON() require.NoError(err) @@ -375,7 +375,7 @@ func TestEthTxPushGossipInbound(t *testing.T) { pk, err := secp256k1.NewPrivateKey() require.NoError(err) - address := atomic.GetEthAddress(pk) + address := utils.GetEthAddress(pk) genesis := newPrefundedGenesis(100_000_000_000_000_000, address) genesisBytes, err := genesis.MarshalJSON() require.NoError(err) @@ -434,7 +434,7 @@ func TestAtomicTxPushGossipOutbound(t *testing.T) { pk, err := secp256k1.NewPrivateKey() require.NoError(err) - address := atomic.GetEthAddress(pk) + address := utils.GetEthAddress(pk) genesis := newPrefundedGenesis(100_000_000_000_000_000, address) genesisBytes, err := genesis.MarshalJSON() require.NoError(err) @@ -507,7 +507,7 @@ func TestAtomicTxPushGossipInbound(t *testing.T) { pk, err := secp256k1.NewPrivateKey() require.NoError(err) - address := atomic.GetEthAddress(pk) + address := utils.GetEthAddress(pk) genesis := newPrefundedGenesis(100_000_000_000_000_000, address) genesisBytes, err := genesis.MarshalJSON() require.NoError(err) diff --git a/plugin/evm/user.go b/plugin/evm/user.go index 627a7af1d1..4a68eca2fb 100644 --- a/plugin/evm/user.go +++ b/plugin/evm/user.go @@ -10,6 +10,7 @@ import ( "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/utils/crypto/secp256k1" "github.com/ava-labs/coreth/plugin/evm/atomic" + "github.com/ava-labs/coreth/utils" "github.com/ethereum/go-ethereum/common" ) @@ -70,7 +71,7 @@ func (u *user) putAddress(privKey *secp256k1.PrivateKey) error { return errKeyNil } - address := atomic.GetEthAddress(privKey) // address the privKey controls + address := utils.GetEthAddress(privKey) // address the privKey controls controlsAddress, err := u.controlsAddress(address) if err != nil { return err diff --git a/plugin/evm/vm_test.go b/plugin/evm/vm_test.go index 7c0a682324..668a1b31c2 100644 --- a/plugin/evm/vm_test.go +++ b/plugin/evm/vm_test.go @@ -161,7 +161,7 @@ func init() { b, _ = cb58.Decode(key) pk, _ := secp256k1.ToPrivateKey(b) testKeys = append(testKeys, pk) - testEthAddrs = append(testEthAddrs, atomic.GetEthAddress(pk)) + testEthAddrs = append(testEthAddrs, utils.GetEthAddress(pk)) testShortIDAddrs = append(testShortIDAddrs, pk.PublicKey().Address()) } } diff --git a/plugin/evm/atomic/utils.go b/utils/utils.go similarity index 91% rename from plugin/evm/atomic/utils.go rename to utils/utils.go index 8872e09861..af2c0f822d 100644 --- a/plugin/evm/atomic/utils.go +++ b/utils/utils.go @@ -1,7 +1,7 @@ // (c) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. -package atomic +package utils import ( "errors" @@ -13,7 +13,6 @@ import ( 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 From d93257b06d861723f34f5c2a28852112ca542f10 Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Sun, 15 Dec 2024 16:07:56 +0300 Subject: [PATCH 08/27] remove extra heaps files --- plugin/evm/tx_heap.go | 164 ------------------------------------- plugin/evm/tx_heap_test.go | 143 -------------------------------- 2 files changed, 307 deletions(-) delete mode 100644 plugin/evm/tx_heap.go delete mode 100644 plugin/evm/tx_heap_test.go diff --git a/plugin/evm/tx_heap.go b/plugin/evm/tx_heap.go deleted file mode 100644 index c6562fd9b0..0000000000 --- a/plugin/evm/tx_heap.go +++ /dev/null @@ -1,164 +0,0 @@ -// (c) 2020-2021, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package evm - -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 -// the mempool. -type txEntry struct { - id ids.ID - gasPrice uint64 - tx *atomic.Tx - index int -} - -// internalTxHeap is used to track pending atomic transactions by [gasPrice] -type internalTxHeap struct { - isMinHeap bool - items []*txEntry - lookup map[ids.ID]*txEntry -} - -func newInternalTxHeap(items int, isMinHeap bool) *internalTxHeap { - return &internalTxHeap{ - isMinHeap: isMinHeap, - items: make([]*txEntry, 0, items), - lookup: map[ids.ID]*txEntry{}, - } -} - -func (th internalTxHeap) Len() int { return len(th.items) } - -func (th internalTxHeap) Less(i, j int) bool { - if th.isMinHeap { - return th.items[i].gasPrice < th.items[j].gasPrice - } - return th.items[i].gasPrice > th.items[j].gasPrice -} - -func (th internalTxHeap) Swap(i, j int) { - th.items[i], th.items[j] = th.items[j], th.items[i] - th.items[i].index = i - th.items[j].index = j -} - -func (th *internalTxHeap) Push(x interface{}) { - entry := x.(*txEntry) - if th.Has(entry.id) { - return - } - th.items = append(th.items, entry) - th.lookup[entry.id] = entry -} - -func (th *internalTxHeap) Pop() interface{} { - n := len(th.items) - item := th.items[n-1] - th.items[n-1] = nil // avoid memory leak - th.items = th.items[0 : n-1] - delete(th.lookup, item.id) - return item -} - -func (th *internalTxHeap) Get(id ids.ID) (*txEntry, bool) { - entry, ok := th.lookup[id] - if !ok { - return nil, false - } - return entry, true -} - -func (th *internalTxHeap) Has(id ids.ID) bool { - _, has := th.Get(id) - return has -} - -type txHeap struct { - maxHeap *internalTxHeap - minHeap *internalTxHeap -} - -func newTxHeap(maxSize int) *txHeap { - return &txHeap{ - maxHeap: newInternalTxHeap(maxSize, false), - minHeap: newInternalTxHeap(maxSize, true), - } -} - -func (th *txHeap) Push(tx *atomic.Tx, gasPrice uint64) { - txID := tx.ID() - oldLen := th.Len() - heap.Push(th.maxHeap, &txEntry{ - id: txID, - gasPrice: gasPrice, - tx: tx, - index: oldLen, - }) - heap.Push(th.minHeap, &txEntry{ - id: txID, - gasPrice: gasPrice, - tx: tx, - index: oldLen, - }) -} - -// Assumes there is non-zero items in [txHeap] -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() (*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() *atomic.Tx { - return th.Remove(th.maxHeap.items[0].id) -} - -// Assumes there is non-zero items in [txHeap] -func (th *txHeap) PopMin() *atomic.Tx { - return th.Remove(th.minHeap.items[0].id) -} - -func (th *txHeap) Remove(id ids.ID) *atomic.Tx { - maxEntry, ok := th.maxHeap.Get(id) - if !ok { - return nil - } - heap.Remove(th.maxHeap, maxEntry.index) - - minEntry, ok := th.minHeap.Get(id) - if !ok { - // This should never happen, as that would mean the heaps are out of - // sync. - return nil - } - return heap.Remove(th.minHeap, minEntry.index).(*txEntry).tx -} - -func (th *txHeap) Len() int { - return th.maxHeap.Len() -} - -func (th *txHeap) Get(id ids.ID) (*atomic.Tx, bool) { - txEntry, ok := th.maxHeap.Get(id) - if !ok { - return nil, false - } - return txEntry.tx, true -} - -func (th *txHeap) Has(id ids.ID) bool { - return th.maxHeap.Has(id) -} diff --git a/plugin/evm/tx_heap_test.go b/plugin/evm/tx_heap_test.go deleted file mode 100644 index a054b7362e..0000000000 --- a/plugin/evm/tx_heap_test.go +++ /dev/null @@ -1,143 +0,0 @@ -// (c) 2019-2021, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package evm - -import ( - "testing" - - "github.com/ava-labs/coreth/plugin/evm/atomic" - "github.com/stretchr/testify/assert" -) - -func TestTxHeap(t *testing.T) { - var ( - tx0 = &atomic.Tx{ - UnsignedAtomicTx: &atomic.UnsignedImportTx{ - NetworkID: 0, - }, - } - tx0Bytes = []byte{0} - - tx1 = &atomic.Tx{ - UnsignedAtomicTx: &atomic.UnsignedImportTx{ - NetworkID: 1, - }, - } - tx1Bytes = []byte{1} - - tx2 = &atomic.Tx{ - UnsignedAtomicTx: &atomic.UnsignedImportTx{ - NetworkID: 2, - }, - } - tx2Bytes = []byte{2} - ) - tx0.Initialize(tx0Bytes, tx0Bytes) - tx1.Initialize(tx1Bytes, tx1Bytes) - tx2.Initialize(tx2Bytes, tx2Bytes) - - id0 := tx0.ID() - id1 := tx1.ID() - id2 := tx2.ID() - - t.Run("add/remove single entry", func(t *testing.T) { - h := newTxHeap(3) - assert.Zero(t, h.Len()) - - assert := assert.New(t) - h.Push(tx0, 5) - assert.True(h.Has(id0)) - gTx0, gHas0 := h.Get(id0) - assert.Equal(tx0, gTx0) - assert.True(gHas0) - h.Remove(id0) - assert.False(h.Has(id0)) - assert.Zero(h.Len()) - h.Push(tx0, 5) - assert.True(h.Has(id0)) - assert.Equal(1, h.Len()) - }) - - t.Run("add other items", func(t *testing.T) { - h := newTxHeap(3) - assert.Zero(t, h.Len()) - - assert := assert.New(t) - h.Push(tx1, 10) - assert.True(h.Has(id1)) - gTx1, gHas1 := h.Get(id1) - assert.Equal(tx1, gTx1) - assert.True(gHas1) - - h.Push(tx2, 2) - assert.True(h.Has(id2)) - gTx2, gHas2 := h.Get(id2) - assert.Equal(tx2, gTx2) - assert.True(gHas2) - - assert.Equal(id1, h.PopMax().ID()) - assert.Equal(id2, h.PopMax().ID()) - - assert.False(h.Has(id0)) - gTx0, gHas0 := h.Get(id0) - assert.Nil(gTx0) - assert.False(gHas0) - - assert.False(h.Has(id1)) - gTx1, gHas1 = h.Get(id1) - assert.Nil(gTx1) - assert.False(gHas1) - - assert.False(h.Has(id2)) - gTx2, gHas2 = h.Get(id2) - assert.Nil(gTx2) - assert.False(gHas2) - }) - - verifyRemovalOrder := func(t *testing.T, h *txHeap) { - t.Helper() - - assert := assert.New(t) - assert.Equal(id2, h.PopMin().ID()) - assert.True(h.Has(id0)) - assert.True(h.Has(id1)) - assert.False(h.Has(id2)) - assert.Equal(id0, h.PopMin().ID()) - assert.False(h.Has(id0)) - assert.True(h.Has(id1)) - assert.False(h.Has(id2)) - assert.Equal(id1, h.PopMin().ID()) - assert.False(h.Has(id0)) - assert.False(h.Has(id1)) - assert.False(h.Has(id2)) - } - - t.Run("drop", func(t *testing.T) { - h := newTxHeap(3) - assert.Zero(t, h.Len()) - - h.Push(tx0, 5) - h.Push(tx1, 10) - h.Push(tx2, 2) - verifyRemovalOrder(t, h) - }) - t.Run("drop (alt order)", func(t *testing.T) { - h := newTxHeap(3) - assert.Zero(t, h.Len()) - - h.Push(tx0, 5) - h.Push(tx2, 2) - h.Push(tx1, 10) - verifyRemovalOrder(t, h) - }) - t.Run("drop (alt order 2)", func(t *testing.T) { - h := newTxHeap(3) - assert.Zero(t, h.Len()) - - h.Push(tx2, 2) - h.Push(tx0, 5) - h.Push(tx1, 10) - verifyRemovalOrder(t, h) - }) -} From 0b10cc497eb3f5de971d0f0f3eca4f076887c294 Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Sun, 15 Dec 2024 16:08:18 +0300 Subject: [PATCH 09/27] move database to own pkg --- plugin/evm/atomic_trie.go | 33 ++++++++--------- .../wrapped_database.go} | 32 +++++++++-------- plugin/evm/syncervm_test.go | 7 ++-- plugin/evm/vm.go | 35 ++++++++++--------- 4 files changed, 57 insertions(+), 50 deletions(-) rename plugin/evm/{database.go => database/wrapped_database.go} (55%) diff --git a/plugin/evm/atomic_trie.go b/plugin/evm/atomic_trie.go index d734268e23..f6add46623 100644 --- a/plugin/evm/atomic_trie.go +++ b/plugin/evm/atomic_trie.go @@ -9,7 +9,7 @@ import ( avalancheatomic "github.com/ava-labs/avalanchego/chains/atomic" "github.com/ava-labs/avalanchego/codec" - "github.com/ava-labs/avalanchego/database" + avalanchedatabase "github.com/ava-labs/avalanchego/database" "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/utils/units" "github.com/ava-labs/avalanchego/utils/wrappers" @@ -18,6 +18,7 @@ import ( "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/plugin/evm/database" "github.com/ava-labs/coreth/trie" "github.com/ava-labs/coreth/trie/trienode" "github.com/ava-labs/coreth/triedb" @@ -117,12 +118,12 @@ type AtomicTrieIterator interface { // atomicTrie implements the AtomicTrie interface type atomicTrie struct { - commitInterval uint64 // commit interval, same as commitHeightInterval by default - metadataDB database.Database // Underlying database containing the atomic trie metadata - trieDB *triedb.Database // Trie database - lastCommittedRoot common.Hash // trie root of the most recent commit - lastCommittedHeight uint64 // index height of the most recent commit - lastAcceptedRoot common.Hash // most recent trie root passed to accept trie or the root of the atomic trie on intialization. + commitInterval uint64 // commit interval, same as commitHeightInterval by default + metadataDB avalanchedatabase.Database // Underlying database containing the atomic trie metadata + trieDB *triedb.Database // Trie database + lastCommittedRoot common.Hash // trie root of the most recent commit + lastCommittedHeight uint64 // index height of the most recent commit + lastAcceptedRoot common.Hash // most recent trie root passed to accept trie or the root of the atomic trie on intialization. codec codec.Manager memoryCap common.StorageSize tipBuffer *core.BoundedBuffer[common.Hash] @@ -131,7 +132,7 @@ type atomicTrie struct { // newAtomicTrie returns a new instance of a atomicTrie with a configurable commitHeightInterval, used in testing. // Initializes the trie before returning it. func newAtomicTrie( - atomicTrieDB database.Database, metadataDB database.Database, + atomicTrieDB avalanchedatabase.Database, metadataDB avalanchedatabase.Database, codec codec.Manager, lastAcceptedHeight uint64, commitHeightInterval uint64, ) (*atomicTrie, error) { root, height, err := lastCommittedRootIfExists(metadataDB) @@ -153,7 +154,7 @@ func newAtomicTrie( } trieDB := triedb.NewDatabase( - rawdb.NewDatabase(Database{atomicTrieDB}), + rawdb.NewDatabase(database.WrapDatabase(atomicTrieDB)), &triedb.Config{ HashDB: &hashdb.Config{ CleanCacheSize: 64 * units.MiB, // Allocate 64MB of memory for clean cache @@ -182,17 +183,17 @@ func newAtomicTrie( // else returns empty common.Hash{} and 0 // returns error only if there are issues with the underlying data store // or if values present in the database are not as expected -func lastCommittedRootIfExists(db database.Database) (common.Hash, uint64, error) { +func lastCommittedRootIfExists(db avalanchedatabase.Database) (common.Hash, uint64, error) { // read the last committed entry if it exists and set the root hash lastCommittedHeightBytes, err := db.Get(lastCommittedKey) switch { - case err == database.ErrNotFound: + case err == avalanchedatabase.ErrNotFound: return common.Hash{}, 0, nil case err != nil: return common.Hash{}, 0, err } - height, err := database.ParseUInt64(lastCommittedHeightBytes) + height, err := avalanchedatabase.ParseUInt64(lastCommittedHeightBytes) if err != nil { return common.Hash{}, 0, fmt.Errorf("expected value at lastCommittedKey to be a valid uint64: %w", err) } @@ -251,7 +252,7 @@ func (a *atomicTrie) LastCommitted() (common.Hash, uint64) { // updateLastCommitted adds [height] -> [root] to the index and marks it as the last committed // root/height pair. func (a *atomicTrie) updateLastCommitted(root common.Hash, height uint64) error { - heightBytes := database.PackUInt64(height) + heightBytes := avalanchedatabase.PackUInt64(height) // now save the trie hash against the height it was committed at if err := a.metadataDB.Put(heightBytes, root[:]); err != nil { @@ -297,7 +298,7 @@ func (a *atomicTrie) Root(height uint64) (common.Hash, error) { // getRoot is a helper function to return the committed atomic trie root hash at [height] // from [metadataDB]. -func getRoot(metadataDB database.Database, height uint64) (common.Hash, error) { +func getRoot(metadataDB avalanchedatabase.Database, height uint64) (common.Hash, error) { if height == 0 { // if root is queried at height == 0, return the empty root hash // this may occur if peers ask for the most recent state summary @@ -305,10 +306,10 @@ func getRoot(metadataDB database.Database, height uint64) (common.Hash, error) { return types.EmptyRootHash, nil } - heightBytes := database.PackUInt64(height) + heightBytes := avalanchedatabase.PackUInt64(height) hash, err := metadataDB.Get(heightBytes) switch { - case err == database.ErrNotFound: + case err == avalanchedatabase.ErrNotFound: return common.Hash{}, nil case err != nil: return common.Hash{}, err diff --git a/plugin/evm/database.go b/plugin/evm/database/wrapped_database.go similarity index 55% rename from plugin/evm/database.go rename to plugin/evm/database/wrapped_database.go index 479c995ba3..f8a36913bb 100644 --- a/plugin/evm/database.go +++ b/plugin/evm/database/wrapped_database.go @@ -1,7 +1,7 @@ // (c) 2019-2020, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. -package evm +package database import ( "errors" @@ -11,25 +11,29 @@ import ( ) var ( - _ ethdb.KeyValueStore = &Database{} + _ ethdb.KeyValueStore = ðDbWrapper{} ErrSnapshotNotSupported = errors.New("snapshot is not supported") ) -// Database implements ethdb.Database -type Database struct{ database.Database } +// ethDbWrapper implements ethdb.Database +type ethDbWrapper struct{ database.Database } + +func WrapDatabase(db database.Database) ethdb.KeyValueStore { return ethDbWrapper{db} } // Stat implements ethdb.Database -func (db Database) Stat(string) (string, error) { return "", database.ErrNotFound } +func (db ethDbWrapper) Stat(string) (string, error) { return "", database.ErrNotFound } // NewBatch implements ethdb.Database -func (db Database) NewBatch() ethdb.Batch { return Batch{db.Database.NewBatch()} } +func (db ethDbWrapper) NewBatch() ethdb.Batch { return wrappedBatch{db.Database.NewBatch()} } // NewBatchWithSize implements ethdb.Database // TODO: propagate size through avalanchego Database interface -func (db Database) NewBatchWithSize(size int) ethdb.Batch { return Batch{db.Database.NewBatch()} } +func (db ethDbWrapper) NewBatchWithSize(size int) ethdb.Batch { + return wrappedBatch{db.Database.NewBatch()} +} -func (db Database) NewSnapshot() (ethdb.Snapshot, error) { +func (db ethDbWrapper) NewSnapshot() (ethdb.Snapshot, error) { return nil, ErrSnapshotNotSupported } @@ -37,7 +41,7 @@ func (db Database) NewSnapshot() (ethdb.Snapshot, error) { // // Note: This method assumes that the prefix is NOT part of the start, so there's // no need for the caller to prepend the prefix to the start. -func (db Database) NewIterator(prefix []byte, start []byte) ethdb.Iterator { +func (db ethDbWrapper) NewIterator(prefix []byte, start []byte) ethdb.Iterator { // avalanchego's database implementation assumes that the prefix is part of the // start, so it is added here (if it is provided). if len(prefix) > 0 { @@ -50,15 +54,15 @@ func (db Database) NewIterator(prefix []byte, start []byte) ethdb.Iterator { } // NewIteratorWithStart implements ethdb.Database -func (db Database) NewIteratorWithStart(start []byte) ethdb.Iterator { +func (db ethDbWrapper) NewIteratorWithStart(start []byte) ethdb.Iterator { return db.Database.NewIteratorWithStart(start) } -// Batch implements ethdb.Batch -type Batch struct{ database.Batch } +// wrappedBatch implements ethdb.wrappedBatch +type wrappedBatch struct{ database.Batch } // ValueSize implements ethdb.Batch -func (batch Batch) ValueSize() int { return batch.Batch.Size() } +func (batch wrappedBatch) ValueSize() int { return batch.Batch.Size() } // Replay implements ethdb.Batch -func (batch Batch) Replay(w ethdb.KeyValueWriter) error { return batch.Batch.Replay(w) } +func (batch wrappedBatch) Replay(w ethdb.KeyValueWriter) error { return batch.Batch.Replay(w) } diff --git a/plugin/evm/syncervm_test.go b/plugin/evm/syncervm_test.go index bd7f993cb5..24491d9fa5 100644 --- a/plugin/evm/syncervm_test.go +++ b/plugin/evm/syncervm_test.go @@ -16,7 +16,7 @@ import ( "github.com/stretchr/testify/require" avalancheatomic "github.com/ava-labs/avalanchego/chains/atomic" - "github.com/ava-labs/avalanchego/database" + avalanchedatabase "github.com/ava-labs/avalanchego/database" "github.com/ava-labs/avalanchego/database/prefixdb" "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/snow" @@ -36,6 +36,7 @@ import ( "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/plugin/evm/database" "github.com/ava-labs/coreth/predicate" statesyncclient "github.com/ava-labs/coreth/sync/client" "github.com/ava-labs/coreth/sync/statesync" @@ -430,7 +431,7 @@ type syncVMSetup struct { fundedAccounts map[*keystore.Key]*types.StateAccount syncerVM *VM - syncerDB database.Database + syncerDB avalanchedatabase.Database syncerEngineChan <-chan commonEng.Message syncerAtomicMemory *avalancheatomic.Memory shutdownOnceSyncerVM *shutdownOnceVM @@ -491,7 +492,7 @@ func testSyncerVM(t *testing.T, vmSetup *syncVMSetup, test syncTest) { if test.expectedErr != nil { require.ErrorIs(err, test.expectedErr) // Note we re-open the database here to avoid a closed error when the test is for a shutdown VM. - chaindb := Database{prefixdb.NewNested(ethDBPrefix, syncerVM.db)} + chaindb := database.WrapDatabase(prefixdb.NewNested(ethDBPrefix, syncerVM.db)) assertSyncPerformedHeights(t, chaindb, map[uint64]struct{}{}) return } diff --git a/plugin/evm/vm.go b/plugin/evm/vm.go index c1b9426bea..c83557d594 100644 --- a/plugin/evm/vm.go +++ b/plugin/evm/vm.go @@ -42,6 +42,7 @@ import ( "github.com/ava-labs/coreth/peer" "github.com/ava-labs/coreth/plugin/evm/atomic" "github.com/ava-labs/coreth/plugin/evm/config" + "github.com/ava-labs/coreth/plugin/evm/database" "github.com/ava-labs/coreth/plugin/evm/message" "github.com/ava-labs/coreth/triedb" "github.com/ava-labs/coreth/triedb/hashdb" @@ -75,7 +76,7 @@ import ( "github.com/ava-labs/avalanchego/cache" "github.com/ava-labs/avalanchego/codec" "github.com/ava-labs/avalanchego/codec/linearcodec" - "github.com/ava-labs/avalanchego/database" + avalanchedatabase "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" @@ -229,18 +230,18 @@ type VM struct { db *versiondb.Database // metadataDB is used to store one off keys. - metadataDB database.Database + metadataDB avalanchedatabase.Database // [chaindb] is the database supplied to the Ethereum backend chaindb ethdb.Database // [acceptedBlockDB] is the database to store the last accepted // block. - acceptedBlockDB database.Database + acceptedBlockDB avalanchedatabase.Database // [warpDB] is used to store warp message signatures // set to a prefixDB with the prefix [warpPrefix] - warpDB database.Database + warpDB avalanchedatabase.Database toEngine chan<- commonEng.Message @@ -329,7 +330,7 @@ func (vm *VM) GetActivationTime() time.Time { func (vm *VM) Initialize( _ context.Context, chainCtx *snow.Context, - db database.Database, + db avalanchedatabase.Database, genesisBytes []byte, upgradeBytes []byte, configBytes []byte, @@ -560,7 +561,7 @@ func (vm *VM) Initialize( // clear warpdb on initialization if config enabled if vm.config.PruneWarpDB { - if err := database.Clear(vm.warpDB, ethdb.IdealBatchSize); err != nil { + if err := avalanchedatabase.Clear(vm.warpDB, ethdb.IdealBatchSize); err != nil { return fmt.Errorf("failed to prune warpDB: %w", err) } } @@ -1376,10 +1377,10 @@ func (vm *VM) ParseEthBlock(b []byte) (*types.Block, error) { // by ChainState. func (vm *VM) getBlock(_ context.Context, id ids.ID) (snowman.Block, error) { ethBlock := vm.blockChain.GetBlockByHash(common.Hash(id)) - // If [ethBlock] is nil, return [database.ErrNotFound] here + // If [ethBlock] is nil, return [avalanchedatabase.ErrNotFound] here // so that the miss is considered cacheable. if ethBlock == nil { - return nil, database.ErrNotFound + return nil, avalanchedatabase.ErrNotFound } // Note: the status of block is set by ChainState return vm.newBlock(ethBlock) @@ -1401,7 +1402,7 @@ func (vm *VM) GetAcceptedBlock(ctx context.Context, blkID ids.ID) (snowman.Block if acceptedBlkID != blkID { // The provided block is not accepted. - return nil, database.ErrNotFound + return nil, avalanchedatabase.ErrNotFound } return blk, nil } @@ -1427,17 +1428,17 @@ func (vm *VM) VerifyHeightIndex(context.Context) error { // GetBlockIDAtHeight returns the canonical block at [height]. // Note: the engine assumes that if a block is not found at [height], then -// [database.ErrNotFound] will be returned. This indicates that the VM has state +// [avalanchedatabase.ErrNotFound] will be returned. This indicates that the VM has state // synced and does not have all historical blocks available. func (vm *VM) GetBlockIDAtHeight(_ context.Context, height uint64) (ids.ID, error) { lastAcceptedBlock := vm.LastAcceptedBlock() if lastAcceptedBlock.Height() < height { - return ids.ID{}, database.ErrNotFound + return ids.ID{}, avalanchedatabase.ErrNotFound } hash := vm.blockChain.GetCanonicalHash(height) if hash == (common.Hash{}) { - return ids.ID{}, database.ErrNotFound + return ids.ID{}, avalanchedatabase.ErrNotFound } return ids.ID(hash), nil } @@ -1514,11 +1515,11 @@ func (vm *VM) CreateHandlers(context.Context) (map[string]http.Handler, error) { } // initializeDBs initializes the databases used by the VM. -// coreth always uses the avalanchego provided database. -func (vm *VM) initializeDBs(db database.Database) error { +// coreth always uses the avalanchego provided avalanchedatabase. +func (vm *VM) initializeDBs(db avalanchedatabase.Database) error { // Use NewNested rather than New so that the structure of the database // remains the same regardless of the provided baseDB type. - vm.chaindb = rawdb.NewDatabase(Database{prefixdb.NewNested(ethDBPrefix, db)}) + vm.chaindb = rawdb.NewDatabase(database.WrapDatabase(prefixdb.NewNested(ethDBPrefix, db))) vm.db = versiondb.New(db) vm.acceptedBlockDB = prefixdb.New(acceptedPrefix, vm.db) vm.metadataDB = prefixdb.New(metadataPrefix, vm.db) @@ -1556,7 +1557,7 @@ func (vm *VM) CreateStaticHandlers(context.Context) (map[string]http.Handler, er 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, atomic.Accepted, height, nil - } else if err != database.ErrNotFound { + } else if err != avalanchedatabase.ErrNotFound { return nil, atomic.Unknown, 0, err } tx, dropped, found := vm.mempool.GetTx(txID) @@ -1802,7 +1803,7 @@ func (vm *VM) readLastAccepted() (common.Hash, uint64, error) { // initialize state with the genesis block. lastAcceptedBytes, lastAcceptedErr := vm.acceptedBlockDB.Get(lastAcceptedKey) switch { - case lastAcceptedErr == database.ErrNotFound: + case lastAcceptedErr == avalanchedatabase.ErrNotFound: // If there is nothing in the database, return the genesis block hash and height return vm.genesisHash, 0, nil case lastAcceptedErr != nil: From 8eb3056b810ae28f531f580771f552e8f53f8783 Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Wed, 18 Dec 2024 17:58:12 +0300 Subject: [PATCH 10/27] move atomic trie/state/backend to separaet pkg --- go.sum | 2 - plugin/evm/{ => atomic}/atomic_backend.go | 29 +++- plugin/evm/{ => atomic}/atomic_state.go | 5 +- plugin/evm/{ => atomic}/atomic_syncer.go | 2 +- plugin/evm/{ => atomic}/atomic_syncer_test.go | 5 +- plugin/evm/{ => atomic}/atomic_trie.go | 5 +- .../evm/{ => atomic}/atomic_trie_iterator.go | 2 +- .../{ => atomic}/atomic_trie_iterator_test.go | 24 +-- plugin/evm/{ => atomic}/atomic_trie_test.go | 154 +++++------------- .../evm/{ => atomic}/atomic_tx_repository.go | 43 +++-- .../{ => atomic}/atomic_tx_repository_test.go | 37 ++--- plugin/evm/atomic/export_tx.go | 2 +- plugin/evm/atomic/import_tx.go | 4 +- plugin/evm/atomic/test_shared_memories.go | 78 +++++++++ plugin/evm/atomic/test_tx.go | 2 +- plugin/evm/atomic/tx.go | 4 +- plugin/evm/atomic/utils.go | 10 ++ plugin/evm/block.go | 6 +- plugin/evm/export_tx_test.go | 38 ++--- plugin/evm/import_tx_test.go | 11 +- plugin/evm/syncervm_client.go | 124 ++++++++------ plugin/evm/syncervm_server.go | 11 +- plugin/evm/syncervm_test.go | 39 +++-- plugin/evm/tx_test.go | 2 +- plugin/evm/vm.go | 59 +++---- plugin/evm/vm_test.go | 65 +++----- utils/snow.go | 26 ++- 27 files changed, 409 insertions(+), 380 deletions(-) rename plugin/evm/{ => atomic}/atomic_backend.go (96%) rename plugin/evm/{ => atomic}/atomic_state.go (97%) rename plugin/evm/{ => atomic}/atomic_syncer.go (99%) rename plugin/evm/{ => atomic}/atomic_syncer_test.go (97%) rename plugin/evm/{ => atomic}/atomic_trie.go (99%) rename plugin/evm/{ => atomic}/atomic_trie_iterator.go (99%) rename plugin/evm/{ => atomic}/atomic_trie_iterator_test.go (83%) rename plugin/evm/{ => atomic}/atomic_trie_test.go (79%) rename plugin/evm/{ => atomic}/atomic_tx_repository.go (91%) rename plugin/evm/{ => atomic}/atomic_tx_repository_test.go (91%) create mode 100644 plugin/evm/atomic/test_shared_memories.go diff --git a/go.sum b/go.sum index 435d1113c8..7fe7f45671 100644 --- a/go.sum +++ b/go.sum @@ -54,8 +54,6 @@ github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156 h1:eMwmnE/GDgah4HI848JfFxHt+iPb26b4zyfspmqY0/8= github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM= 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= diff --git a/plugin/evm/atomic_backend.go b/plugin/evm/atomic/atomic_backend.go similarity index 96% rename from plugin/evm/atomic_backend.go rename to plugin/evm/atomic/atomic_backend.go index 2420021d6f..eefb254321 100644 --- a/plugin/evm/atomic_backend.go +++ b/plugin/evm/atomic/atomic_backend.go @@ -1,9 +1,10 @@ // (c) 2020-2021, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. -package evm +package atomic import ( + "context" "encoding/binary" "fmt" "time" @@ -16,14 +17,28 @@ import ( "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" ) +// Syncer represents a step in state sync, +// along with Start/Done methods to control +// and monitor progress. +// Error returns an error if any was encountered. +type Syncer interface { + Start(ctx context.Context) error + Done() <-chan error +} + var _ AtomicBackend = &atomicBackend{} +var ( + // Prefixes for atomic trie + atomicTrieDBPrefix = []byte("atomicTrieDB") + atomicTrieMetaDBPrefix = []byte("atomicTrieMetaDB") +) + // AtomicBackend abstracts the verification and processing // of atomic transactions type AtomicBackend interface { @@ -34,7 +49,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 []*atomic.Tx) (common.Hash, error) + InsertTxs(blockHash common.Hash, blockHeight uint64, parentHash common.Hash, txs []*Tx) (common.Hash, error) // Returns an AtomicState corresponding to a block hash that has been inserted // but not Accepted or Rejected yet. @@ -152,7 +167,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 := atomic.ExtractAtomicTxs(iter.Value(), true, a.codec) + txs, err := ExtractAtomicTxs(iter.Value(), true, a.codec) if err != nil { return err } @@ -397,7 +412,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 []*atomic.Tx) (common.Hash, error) { +func (a *atomicBackend) InsertTxs(blockHash common.Hash, blockHeight uint64, parentHash common.Hash, txs []*Tx) (common.Hash, error) { // access the atomic trie at the parent block parentRoot, err := a.getAtomicRootAt(parentHash) if err != nil { @@ -460,11 +475,11 @@ func (a *atomicBackend) AtomicTrie() 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) { +func mergeAtomicOps(txs []*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)) + copyTxs := make([]*Tx, len(txs)) copy(copyTxs, txs) utils.Sort(copyTxs) txs = copyTxs diff --git a/plugin/evm/atomic_state.go b/plugin/evm/atomic/atomic_state.go similarity index 97% rename from plugin/evm/atomic_state.go rename to plugin/evm/atomic/atomic_state.go index 911f1afb3a..5b64145d62 100644 --- a/plugin/evm/atomic_state.go +++ b/plugin/evm/atomic/atomic_state.go @@ -1,7 +1,7 @@ // (c) 2020-2021, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. -package evm +package atomic import ( "fmt" @@ -9,7 +9,6 @@ import ( 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" ) @@ -37,7 +36,7 @@ type atomicState struct { backend *atomicBackend blockHash common.Hash blockHeight uint64 - txs []*atomic.Tx + txs []*Tx atomicOps map[ids.ID]*avalancheatomic.Requests atomicRoot common.Hash } diff --git a/plugin/evm/atomic_syncer.go b/plugin/evm/atomic/atomic_syncer.go similarity index 99% rename from plugin/evm/atomic_syncer.go rename to plugin/evm/atomic/atomic_syncer.go index d68d61d597..daffb9d771 100644 --- a/plugin/evm/atomic_syncer.go +++ b/plugin/evm/atomic/atomic_syncer.go @@ -1,7 +1,7 @@ // (c) 2019-2022, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. -package evm +package atomic import ( "bytes" diff --git a/plugin/evm/atomic_syncer_test.go b/plugin/evm/atomic/atomic_syncer_test.go similarity index 97% rename from plugin/evm/atomic_syncer_test.go rename to plugin/evm/atomic/atomic_syncer_test.go index 86589cc4d8..140a627710 100644 --- a/plugin/evm/atomic_syncer_test.go +++ b/plugin/evm/atomic/atomic_syncer_test.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 ( "bytes" @@ -25,6 +25,7 @@ import ( "github.com/ava-labs/coreth/sync/syncutils" "github.com/ava-labs/coreth/trie" "github.com/ava-labs/coreth/triedb" + "github.com/ava-labs/coreth/utils" "github.com/ethereum/go-ethereum/common" ) @@ -56,7 +57,7 @@ func testAtomicSyncer(t *testing.T, serverTrieDB *triedb.Database, targetHeight if err != nil { t.Fatal("could not initialize atomix tx repository", err) } - atomicBackend, err := NewAtomicBackend(clientDB, testSharedMemory(), nil, repo, 0, common.Hash{}, commitInterval) + atomicBackend, err := NewAtomicBackend(clientDB, utils.TestSharedMemory(), nil, repo, 0, common.Hash{}, commitInterval) if err != nil { t.Fatal("could not initialize atomic backend", err) } diff --git a/plugin/evm/atomic_trie.go b/plugin/evm/atomic/atomic_trie.go similarity index 99% rename from plugin/evm/atomic_trie.go rename to plugin/evm/atomic/atomic_trie.go index f6add46623..bbb299a391 100644 --- a/plugin/evm/atomic_trie.go +++ b/plugin/evm/atomic/atomic_trie.go @@ -1,7 +1,7 @@ // (c) 2020-2021, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. -package evm +package atomic import ( "fmt" @@ -17,7 +17,6 @@ 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/plugin/evm/database" "github.com/ava-labs/coreth/trie" "github.com/ava-labs/coreth/trie/trienode" @@ -225,7 +224,7 @@ func (a *atomicTrie) commit(height uint64, root common.Hash) 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(atomic.CodecVersion, requests) + valueBytes, err := a.codec.Marshal(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_iterator.go b/plugin/evm/atomic/atomic_trie_iterator.go similarity index 99% rename from plugin/evm/atomic_trie_iterator.go rename to plugin/evm/atomic/atomic_trie_iterator.go index 2bdf90b581..20be76416e 100644 --- a/plugin/evm/atomic_trie_iterator.go +++ b/plugin/evm/atomic/atomic_trie_iterator.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 ( "encoding/binary" diff --git a/plugin/evm/atomic_trie_iterator_test.go b/plugin/evm/atomic/atomic_trie_iterator_test.go similarity index 83% rename from plugin/evm/atomic_trie_iterator_test.go rename to plugin/evm/atomic/atomic_trie_iterator_test.go index 50ba586ffd..8766752b28 100644 --- a/plugin/evm/atomic_trie_iterator_test.go +++ b/plugin/evm/atomic/atomic_trie_iterator_test.go @@ -1,7 +1,7 @@ // (c) 2020-2021, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. -package evm +package atomic import ( "testing" @@ -10,23 +10,17 @@ import ( "github.com/ava-labs/avalanchego/database/memdb" "github.com/ava-labs/avalanchego/database/versiondb" "github.com/ava-labs/avalanchego/ids" - "github.com/ava-labs/avalanchego/utils" + avalancheutils "github.com/ava-labs/avalanchego/utils" + "github.com/ava-labs/coreth/utils" "github.com/ethereum/go-ethereum/common" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - - "github.com/ava-labs/coreth/plugin/evm/atomic" ) -func testSharedMemory() avalancheatomic.SharedMemory { - m := avalancheatomic.NewMemory(memdb.New()) - return m.NewSharedMemory(testCChainID) -} - func TestIteratorCanIterate(t *testing.T) { lastAcceptedHeight := uint64(1000) db := versiondb.New(memdb.New()) - codec := atomic.TestTxCodec + codec := TestTxCodec repo, err := NewAtomicTxRepository(db, codec, lastAcceptedHeight) assert.NoError(t, err) @@ -38,7 +32,7 @@ func TestIteratorCanIterate(t *testing.T) { // create an atomic trie // on create it will initialize all the transactions from the above atomic repository - atomicBackend, err := NewAtomicBackend(db, testSharedMemory(), nil, repo, lastAcceptedHeight, common.Hash{}, 100) + atomicBackend, err := NewAtomicBackend(db, utils.TestSharedMemory(), nil, repo, lastAcceptedHeight, common.Hash{}, 100) assert.NoError(t, err) atomicTrie1 := atomicBackend.AtomicTrie() @@ -51,7 +45,7 @@ func TestIteratorCanIterate(t *testing.T) { // iterate on a new atomic trie to make sure there is no resident state affecting the data and the // iterator - atomicBackend2, err := NewAtomicBackend(db, testSharedMemory(), nil, repo, lastAcceptedHeight, common.Hash{}, 100) + atomicBackend2, err := NewAtomicBackend(db, utils.TestSharedMemory(), nil, repo, lastAcceptedHeight, common.Hash{}, 100) assert.NoError(t, err) atomicTrie2 := atomicBackend2.AtomicTrie() lastCommittedHash2, lastCommittedHeight2 := atomicTrie2.LastCommitted() @@ -66,7 +60,7 @@ func TestIteratorHandlesInvalidData(t *testing.T) { require := require.New(t) lastAcceptedHeight := uint64(1000) db := versiondb.New(memdb.New()) - codec := atomic.TestTxCodec + codec := TestTxCodec repo, err := NewAtomicTxRepository(db, codec, lastAcceptedHeight) require.NoError(err) @@ -79,7 +73,7 @@ func TestIteratorHandlesInvalidData(t *testing.T) { // create an atomic trie // on create it will initialize all the transactions from the above atomic repository commitInterval := uint64(100) - atomicBackend, err := NewAtomicBackend(db, testSharedMemory(), nil, repo, lastAcceptedHeight, common.Hash{}, commitInterval) + atomicBackend, err := NewAtomicBackend(db, utils.TestSharedMemory(), nil, repo, lastAcceptedHeight, common.Hash{}, commitInterval) require.NoError(err) atomicTrie := atomicBackend.AtomicTrie() @@ -94,7 +88,7 @@ func TestIteratorHandlesInvalidData(t *testing.T) { // handles an error when it runs into an unexpected key-value pair in the trie. atomicTrieSnapshot, err := atomicTrie.OpenTrie(lastCommittedHash) require.NoError(err) - require.NoError(atomicTrieSnapshot.Update(utils.RandomBytes(50), utils.RandomBytes(50))) + require.NoError(atomicTrieSnapshot.Update(avalancheutils.RandomBytes(50), avalancheutils.RandomBytes(50))) nextRoot, nodes, err := atomicTrieSnapshot.Commit(false) require.NoError(err) diff --git a/plugin/evm/atomic_trie_test.go b/plugin/evm/atomic/atomic_trie_test.go similarity index 79% rename from plugin/evm/atomic_trie_test.go rename to plugin/evm/atomic/atomic_trie_test.go index 2a82964e94..4c9c84468a 100644 --- a/plugin/evm/atomic_trie_test.go +++ b/plugin/evm/atomic/atomic_trie_test.go @@ -1,7 +1,7 @@ // (c) 2020-2021, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. -package evm +package atomic import ( "encoding/binary" @@ -19,21 +19,13 @@ 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/ava-labs/coreth/utils" "github.com/ethereum/go-ethereum/common" ) const testCommitInterval = 100 -func mustAtomicOps(tx *atomic.Tx) map[ids.ID]*avalancheatomic.Requests { - id, reqs, err := tx.AtomicOps() - if err != nil { - panic(err) - } - 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. @@ -139,7 +131,7 @@ func TestAtomicTrieInitialize(t *testing.T) { } { t.Run(name, func(t *testing.T) { db := versiondb.New(memdb.New()) - codec := atomic.TestTxCodec + codec := TestTxCodec repo, err := NewAtomicTxRepository(db, codec, test.lastAcceptedHeight) if err != nil { t.Fatal(err) @@ -148,7 +140,7 @@ func TestAtomicTrieInitialize(t *testing.T) { writeTxs(t, repo, 1, test.lastAcceptedHeight+1, test.numTxsPerBlock, nil, operationsMap) // Construct the atomic trie for the first time - atomicBackend1, err := NewAtomicBackend(db, testSharedMemory(), nil, repo, test.lastAcceptedHeight, common.Hash{}, test.commitInterval) + atomicBackend1, err := NewAtomicBackend(db, utils.TestSharedMemory(), nil, repo, test.lastAcceptedHeight, common.Hash{}, test.commitInterval) if err != nil { t.Fatal(err) } @@ -164,7 +156,7 @@ func TestAtomicTrieInitialize(t *testing.T) { verifyOperations(t, atomicTrie1, codec, rootHash1, 1, test.expectedCommitHeight, operationsMap) // Construct the atomic trie again (on the same database) and ensure the last accepted root is correct. - atomicBackend2, err := NewAtomicBackend(db, testSharedMemory(), nil, repo, test.lastAcceptedHeight, common.Hash{}, test.commitInterval) + atomicBackend2, err := NewAtomicBackend(db, utils.TestSharedMemory(), nil, repo, test.lastAcceptedHeight, common.Hash{}, test.commitInterval) if err != nil { t.Fatal(err) } @@ -173,7 +165,7 @@ func TestAtomicTrieInitialize(t *testing.T) { // Construct the atomic trie again (on an empty database) and ensure that it produces the same hash. atomicBackend3, err := NewAtomicBackend( - versiondb.New(memdb.New()), testSharedMemory(), nil, repo, test.lastAcceptedHeight, common.Hash{}, test.commitInterval, + versiondb.New(memdb.New()), utils.TestSharedMemory(), nil, repo, test.lastAcceptedHeight, common.Hash{}, test.commitInterval, ) if err != nil { t.Fatal(err) @@ -188,7 +180,7 @@ func TestAtomicTrieInitialize(t *testing.T) { // during the initialization phase will cause an invalid root when indexing continues. nextCommitHeight := nearestCommitHeight(test.lastAcceptedHeight+test.commitInterval, test.commitInterval) for i := test.lastAcceptedHeight + 1; i <= nextCommitHeight; i++ { - txs := atomic.NewTestTxs(test.numTxsPerBlock(i)) + txs := NewTestTxs(test.numTxsPerBlock(i)) if err := repo.Write(i, txs); err != nil { t.Fatal(err) } @@ -211,7 +203,7 @@ func TestAtomicTrieInitialize(t *testing.T) { // Generate a new atomic trie to compare the root against. atomicBackend4, err := NewAtomicBackend( - versiondb.New(memdb.New()), testSharedMemory(), nil, repo, nextCommitHeight, common.Hash{}, test.commitInterval, + versiondb.New(memdb.New()), utils.TestSharedMemory(), nil, repo, nextCommitHeight, common.Hash{}, test.commitInterval, ) if err != nil { t.Fatal(err) @@ -228,14 +220,14 @@ func TestAtomicTrieInitialize(t *testing.T) { func TestIndexerInitializesOnlyOnce(t *testing.T) { lastAcceptedHeight := uint64(25) db := versiondb.New(memdb.New()) - codec := atomic.TestTxCodec + codec := TestTxCodec repo, err := NewAtomicTxRepository(db, codec, lastAcceptedHeight) assert.NoError(t, err) operationsMap := make(map[uint64]map[ids.ID]*avalancheatomic.Requests) writeTxs(t, repo, 1, lastAcceptedHeight+1, constTxsPerHeight(2), nil, operationsMap) // Initialize atomic repository - atomicBackend, err := NewAtomicBackend(db, testSharedMemory(), nil, repo, lastAcceptedHeight, common.Hash{}, 10 /* commitInterval*/) + atomicBackend, err := NewAtomicBackend(db, utils.TestSharedMemory(), nil, repo, lastAcceptedHeight, common.Hash{}, 10 /* commitInterval*/) assert.NoError(t, err) atomicTrie := atomicBackend.AtomicTrie() @@ -247,11 +239,11 @@ 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, []*atomic.Tx{atomic.GenerateTestExportTx()}) + err = repo.Write(15, []*Tx{GenerateTestExportTx()}) assert.NoError(t, err) // Re-initialize the atomic trie - atomicBackend, err = NewAtomicBackend(db, testSharedMemory(), nil, repo, lastAcceptedHeight, common.Hash{}, 10 /* commitInterval */) + atomicBackend, err = NewAtomicBackend(db, utils.TestSharedMemory(), nil, repo, lastAcceptedHeight, common.Hash{}, 10 /* commitInterval */) assert.NoError(t, err) atomicTrie = atomicBackend.AtomicTrie() @@ -262,11 +254,11 @@ func TestIndexerInitializesOnlyOnce(t *testing.T) { func newTestAtomicTrie(t *testing.T) AtomicTrie { db := versiondb.New(memdb.New()) - repo, err := NewAtomicTxRepository(db, atomic.TestTxCodec, 0) + repo, err := NewAtomicTxRepository(db, TestTxCodec, 0) if err != nil { t.Fatal(err) } - atomicBackend, err := NewAtomicBackend(db, testSharedMemory(), nil, repo, 0, common.Hash{}, testCommitInterval) + atomicBackend, err := NewAtomicBackend(db, utils.TestSharedMemory(), nil, repo, 0, common.Hash{}, testCommitInterval) if err != nil { t.Fatal(err) } @@ -282,8 +274,9 @@ 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 := mustAtomicOps(atomic.GenerateTestImportTx()) - err := indexAtomicTxs(atomicTrie, height, atomicRequests) + atomicRequests, err := ConvertToAtomicOps(GenerateTestImportTx()) + assert.NoError(t, err) + err = indexAtomicTxs(atomicTrie, height, atomicRequests) assert.NoError(t, err) if height%testCommitInterval == 0 { lastCommittedBlockHash, lastCommittedBlockHeight = atomicTrie.LastCommitted() @@ -313,11 +306,11 @@ func TestAtomicOpsAreNotTxOrderDependent(t *testing.T) { atomicTrie2 := newTestAtomicTrie(t) for height := uint64(0); height <= testCommitInterval; /*=205*/ height++ { - tx1 := atomic.GenerateTestImportTx() - tx2 := atomic.GenerateTestImportTx() - atomicRequests1, err := mergeAtomicOps([]*atomic.Tx{tx1, tx2}) + tx1 := GenerateTestImportTx() + tx2 := GenerateTestImportTx() + atomicRequests1, err := mergeAtomicOps([]*Tx{tx1, tx2}) assert.NoError(t, err) - atomicRequests2, err := mergeAtomicOps([]*atomic.Tx{tx2, tx1}) + atomicRequests2, err := mergeAtomicOps([]*Tx{tx2, tx1}) assert.NoError(t, err) err = indexAtomicTxs(atomicTrie1, height, atomicRequests1) @@ -339,7 +332,7 @@ func TestAtomicTrieDoesNotSkipBonusBlocks(t *testing.T) { commitInterval := uint64(10) expectedCommitHeight := uint64(100) db := versiondb.New(memdb.New()) - codec := atomic.TestTxCodec + codec := TestTxCodec repo, err := NewAtomicTxRepository(db, codec, lastAcceptedHeight) if err != nil { t.Fatal(err) @@ -353,7 +346,7 @@ func TestAtomicTrieDoesNotSkipBonusBlocks(t *testing.T) { 14: {}, } // Construct the atomic trie for the first time - atomicBackend, err := NewAtomicBackend(db, testSharedMemory(), bonusBlocks, repo, lastAcceptedHeight, common.Hash{}, commitInterval) + atomicBackend, err := NewAtomicBackend(db, utils.TestSharedMemory(), bonusBlocks, repo, lastAcceptedHeight, common.Hash{}, commitInterval) if err != nil { t.Fatal(err) } @@ -371,7 +364,9 @@ func TestIndexingNilShouldNotImpactTrie(t *testing.T) { // operations to index ops := make([]map[ids.ID]*avalancheatomic.Requests, 0) for i := 0; i <= testCommitInterval; i++ { - ops = append(ops, mustAtomicOps(atomic.GenerateTestImportTx())) + atomicOps, err := ConvertToAtomicOps(GenerateTestImportTx()) + assert.NoError(t, err) + ops = append(ops, atomicOps) } // without nils @@ -411,75 +406,6 @@ func TestIndexingNilShouldNotImpactTrie(t *testing.T) { assert.Equal(t, root1, root2) } -type sharedMemories struct { - thisChain avalancheatomic.SharedMemory - peerChain avalancheatomic.SharedMemory - thisChainID ids.ID - peerChainID ids.ID -} - -func (s *sharedMemories) addItemsToBeRemovedToPeerChain(ops map[ids.ID]*avalancheatomic.Requests) error { - for _, reqs := range ops { - 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, &avalancheatomic.Element{Key: key, Value: val}) - } - if err := s.peerChain.Apply(puts); err != nil { - return err - } - } - return nil -} - -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 - for _, elem := range reqs.PutRequests { - val, err := s.peerChain.Get(s.thisChainID, [][]byte{elem.Key}) - if err != nil { - t.Fatalf("error finding puts in peerChainMemory: %s", err) - } - assert.Equal(t, elem.Value, val[0]) - } - - // should not be able to get remove requests - for _, key := range reqs.RemoveRequests { - _, err := s.thisChain.Get(s.peerChainID, [][]byte{key}) - assert.EqualError(t, err, "not found") - } - } -} - -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 - for _, elem := range reqs.PutRequests { - _, err := s.peerChain.Get(s.thisChainID, [][]byte{elem.Key}) - assert.EqualError(t, err, "not found") - } - - // should be able to get remove requests (these were previously added as puts on peerChain) - for _, key := range reqs.RemoveRequests { - val, err := s.thisChain.Get(s.peerChainID, [][]byte{key}) - assert.NoError(t, err) - assert.Equal(t, []byte{0x1}, val[0]) - } - } -} - -func newSharedMemories(atomicMemory *avalancheatomic.Memory, thisChainID, peerChainID ids.ID) *sharedMemories { - return &sharedMemories{ - thisChain: atomicMemory.NewSharedMemory(thisChainID), - peerChain: atomicMemory.NewSharedMemory(peerChainID), - thisChainID: thisChainID, - peerChainID: peerChainID, - } -} - func TestApplyToSharedMemory(t *testing.T) { type test struct { commitInterval, lastAcceptedHeight uint64 @@ -511,9 +437,9 @@ func TestApplyToSharedMemory(t *testing.T) { commitInterval: 10, lastAcceptedHeight: 25, setMarker: func(a *atomicBackend) error { - cursor := make([]byte, wrappers.LongLen+len(atomic.TestBlockchainID[:])) + cursor := make([]byte, wrappers.LongLen+len(TestBlockchainID[:])) binary.BigEndian.PutUint64(cursor, 10) - copy(cursor[wrappers.LongLen:], atomic.TestBlockchainID[:]) + copy(cursor[wrappers.LongLen:], TestBlockchainID[:]) return a.metadataDB.Put(appliedSharedMemoryCursorKey, cursor) }, expectOpsApplied: func(height uint64) bool { return height > 10 && height <= 20 }, @@ -527,7 +453,7 @@ func TestApplyToSharedMemory(t *testing.T) { } { t.Run(name, func(t *testing.T) { db := versiondb.New(memdb.New()) - codec := atomic.TestTxCodec + codec := TestTxCodec repo, err := NewAtomicTxRepository(db, codec, test.lastAcceptedHeight) assert.NoError(t, err) operationsMap := make(map[uint64]map[ids.ID]*avalancheatomic.Requests) @@ -535,7 +461,7 @@ func TestApplyToSharedMemory(t *testing.T) { // Initialize atomic repository m := avalancheatomic.NewMemory(db) - sharedMemories := newSharedMemories(m, testCChainID, atomic.TestBlockchainID) + sharedMemories := NewSharedMemories(m, ids.GenerateTestID(), TestBlockchainID) backend, err := NewAtomicBackend(db, sharedMemories.thisChain, test.bonusBlockHeights, repo, test.lastAcceptedHeight, common.Hash{}, test.commitInterval) assert.NoError(t, err) atomicTrie := backend.AtomicTrie().(*atomicTrie) @@ -558,7 +484,7 @@ func TestApplyToSharedMemory(t *testing.T) { // assert ops were applied as expected for height, ops := range operationsMap { if test.expectOpsApplied(height) { - sharedMemories.assertOpsApplied(t, ops) + sharedMemories.AssertOpsApplied(t, ops) } else { sharedMemories.assertOpsNotApplied(t, ops) } @@ -577,7 +503,7 @@ func TestApplyToSharedMemory(t *testing.T) { // assert they are as they were prior to reinitializing for height, ops := range operationsMap { if test.expectOpsApplied(height) { - sharedMemories.assertOpsApplied(t, ops) + sharedMemories.AssertOpsApplied(t, ops) } else { sharedMemories.assertOpsNotApplied(t, ops) } @@ -593,7 +519,7 @@ func TestApplyToSharedMemory(t *testing.T) { func BenchmarkAtomicTrieInit(b *testing.B) { db := versiondb.New(memdb.New()) - codec := atomic.TestTxCodec + codec := TestTxCodec operationsMap := make(map[uint64]map[ids.ID]*avalancheatomic.Requests) @@ -611,7 +537,7 @@ func BenchmarkAtomicTrieInit(b *testing.B) { b.ReportAllocs() b.ResetTimer() for i := 0; i < b.N; i++ { - sharedMemory := testSharedMemory() + sharedMemory := utils.TestSharedMemory() atomicBackend, err := NewAtomicBackend(db, sharedMemory, nil, repo, lastAcceptedHeight, common.Hash{}, 5000) assert.NoError(b, err) atomicTrie = atomicBackend.AtomicTrie() @@ -628,7 +554,7 @@ func BenchmarkAtomicTrieInit(b *testing.B) { func BenchmarkAtomicTrieIterate(b *testing.B) { db := versiondb.New(memdb.New()) - codec := atomic.TestTxCodec + codec := TestTxCodec operationsMap := make(map[uint64]map[ids.ID]*avalancheatomic.Requests) @@ -638,7 +564,7 @@ func BenchmarkAtomicTrieIterate(b *testing.B) { assert.NoError(b, err) writeTxs(b, repo, 1, lastAcceptedHeight, constTxsPerHeight(3), nil, operationsMap) - atomicBackend, err := NewAtomicBackend(db, testSharedMemory(), nil, repo, lastAcceptedHeight, common.Hash{}, 5000) + atomicBackend, err := NewAtomicBackend(db, utils.TestSharedMemory(), nil, repo, lastAcceptedHeight, common.Hash{}, 5000) assert.NoError(b, err) atomicTrie := atomicBackend.AtomicTrie() @@ -707,8 +633,8 @@ func BenchmarkApplyToSharedMemory(b *testing.B) { func benchmarkApplyToSharedMemory(b *testing.B, disk database.Database, blocks uint64) { db := versiondb.New(disk) - codec := atomic.TestTxCodec - sharedMemory := testSharedMemory() + codec := TestTxCodec + sharedMemory := utils.TestSharedMemory() lastAcceptedHeight := blocks repo, err := NewAtomicTxRepository(db, codec, lastAcceptedHeight) @@ -720,7 +646,7 @@ func benchmarkApplyToSharedMemory(b *testing.B, disk database.Database, blocks u } trie := backend.AtomicTrie() for height := uint64(1); height <= lastAcceptedHeight; height++ { - txs := atomic.NewTestTxs(constTxsPerHeight(3)(height)) + txs := NewTestTxs(constTxsPerHeight(3)(height)) ops, err := mergeAtomicOps(txs) assert.NoError(b, err) assert.NoError(b, indexAtomicTxs(trie, height, ops)) @@ -733,7 +659,7 @@ func benchmarkApplyToSharedMemory(b *testing.B, disk database.Database, blocks u b.ReportAllocs() b.ResetTimer() for i := 0; i < b.N; i++ { - backend.(*atomicBackend).sharedMemory = testSharedMemory() + backend.(*atomicBackend).sharedMemory = utils.TestSharedMemory() assert.NoError(b, backend.MarkApplyToSharedMemoryCursor(0)) assert.NoError(b, db.Commit()) assert.NoError(b, backend.ApplyToSharedMemory(lastAcceptedHeight)) diff --git a/plugin/evm/atomic_tx_repository.go b/plugin/evm/atomic/atomic_tx_repository.go similarity index 91% rename from plugin/evm/atomic_tx_repository.go rename to plugin/evm/atomic/atomic_tx_repository.go index d1074f60f2..d451ce9d86 100644 --- a/plugin/evm/atomic_tx_repository.go +++ b/plugin/evm/atomic/atomic_tx_repository.go @@ -1,7 +1,7 @@ // (c) 2020-2021, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. -package evm +package atomic import ( "encoding/binary" @@ -19,7 +19,6 @@ 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 ( @@ -40,10 +39,10 @@ var ( // atomic transactions type AtomicTxRepository interface { GetIndexHeight() (uint64, 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 + GetByTxID(txID ids.ID) (*Tx, uint64, error) + GetByHeight(height uint64) ([]*Tx, error) + Write(height uint64, txs []*Tx) error + WriteBonus(height uint64, txs []*Tx) error IterateByHeight(start uint64) database.Iterator Codec() codec.Manager @@ -137,7 +136,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 := atomic.ExtractAtomicTx(txBytes, a.codec) + tx, err := ExtractAtomicTx(txBytes, a.codec) if err != nil { return err } @@ -199,10 +198,10 @@ func (a *atomicTxRepository) GetIndexHeight() (uint64, error) { return indexHeight, nil } -// GetByTxID queries [acceptedAtomicTxDB] for the [txID], parses a [*atomic.Tx] object +// GetByTxID queries [acceptedAtomicTxDB] for the [txID], parses a [*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) (*atomic.Tx, uint64, error) { +func (a *atomicTxRepository) GetByTxID(txID ids.ID) (*Tx, uint64, error) { indexedTxBytes, err := a.acceptedAtomicTxDB.Get(txID[:]) if err != nil { return nil, 0, err @@ -216,7 +215,7 @@ func (a *atomicTxRepository) GetByTxID(txID ids.ID) (*atomic.Tx, uint64, error) packer := wrappers.Packer{Bytes: indexedTxBytes} height := packer.UnpackLong() txBytes := packer.UnpackBytes() - tx, err := atomic.ExtractAtomicTx(txBytes, a.codec) + tx, err := ExtractAtomicTx(txBytes, a.codec) if err != nil { return nil, 0, err } @@ -230,40 +229,40 @@ func (a *atomicTxRepository) GetByTxID(txID ids.ID) (*atomic.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) ([]*atomic.Tx, error) { +func (a *atomicTxRepository) GetByHeight(height uint64) ([]*Tx, error) { heightBytes := make([]byte, wrappers.LongLen) binary.BigEndian.PutUint64(heightBytes, height) return a.getByHeightBytes(heightBytes) } -func (a *atomicTxRepository) getByHeightBytes(heightBytes []byte) ([]*atomic.Tx, error) { +func (a *atomicTxRepository) getByHeightBytes(heightBytes []byte) ([]*Tx, error) { txsBytes, err := a.acceptedAtomicTxByHeightDB.Get(heightBytes) if err != nil { return nil, err } - return atomic.ExtractAtomicTxsBatch(txsBytes, a.codec) + return 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 []*atomic.Tx) error { +func (a *atomicTxRepository) Write(height uint64, txs []*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 []*atomic.Tx) error { +func (a *atomicTxRepository) WriteBonus(height uint64, txs []*Tx) error { return a.write(height, txs, true) } -func (a *atomicTxRepository) write(height uint64, txs []*atomic.Tx, bonus bool) error { +func (a *atomicTxRepository) write(height uint64, txs []*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([]*atomic.Tx, len(txs)) + copyTxs := make([]*Tx, len(txs)) copy(copyTxs, txs) utils.Sort(copyTxs) txs = copyTxs @@ -301,8 +300,8 @@ func (a *atomicTxRepository) write(height uint64, txs []*atomic.Tx, bonus bool) // indexTxByID writes [tx] into the [acceptedAtomicTxDB] stored as // [height] + [tx bytes] -func (a *atomicTxRepository) indexTxByID(heightBytes []byte, tx *atomic.Tx) error { - txBytes, err := a.codec.Marshal(atomic.CodecVersion, tx) +func (a *atomicTxRepository) indexTxByID(heightBytes []byte, tx *Tx) error { + txBytes, err := a.codec.Marshal(CodecVersion, tx) if err != nil { return err } @@ -321,8 +320,8 @@ func (a *atomicTxRepository) indexTxByID(heightBytes []byte, tx *atomic.Tx) erro } // indexTxsAtHeight adds [height] -> [txs] to the [acceptedAtomicTxByHeightDB] -func (a *atomicTxRepository) indexTxsAtHeight(heightBytes []byte, txs []*atomic.Tx) error { - txsBytes, err := a.codec.Marshal(atomic.CodecVersion, txs) +func (a *atomicTxRepository) indexTxsAtHeight(heightBytes []byte, txs []*Tx) error { + txsBytes, err := a.codec.Marshal(CodecVersion, txs) if err != nil { return err } @@ -336,7 +335,7 @@ func (a *atomicTxRepository) indexTxsAtHeight(heightBytes []byte, txs []*atomic. // [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 *atomic.Tx) error { +func (a *atomicTxRepository) appendTxToHeightIndex(heightBytes []byte, tx *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/atomic_tx_repository_test.go similarity index 91% rename from plugin/evm/atomic_tx_repository_test.go rename to plugin/evm/atomic/atomic_tx_repository_test.go index 224f8fa726..4dda70ab10 100644 --- a/plugin/evm/atomic_tx_repository_test.go +++ b/plugin/evm/atomic/atomic_tx_repository_test.go @@ -1,7 +1,7 @@ // (c) 2020-2021, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. -package evm +package atomic import ( "encoding/binary" @@ -12,7 +12,6 @@ import ( "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" @@ -28,13 +27,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][]*atomic.Tx, operationsMap map[uint64]map[ids.ID]*avalancheatomic.Requests) { +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]*avalancheatomic.Requests) { for height := fromHeight; height < toHeight; height++ { - txs := make([]*atomic.Tx, 0, txsPerHeight) + txs := make([]*Tx, 0, txsPerHeight) for i := 0; i < txsPerHeight; i++ { - tx := atomic.NewTestTx() + tx := NewTestTx() txs = append(txs, tx) - txBytes, err := codec.Marshal(atomic.CodecVersion, tx) + txBytes, err := codec.Marshal(CodecVersion, tx) assert.NoError(t, err) // Write atomic transactions to the [acceptedAtomicTxDB] @@ -71,10 +70,10 @@ 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][]*atomic.Tx, operationsMap map[uint64]map[ids.ID]*avalancheatomic.Requests, + txsPerHeight func(height uint64) int, txMap map[uint64][]*Tx, operationsMap map[uint64]map[ids.ID]*avalancheatomic.Requests, ) { for height := fromHeight; height < toHeight; height++ { - txs := atomic.NewTestTxs(txsPerHeight(height)) + txs := NewTestTxs(txsPerHeight(height)) if err := repo.Write(height, txs); err != nil { t.Fatal(err) } @@ -96,7 +95,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][]*atomic.Tx) { +func verifyTxs(t testing.TB, repo AtomicTxRepository, txMap map[uint64][]*Tx) { // We should be able to fetch indexed txs by height: for height, expectedTxs := range txMap { txs, err := repo.GetByHeight(height) @@ -183,12 +182,12 @@ func verifyOperations(t testing.TB, atomicTrie AtomicTrie, codec codec.Manager, func TestAtomicRepositoryReadWriteSingleTx(t *testing.T) { db := versiondb.New(memdb.New()) - codec := atomic.TestTxCodec + codec := TestTxCodec repo, err := NewAtomicTxRepository(db, codec, 0) if err != nil { t.Fatal(err) } - txMap := make(map[uint64][]*atomic.Tx) + txMap := make(map[uint64][]*Tx) writeTxs(t, repo, 1, 100, constTxsPerHeight(1), txMap, nil) verifyTxs(t, repo, txMap) @@ -196,12 +195,12 @@ func TestAtomicRepositoryReadWriteSingleTx(t *testing.T) { func TestAtomicRepositoryReadWriteMultipleTxs(t *testing.T) { db := versiondb.New(memdb.New()) - codec := atomic.TestTxCodec + codec := TestTxCodec repo, err := NewAtomicTxRepository(db, codec, 0) if err != nil { t.Fatal(err) } - txMap := make(map[uint64][]*atomic.Tx) + txMap := make(map[uint64][]*Tx) writeTxs(t, repo, 1, 100, constTxsPerHeight(10), txMap, nil) verifyTxs(t, repo, txMap) @@ -209,10 +208,10 @@ func TestAtomicRepositoryReadWriteMultipleTxs(t *testing.T) { func TestAtomicRepositoryPreAP5Migration(t *testing.T) { db := versiondb.New(memdb.New()) - codec := atomic.TestTxCodec + codec := TestTxCodec acceptedAtomicTxDB := prefixdb.New(atomicTxIDDBPrefix, db) - txMap := make(map[uint64][]*atomic.Tx) + txMap := make(map[uint64][]*Tx) addTxs(t, codec, acceptedAtomicTxDB, 1, 100, 1, txMap, nil) if err := db.Commit(); err != nil { t.Fatal(err) @@ -234,10 +233,10 @@ func TestAtomicRepositoryPreAP5Migration(t *testing.T) { func TestAtomicRepositoryPostAP5Migration(t *testing.T) { db := versiondb.New(memdb.New()) - codec := atomic.TestTxCodec + codec := TestTxCodec acceptedAtomicTxDB := prefixdb.New(atomicTxIDDBPrefix, db) - txMap := make(map[uint64][]*atomic.Tx) + txMap := make(map[uint64][]*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 { @@ -259,10 +258,10 @@ func TestAtomicRepositoryPostAP5Migration(t *testing.T) { func benchAtomicRepositoryIndex10_000(b *testing.B, maxHeight uint64, txsPerHeight int) { db := versiondb.New(memdb.New()) - codec := atomic.TestTxCodec + codec := TestTxCodec acceptedAtomicTxDB := prefixdb.New(atomicTxIDDBPrefix, db) - txMap := make(map[uint64][]*atomic.Tx) + txMap := make(map[uint64][]*Tx) addTxs(b, codec, acceptedAtomicTxDB, 0, maxHeight, txsPerHeight, txMap, nil) if err := db.Commit(); err != nil { diff --git a/plugin/evm/atomic/export_tx.go b/plugin/evm/atomic/export_tx.go index 26307cface..a07b4f4fee 100644 --- a/plugin/evm/atomic/export_tx.go +++ b/plugin/evm/atomic/export_tx.go @@ -180,7 +180,7 @@ func (utx *UnsignedExportTx) Burned(assetID ids.ID) (uint64, error) { // SemanticVerify this transaction is valid. func (utx *UnsignedExportTx) SemanticVerify( - backend *Backend, + backend *VerifierBackend, stx *Tx, parent AtomicBlockContext, baseFee *big.Int, diff --git a/plugin/evm/atomic/import_tx.go b/plugin/evm/atomic/import_tx.go index 0d4d367d4e..76799dabaf 100644 --- a/plugin/evm/atomic/import_tx.go +++ b/plugin/evm/atomic/import_tx.go @@ -188,7 +188,7 @@ func (utx *UnsignedImportTx) Burned(assetID ids.ID) (uint64, error) { // SemanticVerify this transaction is valid. func (utx *UnsignedImportTx) SemanticVerify( - backend *Backend, + backend *VerifierBackend, stx *Tx, parent AtomicBlockContext, baseFee *big.Int, @@ -443,7 +443,7 @@ func (utx *UnsignedImportTx) EVMStateTransfer(ctx *snow.Context, state StateDB) // 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 { +func conflicts(backend *VerifierBackend, inputs set.Set[ids.ID], ancestor AtomicBlockContext) error { fetcher := backend.BlockFetcher lastAcceptedBlock := fetcher.LastAcceptedBlockInternal() lastAcceptedHeight := lastAcceptedBlock.Height() diff --git a/plugin/evm/atomic/test_shared_memories.go b/plugin/evm/atomic/test_shared_memories.go new file mode 100644 index 0000000000..f526748b03 --- /dev/null +++ b/plugin/evm/atomic/test_shared_memories.go @@ -0,0 +1,78 @@ +package atomic + +import ( + "testing" + + "github.com/ava-labs/avalanchego/chains/atomic" + "github.com/ava-labs/avalanchego/ids" + "github.com/stretchr/testify/assert" +) + +type SharedMemories struct { + thisChain atomic.SharedMemory + peerChain atomic.SharedMemory + thisChainID ids.ID + peerChainID ids.ID +} + +func (s *SharedMemories) addItemsToBeRemovedToPeerChain(ops map[ids.ID]*atomic.Requests) error { + for _, reqs := range ops { + puts := make(map[ids.ID]*atomic.Requests) + puts[s.thisChainID] = &atomic.Requests{} + for _, key := range reqs.RemoveRequests { + val := []byte{0x1} + puts[s.thisChainID].PutRequests = append(puts[s.thisChainID].PutRequests, &atomic.Element{Key: key, Value: val}) + } + if err := s.peerChain.Apply(puts); err != nil { + return err + } + } + return nil +} + +func (s *SharedMemories) AssertOpsApplied(t *testing.T, ops map[ids.ID]*atomic.Requests) { + t.Helper() + for _, reqs := range ops { + // should be able to get put requests + for _, elem := range reqs.PutRequests { + val, err := s.peerChain.Get(s.thisChainID, [][]byte{elem.Key}) + if err != nil { + t.Fatalf("error finding puts in peerChainMemory: %s", err) + } + assert.Equal(t, elem.Value, val[0]) + } + + // should not be able to get remove requests + for _, key := range reqs.RemoveRequests { + _, err := s.thisChain.Get(s.peerChainID, [][]byte{key}) + assert.EqualError(t, err, "not found") + } + } +} + +func (s *SharedMemories) assertOpsNotApplied(t *testing.T, ops map[ids.ID]*atomic.Requests) { + t.Helper() + for _, reqs := range ops { + // should not be able to get put requests + for _, elem := range reqs.PutRequests { + _, err := s.peerChain.Get(s.thisChainID, [][]byte{elem.Key}) + assert.EqualError(t, err, "not found") + } + + // should be able to get remove requests (these were previously added as puts on peerChain) + for _, key := range reqs.RemoveRequests { + val, err := s.thisChain.Get(s.peerChainID, [][]byte{key}) + assert.NoError(t, err) + assert.Equal(t, []byte{0x1}, val[0]) + } + } +} + +func NewSharedMemories(atomicMemory *atomic.Memory, thisChainID, peerChainID ids.ID) *SharedMemories { + return &SharedMemories{ + thisChain: atomicMemory.NewSharedMemory(thisChainID), + peerChain: atomicMemory.NewSharedMemory(peerChainID), + thisChainID: thisChainID, + peerChainID: peerChainID, + } +} diff --git a/plugin/evm/atomic/test_tx.go b/plugin/evm/atomic/test_tx.go index 50af59e09f..21adecfd39 100644 --- a/plugin/evm/atomic/test_tx.go +++ b/plugin/evm/atomic/test_tx.go @@ -85,7 +85,7 @@ 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(backend *Backend, stx *Tx, parent AtomicBlockContext, baseFee *big.Int) error { +func (t *TestUnsignedTx) SemanticVerify(backend *VerifierBackend, stx *Tx, parent AtomicBlockContext, baseFee *big.Int) error { return t.SemanticVerifyV } diff --git a/plugin/evm/atomic/tx.go b/plugin/evm/atomic/tx.go index a911402dea..5c44b61937 100644 --- a/plugin/evm/atomic/tx.go +++ b/plugin/evm/atomic/tx.go @@ -127,7 +127,7 @@ type UnsignedTx interface { SignedBytes() []byte } -type Backend struct { +type VerifierBackend struct { Ctx *snow.Context Fx fx.Fx Rules params.Rules @@ -170,7 +170,7 @@ type UnsignedAtomicTx interface { Verify(ctx *snow.Context, rules params.Rules) error // Attempts to verify this transaction with the provided state. // SemanticVerify this transaction is valid. - SemanticVerify(backend *Backend, stx *Tx, parent AtomicBlockContext, baseFee *big.Int) error + SemanticVerify(backend *VerifierBackend, 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. diff --git a/plugin/evm/atomic/utils.go b/plugin/evm/atomic/utils.go index 8872e09861..133b185930 100644 --- a/plugin/evm/atomic/utils.go +++ b/plugin/evm/atomic/utils.go @@ -6,6 +6,8 @@ package atomic import ( "errors" + avalancheatomic "github.com/ava-labs/avalanchego/chains/atomic" + "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/utils/crypto/secp256k1" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" @@ -30,3 +32,11 @@ func GetEthAddress(privKey *secp256k1.PrivateKey) common.Address { func PublicKeyToEthAddress(pubKey *secp256k1.PublicKey) common.Address { return crypto.PubkeyToAddress(*(pubKey.ToECDSA())) } + +func ConvertToAtomicOps(tx *Tx) (map[ids.ID]*avalancheatomic.Requests, error) { + id, reqs, err := tx.AtomicOps() + if err != nil { + return nil, err + } + return map[ids.ID]*avalancheatomic.Requests{id: reqs}, nil +} diff --git a/plugin/evm/block.go b/plugin/evm/block.go index 9a2de32601..ffadf284f3 100644 --- a/plugin/evm/block.go +++ b/plugin/evm/block.go @@ -158,7 +158,7 @@ func (b *Block) Accept(context.Context) error { return fmt.Errorf("chain could not accept %s: %w", b.ID(), err) } - if err := vm.acceptedBlockDB.Put(lastAcceptedKey, b.id[:]); err != nil { + if err := vm.PutLastAcceptedID(b.id[:]); err != nil { return fmt.Errorf("failed to put %s as the last accepted block: %w", b.ID(), err) } @@ -428,3 +428,7 @@ func (b *Block) Bytes() []byte { } func (b *Block) String() string { return fmt.Sprintf("EVM block, ID = %s", b.ID()) } + +func (b *Block) GetEthBlock() *types.Block { + return b.ethBlock +} diff --git a/plugin/evm/export_tx_test.go b/plugin/evm/export_tx_test.go index 9bc1f498a9..4bc0999797 100644 --- a/plugin/evm/export_tx_test.go +++ b/plugin/evm/export_tx_test.go @@ -19,6 +19,7 @@ import ( "github.com/ava-labs/avalanchego/vms/secp256k1fx" "github.com/ava-labs/coreth/params" "github.com/ava-labs/coreth/plugin/evm/atomic" + "github.com/ava-labs/coreth/utils" "github.com/ethereum/go-ethereum/common" "github.com/holiman/uint256" ) @@ -155,7 +156,7 @@ func TestExportTxEVMStateTransfer(t *testing.T) { { Address: ethAddr, Amount: avaxAmount / 2, - AssetID: testAvaxAssetID, + AssetID: utils.TestAvaxAssetID, Nonce: 0, }, }, @@ -172,7 +173,7 @@ func TestExportTxEVMStateTransfer(t *testing.T) { { Address: ethAddr, Amount: avaxAmount, - AssetID: testAvaxAssetID, + AssetID: utils.TestAvaxAssetID, Nonce: 0, }, }, @@ -189,7 +190,7 @@ func TestExportTxEVMStateTransfer(t *testing.T) { { Address: ethAddr, Amount: avaxAmount + 1, - AssetID: testAvaxAssetID, + AssetID: utils.TestAvaxAssetID, Nonce: 0, }, }, @@ -263,7 +264,7 @@ func TestExportTxEVMStateTransfer(t *testing.T) { { Address: ethAddr, Amount: avaxAmount, - AssetID: testAvaxAssetID, + AssetID: utils.TestAvaxAssetID, Nonce: 0, }, }, @@ -286,7 +287,7 @@ func TestExportTxEVMStateTransfer(t *testing.T) { { Address: ethAddr, Amount: avaxAmount, - AssetID: testAvaxAssetID, + AssetID: utils.TestAvaxAssetID, Nonce: 1, }, }, @@ -309,7 +310,7 @@ func TestExportTxEVMStateTransfer(t *testing.T) { { Address: ethAddr, Amount: avaxAmount, - AssetID: testAvaxAssetID, + AssetID: utils.TestAvaxAssetID, Nonce: 1, }, }, @@ -912,7 +913,7 @@ func TestExportTxSemanticVerify(t *testing.T) { t.Fatal(err) } - backend := &atomic.Backend{ + backend := &atomic.VerifierBackend{ Ctx: vm.ctx, Fx: &vm.fx, Rules: test.rules, @@ -1087,27 +1088,28 @@ func TestExportTxAccept(t *testing.T) { func TestExportTxVerify(t *testing.T) { var exportAmount uint64 = 10000000 + ctx := utils.TestSnowContext() exportTx := &atomic.UnsignedExportTx{ - NetworkID: testNetworkID, - BlockchainID: testCChainID, - DestinationChain: testXChainID, + NetworkID: ctx.NetworkID, + BlockchainID: ctx.CChainID, + DestinationChain: ctx.XChainID, Ins: []atomic.EVMInput{ { Address: testEthAddrs[0], Amount: exportAmount, - AssetID: testAvaxAssetID, + AssetID: ctx.AVAXAssetID, Nonce: 0, }, { Address: testEthAddrs[2], Amount: exportAmount, - AssetID: testAvaxAssetID, + AssetID: ctx.AVAXAssetID, Nonce: 0, }, }, ExportedOutputs: []*avax.TransferableOutput{ { - Asset: avax.Asset{ID: testAvaxAssetID}, + Asset: avax.Asset{ID: ctx.AVAXAssetID}, Out: &secp256k1fx.TransferOutput{ Amt: exportAmount, OutputOwners: secp256k1fx.OutputOwners{ @@ -1118,7 +1120,7 @@ func TestExportTxVerify(t *testing.T) { }, }, { - Asset: avax.Asset{ID: testAvaxAssetID}, + Asset: avax.Asset{ID: ctx.AVAXAssetID}, Out: &secp256k1fx.TransferOutput{ Amt: exportAmount, OutputOwners: secp256k1fx.OutputOwners{ @@ -1138,8 +1140,6 @@ func TestExportTxVerify(t *testing.T) { emptySigners := make([][]*secp256k1.PrivateKey, 2) atomic.SortEVMInputsAndSigners(exportTx.Ins, emptySigners) - ctx := NewContext() - tests := map[string]atomicTxVerifyTest{ "nil tx": { generate: func(t *testing.T) atomic.UnsignedAtomicTx { @@ -1261,7 +1261,7 @@ func TestExportTxVerify(t *testing.T) { { Address: testEthAddrs[0], Amount: 0, - AssetID: testAvaxAssetID, + AssetID: utils.TestAvaxAssetID, Nonce: 0, }, } @@ -1779,7 +1779,7 @@ func TestNewExportTx(t *testing.T) { exportTx := tx.UnsignedAtomicTx - backend := &atomic.Backend{ + backend := &atomic.VerifierBackend{ Ctx: vm.ctx, Fx: &vm.fx, Rules: vm.currentRules(), @@ -1987,7 +1987,7 @@ func TestNewExportTxMulticoin(t *testing.T) { } exportTx := tx.UnsignedAtomicTx - backend := &atomic.Backend{ + backend := &atomic.VerifierBackend{ Ctx: vm.ctx, Fx: &vm.fx, Rules: vm.currentRules(), diff --git a/plugin/evm/import_tx_test.go b/plugin/evm/import_tx_test.go index d254153712..4ce2f9284e 100644 --- a/plugin/evm/import_tx_test.go +++ b/plugin/evm/import_tx_test.go @@ -9,12 +9,13 @@ import ( "github.com/ava-labs/coreth/params" "github.com/ava-labs/coreth/plugin/evm/atomic" + "github.com/ava-labs/coreth/utils" "github.com/ethereum/go-ethereum/common" "github.com/holiman/uint256" avalancheatomic "github.com/ava-labs/avalanchego/chains/atomic" "github.com/ava-labs/avalanchego/ids" - "github.com/ava-labs/avalanchego/utils" + avalancheutils "github.com/ava-labs/avalanchego/utils" "github.com/ava-labs/avalanchego/utils/constants" "github.com/ava-labs/avalanchego/utils/crypto/secp256k1" "github.com/ava-labs/avalanchego/utils/set" @@ -66,7 +67,7 @@ func createImportTxOptions(t *testing.T, vm *VM, sharedMemory *avalancheatomic.M } func TestImportTxVerify(t *testing.T) { - ctx := NewContext() + ctx := utils.TestSnowContext() var importAmount uint64 = 10000000 txID := ids.GenerateTestID() @@ -117,8 +118,8 @@ func TestImportTxVerify(t *testing.T) { } // Sort the inputs and outputs to ensure the transaction is canonical - utils.Sort(importTx.ImportedInputs) - utils.Sort(importTx.Outs) + avalancheutils.Sort(importTx.ImportedInputs) + avalancheutils.Sort(importTx.Outs) tests := map[string]atomicTxVerifyTest{ "nil tx": { @@ -316,7 +317,7 @@ func TestImportTxVerify(t *testing.T) { { Address: testEthAddrs[0], Amount: 0, - AssetID: testAvaxAssetID, + AssetID: utils.TestAvaxAssetID, }, } return &tx diff --git a/plugin/evm/syncervm_client.go b/plugin/evm/syncervm_client.go index 0b704d6233..15a5b384f5 100644 --- a/plugin/evm/syncervm_client.go +++ b/plugin/evm/syncervm_client.go @@ -1,6 +1,6 @@ // (c) 2021-2022, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. - +// TODO: move to separate package package evm import ( @@ -8,7 +8,10 @@ import ( "fmt" "sync" + syncclient "github.com/ava-labs/coreth/sync/client" + "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" commonEng "github.com/ava-labs/avalanchego/snow/engine/common" @@ -16,51 +19,60 @@ import ( "github.com/ava-labs/avalanchego/vms/components/chain" "github.com/ava-labs/coreth/core/rawdb" "github.com/ava-labs/coreth/core/state/snapshot" + "github.com/ava-labs/coreth/core/types" "github.com/ava-labs/coreth/eth" "github.com/ava-labs/coreth/params" + "github.com/ava-labs/coreth/plugin/evm/atomic" "github.com/ava-labs/coreth/plugin/evm/message" - syncclient "github.com/ava-labs/coreth/sync/client" "github.com/ava-labs/coreth/sync/statesync" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/log" ) -const ( - // State sync fetches [parentsToGet] parents of the block it syncs to. - // The last 256 block hashes are necessary to support the BLOCKHASH opcode. - parentsToGet = 256 +// State sync fetches [StateSyncParentsToFetch] parents of the block it syncs to. +// The last 256 block hashes are necessary to support the BLOCKHASH opcode. +const StateSyncParentsToFetch = 256 + +var ( + metadataPrefix = []byte("metadata") + stateSyncSummaryKey = []byte("stateSyncSummary") ) -var stateSyncSummaryKey = []byte("stateSyncSummary") +type BlockAcceptor interface { + PutLastAcceptedID([]byte) error +} + +type EthBlockWrapper interface { + GetEthBlock() *types.Block +} -// stateSyncClientConfig defines the options and dependencies needed to construct a StateSyncerClient -type stateSyncClientConfig struct { - enabled bool - skipResume bool +// StateSyncClientConfig defines the options and dependencies needed to construct a StateSyncerClient +type StateSyncClientConfig struct { + Enabled bool + SkipResume bool // Specifies the number of blocks behind the latest state summary that the chain must be // in order to prefer performing state sync over falling back to the normal bootstrapping // algorithm. - stateSyncMinBlocks uint64 - stateSyncRequestSize uint16 // number of key/value pairs to ask peers for per request + StateSyncMinBlocks uint64 + StateSyncRequestSize uint16 // number of key/value pairs to ask peers for per request - lastAcceptedHeight uint64 + LastAcceptedHeight uint64 - chain *eth.Ethereum - state *chain.State - chaindb ethdb.Database - metadataDB database.Database - acceptedBlockDB database.Database - db *versiondb.Database - atomicBackend AtomicBackend + Chain *eth.Ethereum + State *chain.State + ChaindDB ethdb.Database + Acceptor BlockAcceptor + DB *versiondb.Database + AtomicBackend atomic.AtomicBackend - client syncclient.Client + Client syncclient.Client - toEngine chan<- commonEng.Message + ToEngine chan<- commonEng.Message } type stateSyncerClient struct { - *stateSyncClientConfig + *StateSyncClientConfig resumableSummary message.SyncSummary @@ -70,11 +82,13 @@ type stateSyncerClient struct { // State Sync results syncSummary message.SyncSummary stateSyncErr error + metadataDB database.Database } -func NewStateSyncClient(config *stateSyncClientConfig) StateSyncClient { +func NewStateSyncClient(config *StateSyncClientConfig) StateSyncClient { return &stateSyncerClient{ - stateSyncClientConfig: config, + StateSyncClientConfig: config, + metadataDB: prefixdb.New(metadataPrefix, config.DB), } } @@ -101,14 +115,14 @@ type Syncer interface { // StateSyncEnabled returns [client.enabled], which is set in the chain's config file. func (client *stateSyncerClient) StateSyncEnabled(context.Context) (bool, error) { - return client.enabled, nil + return client.Enabled, nil } // GetOngoingSyncStateSummary returns a state summary that was previously started // and not finished, and sets [resumableSummary] if one was found. // Returns [database.ErrNotFound] if no ongoing summary is found or if [client.skipResume] is true. func (client *stateSyncerClient) GetOngoingSyncStateSummary(context.Context) (block.StateSummary, error) { - if client.skipResume { + if client.SkipResume { return nil, database.ErrNotFound } @@ -130,7 +144,7 @@ func (client *stateSyncerClient) ClearOngoingSummary() error { if err := client.metadataDB.Delete(stateSyncSummaryKey); err != nil { return fmt.Errorf("failed to clear ongoing summary: %w", err) } - if err := client.db.Commit(); err != nil { + if err := client.DB.Commit(); err != nil { return fmt.Errorf("failed to commit db while clearing ongoing summary: %w", err) } @@ -145,7 +159,7 @@ func (client *stateSyncerClient) ParseStateSummary(_ context.Context, summaryByt // stateSync blockingly performs the state sync for the EVM state and the atomic state // to [client.syncSummary]. returns an error if one occurred. func (client *stateSyncerClient) stateSync(ctx context.Context) error { - if err := client.syncBlocks(ctx, client.syncSummary.BlockHash, client.syncSummary.BlockNumber, parentsToGet); err != nil { + if err := client.syncBlocks(ctx, client.syncSummary.BlockHash, client.syncSummary.BlockNumber, StateSyncParentsToFetch); err != nil { return err } @@ -166,10 +180,10 @@ func (client *stateSyncerClient) acceptSyncSummary(proposedSummary message.SyncS // Skip syncing if the blockchain is not significantly ahead of local state, // since bootstrapping would be faster. // (Also ensures we don't sync to a height prior to local state.) - if client.lastAcceptedHeight+client.stateSyncMinBlocks > proposedSummary.Height() { + if client.LastAcceptedHeight+client.StateSyncMinBlocks > proposedSummary.Height() { log.Info( "last accepted too close to most recent syncable block, skipping state sync", - "lastAccepted", client.lastAcceptedHeight, + "lastAccepted", client.LastAcceptedHeight, "syncableHeight", proposedSummary.Height(), ) return block.StateSyncSkipped, nil @@ -181,11 +195,11 @@ func (client *stateSyncerClient) acceptSyncSummary(proposedSummary message.SyncS // sync marker will be wiped, so we do not accidentally resume progress from an incorrect version // of the snapshot. (if switching between versions that come before this change and back this could // lead to the snapshot not being cleaned up correctly) - <-snapshot.WipeSnapshot(client.chaindb, true) + <-snapshot.WipeSnapshot(client.ChaindDB, true) // Reset the snapshot generator here so that when state sync completes, snapshots will not attempt to read an // invalid generator. // Note: this must be called after WipeSnapshot is called so that we do not invalidate a partially generated snapshot. - snapshot.ResetSnapshotGeneration(client.chaindb) + snapshot.ResetSnapshotGeneration(client.ChaindDB) } client.syncSummary = proposedSummary @@ -195,7 +209,7 @@ func (client *stateSyncerClient) acceptSyncSummary(proposedSummary message.SyncS if err := client.metadataDB.Put(stateSyncSummaryKey, proposedSummary.Bytes()); err != nil { return block.StateSyncSkipped, fmt.Errorf("failed to write state sync summary key to disk: %w", err) } - if err := client.db.Commit(); err != nil { + if err := client.DB.Commit(); err != nil { return block.StateSyncSkipped, fmt.Errorf("failed to commit db: %w", err) } @@ -218,7 +232,7 @@ func (client *stateSyncerClient) acceptSyncSummary(proposedSummary message.SyncS // this error will be propagated to the engine when it calls // vm.SetState(snow.Bootstrapping) log.Info("stateSync completed, notifying engine", "err", client.stateSyncErr) - client.toEngine <- commonEng.StateSyncDone + client.ToEngine <- commonEng.StateSyncDone }() return block.StateSyncStatic, nil } @@ -235,7 +249,7 @@ func (client *stateSyncerClient) syncBlocks(ctx context.Context, fromHash common // first, check for blocks already available on disk so we don't // request them from peers. for parentsToGet >= 0 { - blk := rawdb.ReadBlock(client.chaindb, nextHash, nextHeight) + blk := rawdb.ReadBlock(client.ChaindDB, nextHash, nextHeight) if blk != nil { // block exists nextHash = blk.ParentHash() @@ -250,12 +264,12 @@ func (client *stateSyncerClient) syncBlocks(ctx context.Context, fromHash common // get any blocks we couldn't find on disk from peers and write // them to disk. - batch := client.chaindb.NewBatch() + batch := client.ChaindDB.NewBatch() for i := parentsToGet - 1; i >= 0 && (nextHash != common.Hash{}); { if err := ctx.Err(); err != nil { return err } - blocks, err := client.client.GetBlocks(ctx, nextHash, nextHeight, parentsPerRequest) + blocks, err := client.Client.GetBlocks(ctx, nextHash, nextHeight, parentsPerRequest) if err != nil { log.Error("could not get blocks from peer", "err", err, "nextHash", nextHash, "remaining", i+1) return err @@ -276,7 +290,7 @@ func (client *stateSyncerClient) syncBlocks(ctx context.Context, fromHash common func (client *stateSyncerClient) syncAtomicTrie(ctx context.Context) error { log.Info("atomic tx: sync starting", "root", client.syncSummary.AtomicRoot) - atomicSyncer, err := client.atomicBackend.Syncer(client.client, client.syncSummary.AtomicRoot, client.syncSummary.BlockNumber, client.stateSyncRequestSize) + atomicSyncer, err := client.AtomicBackend.Syncer(client.Client, client.syncSummary.AtomicRoot, client.syncSummary.BlockNumber, client.StateSyncRequestSize) if err != nil { return err } @@ -291,13 +305,13 @@ func (client *stateSyncerClient) syncAtomicTrie(ctx context.Context) error { func (client *stateSyncerClient) syncStateTrie(ctx context.Context) error { log.Info("state sync: sync starting", "root", client.syncSummary.BlockRoot) evmSyncer, err := statesync.NewStateSyncer(&statesync.StateSyncerConfig{ - Client: client.client, + Client: client.Client, Root: client.syncSummary.BlockRoot, BatchSize: ethdb.IdealBatchSize, - DB: client.chaindb, + DB: client.ChaindDB, MaxOutstandingCodeHashes: statesync.DefaultMaxOutstandingCodeHashes, NumCodeFetchingWorkers: statesync.DefaultNumCodeFetchingWorkers, - RequestSize: client.stateSyncRequestSize, + RequestSize: client.StateSyncRequestSize, }) if err != nil { return err @@ -321,7 +335,7 @@ func (client *stateSyncerClient) Shutdown() error { // finishSync is responsible for updating disk and memory pointers so the VM is prepared // for bootstrapping. Executes any shared memory operations from the atomic trie to shared memory. func (client *stateSyncerClient) finishSync() error { - stateBlock, err := client.state.GetBlock(context.TODO(), ids.ID(client.syncSummary.BlockHash)) + stateBlock, err := client.State.GetBlock(context.TODO(), ids.ID(client.syncSummary.BlockHash)) if err != nil { return fmt.Errorf("could not get block by hash from client state: %s", client.syncSummary.BlockHash) } @@ -330,12 +344,14 @@ func (client *stateSyncerClient) finishSync() error { if !ok { return fmt.Errorf("could not convert block(%T) to *chain.BlockWrapper", wrapper) } - evmBlock, ok := wrapper.Block.(*Block) + wrappedBlock := wrapper.Block + + evmBlockGetter, ok := wrappedBlock.(EthBlockWrapper) if !ok { return fmt.Errorf("could not convert block(%T) to evm.Block", stateBlock) } - block := evmBlock.ethBlock + block := evmBlockGetter.GetEthBlock() if block.Hash() != client.syncSummary.BlockHash { return fmt.Errorf("attempted to set last summary block to unexpected block hash: (%s != %s)", block.Hash(), client.syncSummary.BlockHash) @@ -354,9 +370,9 @@ func (client *stateSyncerClient) finishSync() error { // by [params.BloomBitsBlocks]. parentHeight := block.NumberU64() - 1 parentHash := block.ParentHash() - client.chain.BloomIndexer().AddCheckpoint(parentHeight/params.BloomBitsBlocks, parentHash) + client.Chain.BloomIndexer().AddCheckpoint(parentHeight/params.BloomBitsBlocks, parentHash) - if err := client.chain.BlockChain().ResetToStateSyncedBlock(block); err != nil { + if err := client.Chain.BlockChain().ResetToStateSyncedBlock(block); err != nil { return err } @@ -364,7 +380,7 @@ func (client *stateSyncerClient) finishSync() error { return fmt.Errorf("error updating vm markers, height=%d, hash=%s, err=%w", block.NumberU64(), block.Hash(), err) } - if err := client.state.SetLastAcceptedBlock(evmBlock); err != nil { + if err := client.State.SetLastAcceptedBlock(wrappedBlock); err != nil { return err } @@ -374,7 +390,7 @@ func (client *stateSyncerClient) finishSync() error { // ApplyToSharedMemory does this, and even if the VM is stopped // (gracefully or ungracefully), since MarkApplyToSharedMemoryCursor // is called, VM will resume ApplyToSharedMemory on Initialize. - return client.atomicBackend.ApplyToSharedMemory(block.NumberU64()) + return client.AtomicBackend.ApplyToSharedMemory(block.NumberU64()) } // updateVMMarkers updates the following markers in the VM's database @@ -387,17 +403,17 @@ func (client *stateSyncerClient) updateVMMarkers() error { // Mark the previously last accepted block for the shared memory cursor, so that we will execute shared // memory operations from the previously last accepted block to [vm.syncSummary] when ApplyToSharedMemory // is called. - if err := client.atomicBackend.MarkApplyToSharedMemoryCursor(client.lastAcceptedHeight); err != nil { + if err := client.AtomicBackend.MarkApplyToSharedMemoryCursor(client.LastAcceptedHeight); err != nil { return err } - client.atomicBackend.SetLastAccepted(client.syncSummary.BlockHash) - if err := client.acceptedBlockDB.Put(lastAcceptedKey, client.syncSummary.BlockHash[:]); err != nil { + client.AtomicBackend.SetLastAccepted(client.syncSummary.BlockHash) + if err := client.Acceptor.PutLastAcceptedID(client.syncSummary.BlockHash.Bytes()); err != nil { return err } if err := client.metadataDB.Delete(stateSyncSummaryKey); err != nil { return err } - return client.db.Commit() + return client.DB.Commit() } // Error returns a non-nil error if one occurred during the sync. diff --git a/plugin/evm/syncervm_server.go b/plugin/evm/syncervm_server.go index 3bf051bf87..f434f4ae7d 100644 --- a/plugin/evm/syncervm_server.go +++ b/plugin/evm/syncervm_server.go @@ -1,6 +1,6 @@ // (c) 2021-2022, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. - +// TODO: move to separate package package evm import ( @@ -11,14 +11,15 @@ import ( "github.com/ava-labs/avalanchego/snow/engine/snowman/block" "github.com/ava-labs/coreth/core" + "github.com/ava-labs/coreth/plugin/evm/atomic" "github.com/ava-labs/coreth/plugin/evm/message" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/log" ) -type stateSyncServerConfig struct { +type StateSyncServerConfig struct { Chain *core.BlockChain - AtomicTrie AtomicTrie + AtomicTrie atomic.AtomicTrie // SyncableInterval is the interval at which blocks are eligible to provide syncable block summaries. SyncableInterval uint64 @@ -26,7 +27,7 @@ type stateSyncServerConfig struct { type stateSyncServer struct { chain *core.BlockChain - atomicTrie AtomicTrie + atomicTrie atomic.AtomicTrie syncableInterval uint64 } @@ -36,7 +37,7 @@ type StateSyncServer interface { GetStateSummary(context.Context, uint64) (block.StateSummary, error) } -func NewStateSyncServer(config *stateSyncServerConfig) StateSyncServer { +func NewStateSyncServer(config *StateSyncServerConfig) StateSyncServer { return &stateSyncServer{ chain: config.Chain, atomicTrie: config.AtomicTrie, diff --git a/plugin/evm/syncervm_test.go b/plugin/evm/syncervm_test.go index 24491d9fa5..6d07b6f721 100644 --- a/plugin/evm/syncervm_test.go +++ b/plugin/evm/syncervm_test.go @@ -55,7 +55,7 @@ func TestSkipStateSync(t *testing.T) { stateSyncMinBlocks: 300, // must be greater than [syncableInterval] to skip sync syncMode: block.StateSyncSkipped, } - vmSetup := createSyncServerAndClientVMs(t, test, parentsToGet) + vmSetup := createSyncServerAndClientVMs(t, test, StateSyncParentsToFetch) testSyncerVM(t, vmSetup, test) } @@ -67,14 +67,14 @@ func TestStateSyncFromScratch(t *testing.T) { stateSyncMinBlocks: 50, // must be less than [syncableInterval] to perform sync syncMode: block.StateSyncStatic, } - vmSetup := createSyncServerAndClientVMs(t, test, parentsToGet) + vmSetup := createSyncServerAndClientVMs(t, test, StateSyncParentsToFetch) testSyncerVM(t, vmSetup, test) } func TestStateSyncFromScratchExceedParent(t *testing.T) { rand.Seed(1) - numToGen := parentsToGet + uint64(32) + numToGen := StateSyncParentsToFetch + uint64(32) test := syncTest{ syncableInterval: numToGen, stateSyncMinBlocks: 50, // must be less than [syncableInterval] to perform sync @@ -121,7 +121,7 @@ func TestStateSyncToggleEnabledToDisabled(t *testing.T) { }, expectedErr: context.Canceled, } - vmSetup := createSyncServerAndClientVMs(t, test, parentsToGet) + vmSetup := createSyncServerAndClientVMs(t, test, StateSyncParentsToFetch) // Perform sync resulting in early termination. testSyncerVM(t, vmSetup, test) @@ -272,7 +272,7 @@ func TestVMShutdownWhileSyncing(t *testing.T) { }, expectedErr: context.Canceled, } - vmSetup = createSyncServerAndClientVMs(t, test, parentsToGet) + vmSetup = createSyncServerAndClientVMs(t, test, StateSyncParentsToFetch) // Perform sync resulting in early termination. testSyncerVM(t, vmSetup, test) } @@ -285,8 +285,9 @@ func createSyncServerAndClientVMs(t *testing.T, test syncTest, numBlocks int) *s testShortIDAddrs[0]: importAmount, } ) + config := fmt.Sprintf(`{"commit-interval": %d}`, test.syncableInterval) _, serverVM, _, serverAtomicMemory, serverAppSender := GenesisVMWithUTXOs( - t, true, "", "", "", alloc, + t, true, "", config, "", alloc, ) t.Cleanup(func() { log.Info("Shutting down server VM") @@ -332,14 +333,17 @@ func createSyncServerAndClientVMs(t *testing.T, test syncTest, numBlocks int) *s // override serverAtomicTrie's commitInterval so the call to [serverAtomicTrie.Index] // creates a commit at the height [syncableInterval]. This is necessary to support // fetching a state summary. - serverAtomicTrie := serverVM.atomicTrie.(*atomicTrie) - serverAtomicTrie.commitInterval = test.syncableInterval - require.NoError(serverAtomicTrie.commit(test.syncableInterval, serverAtomicTrie.LastAcceptedRoot())) + serverAtomicTrie := serverVM.atomicTrie + require.True(serverAtomicTrie.AcceptTrie(test.syncableInterval, serverAtomicTrie.LastAcceptedRoot())) require.NoError(serverVM.db.Commit()) - serverSharedMemories := newSharedMemories(serverAtomicMemory, serverVM.ctx.ChainID, serverVM.ctx.XChainID) - serverSharedMemories.assertOpsApplied(t, mustAtomicOps(importTx)) - serverSharedMemories.assertOpsApplied(t, mustAtomicOps(exportTx)) + serverSharedMemories := atomic.NewSharedMemories(serverAtomicMemory, serverVM.ctx.ChainID, serverVM.ctx.XChainID) + importOps, err := atomic.ConvertToAtomicOps(importTx) + require.NoError(err) + exportOps, err := atomic.ConvertToAtomicOps(exportTx) + require.NoError(err) + serverSharedMemories.AssertOpsApplied(t, importOps) + serverSharedMemories.AssertOpsApplied(t, exportOps) // make some accounts trieDB := triedb.NewDatabase(serverVM.chaindb, nil) @@ -360,7 +364,7 @@ func createSyncServerAndClientVMs(t *testing.T, test syncTest, numBlocks int) *s serverVM.StateSyncServer.(*stateSyncServer).syncableInterval = test.syncableInterval // initialise [syncerVM] with blank genesis state - stateSyncEnabledJSON := fmt.Sprintf(`{"state-sync-enabled":true, "state-sync-min-blocks": %d, "tx-lookup-limit": %d}`, test.stateSyncMinBlocks, 4) + stateSyncEnabledJSON := fmt.Sprintf(`{"state-sync-enabled":true, "state-sync-min-blocks": %d, "tx-lookup-limit": %d, "commit-interval": %d}`, test.stateSyncMinBlocks, 4, test.syncableInterval) syncerEngineChan, syncerVM, syncerDB, syncerAtomicMemory, syncerAppSender := GenesisVMWithUTXOs( t, false, "", stateSyncEnabledJSON, "", alloc, ) @@ -373,9 +377,6 @@ func createSyncServerAndClientVMs(t *testing.T, test syncTest, numBlocks int) *s require.NoError(err) require.True(enabled) - // override [syncerVM]'s commit interval so the atomic trie works correctly. - syncerVM.atomicTrie.(*atomicTrie).commitInterval = test.syncableInterval - // override [serverVM]'s SendAppResponse function to trigger AppResponse on [syncerVM] serverAppSender.SendAppResponseF = func(ctx context.Context, nodeID ids.NodeID, requestID uint32, response []byte) error { if test.responseIntercept == nil { @@ -559,10 +560,12 @@ func testSyncerVM(t *testing.T, vmSetup *syncVMSetup, test syncTest) { require.True(syncerVM.bootstrapped.Get()) // check atomic memory was synced properly - syncerSharedMemories := newSharedMemories(syncerAtomicMemory, syncerVM.ctx.ChainID, syncerVM.ctx.XChainID) + syncerSharedMemories := atomic.NewSharedMemories(syncerAtomicMemory, syncerVM.ctx.ChainID, syncerVM.ctx.XChainID) for _, tx := range includedAtomicTxs { - syncerSharedMemories.assertOpsApplied(t, mustAtomicOps(tx)) + ops, err := atomic.ConvertToAtomicOps(tx) + require.NoError(err) + syncerSharedMemories.AssertOpsApplied(t, ops) } // Generate blocks after we have entered normal consensus as well diff --git a/plugin/evm/tx_test.go b/plugin/evm/tx_test.go index a710a3c9e1..9bef967e68 100644 --- a/plugin/evm/tx_test.go +++ b/plugin/evm/tx_test.go @@ -116,7 +116,7 @@ func executeTxTest(t *testing.T, test atomicTxTest) { } lastAcceptedBlock := vm.LastAcceptedBlockInternal().(*Block) - backend := &atomic.Backend{ + backend := &atomic.VerifierBackend{ Ctx: vm.ctx, Fx: &vm.fx, Rules: rules, diff --git a/plugin/evm/vm.go b/plugin/evm/vm.go index c83557d594..aebc94814c 100644 --- a/plugin/evm/vm.go +++ b/plugin/evm/vm.go @@ -107,6 +107,7 @@ var ( _ block.StateSyncableVM = &VM{} _ statesyncclient.EthBlockParser = &VM{} _ secp256k1fx.VM = &VM{} + _ BlockAcceptor = &VM{} ) const ( @@ -153,13 +154,8 @@ var ( // Set last accepted key to be longer than the keys used to store accepted block IDs. lastAcceptedKey = []byte("last_accepted_key") acceptedPrefix = []byte("snowman_accepted") - metadataPrefix = []byte("metadata") warpPrefix = []byte("warp") ethDBPrefix = []byte("ethdb") - - // Prefixes for atomic trie - atomicTrieDBPrefix = []byte("atomicTrieDB") - atomicTrieMetaDBPrefix = []byte("atomicTrieMetaDB") ) var ( @@ -229,9 +225,6 @@ type VM struct { // [db] is the VM's current database managed by ChainState db *versiondb.Database - // metadataDB is used to store one off keys. - metadataDB avalanchedatabase.Database - // [chaindb] is the database supplied to the Ethereum backend chaindb ethdb.Database @@ -250,11 +243,11 @@ type VM struct { // [atomicTxRepository] maintains two indexes on accepted atomic txs. // - txID to accepted atomic tx // - block height to list of atomic txs accepted on block at that height - atomicTxRepository AtomicTxRepository + atomicTxRepository atomic.AtomicTxRepository // [atomicTrie] maintains a merkle forest of [height]=>[atomic txs]. - atomicTrie AtomicTrie + atomicTrie atomic.AtomicTrie // [atomicBackend] abstracts verification and processing of atomic transactions - atomicBackend AtomicBackend + atomicBackend atomic.AtomicBackend builder *blockBuilder @@ -593,11 +586,11 @@ func (vm *VM) Initialize( } // initialize atomic repository - vm.atomicTxRepository, err = NewAtomicTxRepository(vm.db, atomic.Codec, lastAcceptedHeight) + vm.atomicTxRepository, err = atomic.NewAtomicTxRepository(vm.db, atomic.Codec, lastAcceptedHeight) if err != nil { return fmt.Errorf("failed to create atomic repository: %w", err) } - vm.atomicBackend, err = NewAtomicBackend( + vm.atomicBackend, err = atomic.NewAtomicBackend( vm.db, vm.ctx.SharedMemory, bonusBlockHeights, vm.atomicTxRepository, lastAcceptedHeight, lastAcceptedHash, vm.config.CommitInterval, @@ -624,7 +617,7 @@ func (vm *VM) Initialize( vm.setAppRequestHandlers() - vm.StateSyncServer = NewStateSyncServer(&stateSyncServerConfig{ + vm.StateSyncServer = NewStateSyncServer(&StateSyncServerConfig{ Chain: vm.blockChain, AtomicTrie: vm.atomicTrie, SyncableInterval: vm.config.StateSyncCommitInterval, @@ -733,10 +726,10 @@ func (vm *VM) initializeStateSyncClient(lastAcceptedHeight uint64) error { } } - vm.StateSyncClient = NewStateSyncClient(&stateSyncClientConfig{ - chain: vm.eth, - state: vm.State, - client: statesyncclient.NewClient( + vm.StateSyncClient = NewStateSyncClient(&StateSyncClientConfig{ + Chain: vm.eth, + State: vm.State, + Client: statesyncclient.NewClient( &statesyncclient.ClientConfig{ NetworkClient: vm.client, Codec: vm.networkCodec, @@ -745,17 +738,16 @@ func (vm *VM) initializeStateSyncClient(lastAcceptedHeight uint64) error { BlockParser: vm, }, ), - enabled: stateSyncEnabled, - skipResume: vm.config.StateSyncSkipResume, - stateSyncMinBlocks: vm.config.StateSyncMinBlocks, - stateSyncRequestSize: vm.config.StateSyncRequestSize, - lastAcceptedHeight: lastAcceptedHeight, // TODO clean up how this is passed around - chaindb: vm.chaindb, - metadataDB: vm.metadataDB, - acceptedBlockDB: vm.acceptedBlockDB, - db: vm.db, - atomicBackend: vm.atomicBackend, - toEngine: vm.toEngine, + Enabled: stateSyncEnabled, + SkipResume: vm.config.StateSyncSkipResume, + StateSyncMinBlocks: vm.config.StateSyncMinBlocks, + StateSyncRequestSize: vm.config.StateSyncRequestSize, + LastAcceptedHeight: lastAcceptedHeight, // TODO clean up how this is passed around + ChaindDB: vm.chaindb, + DB: vm.db, + AtomicBackend: vm.atomicBackend, + ToEngine: vm.toEngine, + Acceptor: vm, }) // If StateSync is disabled, clear any ongoing summary so that we will not attempt to resume @@ -1522,7 +1514,6 @@ func (vm *VM) initializeDBs(db avalanchedatabase.Database) error { vm.chaindb = rawdb.NewDatabase(database.WrapDatabase(prefixdb.NewNested(ethDBPrefix, db))) vm.db = versiondb.New(db) vm.acceptedBlockDB = prefixdb.New(acceptedPrefix, vm.db) - vm.metadataDB = prefixdb.New(metadataPrefix, vm.db) // Note warpDB is not part of versiondb because it is not necessary // that warp signatures are committed to the database atomically with // the last accepted block. @@ -1648,7 +1639,7 @@ func (vm *VM) verifyTx(tx *atomic.Tx, parentHash common.Hash, baseFee *big.Int, if !ok { return fmt.Errorf("parent block %s had unexpected type %T", parentIntf.ID(), parentIntf) } - atomicBackend := &atomic.Backend{ + atomicBackend := &atomic.VerifierBackend{ Ctx: vm.ctx, Fx: &vm.fx, Rules: rules, @@ -1687,7 +1678,7 @@ func (vm *VM) verifyTxs(txs []*atomic.Tx, parentHash common.Hash, baseFee *big.I // 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{ + atomicBackend := &atomic.VerifierBackend{ Ctx: vm.ctx, Fx: &vm.fx, Rules: rules, @@ -1918,3 +1909,7 @@ func (vm *VM) newExportTx( return tx, nil } + +func (vm *VM) PutLastAcceptedID(ID []byte) error { + return vm.acceptedBlockDB.Put(lastAcceptedKey, ID) +} diff --git a/plugin/evm/vm_test.go b/plugin/evm/vm_test.go index 4ec59de0bc..0a4f9ebc20 100644 --- a/plugin/evm/vm_test.go +++ b/plugin/evm/vm_test.go @@ -39,10 +39,8 @@ import ( "github.com/ava-labs/avalanchego/database/prefixdb" "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/snow" - "github.com/ava-labs/avalanchego/snow/validators/validatorstest" "github.com/ava-labs/avalanchego/upgrade" "github.com/ava-labs/avalanchego/utils/cb58" - "github.com/ava-labs/avalanchego/utils/crypto/bls" "github.com/ava-labs/avalanchego/utils/crypto/secp256k1" "github.com/ava-labs/avalanchego/utils/formatting" "github.com/ava-labs/avalanchego/utils/hashing" @@ -56,7 +54,6 @@ import ( commonEng "github.com/ava-labs/avalanchego/snow/engine/common" "github.com/ava-labs/avalanchego/snow/engine/enginetest" - constantsEng "github.com/ava-labs/avalanchego/utils/constants" "github.com/ava-labs/coreth/consensus/dummy" "github.com/ava-labs/coreth/core" @@ -65,19 +62,15 @@ import ( "github.com/ava-labs/coreth/params" "github.com/ava-labs/coreth/rpc" - avalancheWarp "github.com/ava-labs/avalanchego/vms/platformvm/warp" accountKeystore "github.com/ava-labs/coreth/accounts/keystore" ) var ( testNetworkID uint32 = 10 - testCChainID = ids.ID{'c', 'c', 'h', 'a', 'i', 'n', 't', 'e', 's', 't'} - testXChainID = ids.ID{'t', 'e', 's', 't', 'x'} nonExistentID = ids.ID{'F'} testKeys []*secp256k1.PrivateKey testEthAddrs []common.Address // testEthAddrs[i] corresponds to testKeys[i] testShortIDAddrs []ids.ShortID - testAvaxAssetID = ids.ID{1, 2, 3} username = "Johns" password = "CjasdjhiPeirbSenfeI13" // #nosec G101 @@ -204,41 +197,6 @@ func BuildGenesisTest(t *testing.T, genesisJSON string) []byte { return genesisBytes } -func NewContext() *snow.Context { - ctx := utils.TestSnowContext() - ctx.NodeID = ids.GenerateTestNodeID() - ctx.NetworkID = testNetworkID - ctx.ChainID = testCChainID - ctx.AVAXAssetID = testAvaxAssetID - ctx.XChainID = testXChainID - ctx.SharedMemory = testSharedMemory() - aliaser := ctx.BCLookup.(ids.Aliaser) - _ = aliaser.Alias(testCChainID, "C") - _ = aliaser.Alias(testCChainID, testCChainID.String()) - _ = aliaser.Alias(testXChainID, "X") - _ = aliaser.Alias(testXChainID, testXChainID.String()) - ctx.ValidatorState = &validatorstest.State{ - GetSubnetIDF: func(_ context.Context, chainID ids.ID) (ids.ID, error) { - subnetID, ok := map[ids.ID]ids.ID{ - constantsEng.PlatformChainID: constantsEng.PrimaryNetworkID, - testXChainID: constantsEng.PrimaryNetworkID, - testCChainID: constantsEng.PrimaryNetworkID, - }[chainID] - if !ok { - return ids.Empty, errors.New("unknown chain") - } - return subnetID, nil - }, - } - blsSecretKey, err := bls.NewSigner() - if err != nil { - panic(err) - } - ctx.WarpSigner = avalancheWarp.NewSigner(blsSecretKey, ctx.NetworkID, ctx.ChainID) - ctx.PublicKey = blsSecretKey.PublicKey() - return ctx -} - // setupGenesis sets up the genesis // If [genesisJSON] is empty, defaults to using [genesisJSONLatest] func setupGenesis( @@ -254,7 +212,7 @@ func setupGenesis( genesisJSON = genesisJSONLatest } genesisBytes := BuildGenesisTest(t, genesisJSON) - ctx := NewContext() + ctx := utils.TestSnowContext() baseDB := memdb.New() @@ -813,7 +771,7 @@ func TestBuildEthTxBlock(t *testing.T) { restartedVM := &VM{} if err := restartedVM.Initialize( context.Background(), - NewContext(), + utils.TestSnowContext(), dbManager, []byte(genesisJSONApricotPhase2), []byte(""), @@ -1515,6 +1473,19 @@ func TestConflictingTransitiveAncestryWithGap(t *testing.T) { } } +type wrappedBackend struct { + atomic.AtomicBackend + registeredBonusBlocks map[uint64]common.Hash +} + +func (w *wrappedBackend) IsBonus(blockHeight uint64, blockHash common.Hash) bool { + // Check if the block is a bonus block + if hash, ok := w.registeredBonusBlocks[blockHeight]; ok { + return blockHash.Cmp(hash) == 0 + } + return false +} + func TestBonusBlocksTxs(t *testing.T) { issuer, vm, _, sharedMemory, _ := GenesisVM(t, true, genesisJSONApricotPhase0, "", "") @@ -1572,7 +1543,11 @@ func TestBonusBlocksTxs(t *testing.T) { } // Make [blk] a bonus block. - vm.atomicBackend.(*atomicBackend).bonusBlocks = map[uint64]ids.ID{blk.Height(): blk.ID()} + wrappedBackend := &wrappedBackend{ + AtomicBackend: vm.atomicBackend, + registeredBonusBlocks: map[uint64]common.Hash{1: common.Hash(blk.ID())}, + } + vm.atomicBackend = wrappedBackend // Remove the UTXOs from shared memory, so that non-bonus blocks will fail verification if err := vm.ctx.SharedMemory.Apply(map[ids.ID]*avalancheatomic.Requests{vm.ctx.XChainID: {RemoveRequests: [][]byte{inputID[:]}}}); err != nil { diff --git a/utils/snow.go b/utils/snow.go index 36b9b7b7fb..e24c884b16 100644 --- a/utils/snow.go +++ b/utils/snow.go @@ -8,6 +8,8 @@ import ( "errors" "github.com/ava-labs/avalanchego/api/metrics" + "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/snow" "github.com/ava-labs/avalanchego/snow/validators" @@ -19,9 +21,10 @@ import ( ) var ( - testCChainID = ids.ID{'c', 'c', 'h', 'a', 'i', 'n', 't', 'e', 's', 't'} - testXChainID = ids.ID{'t', 'e', 's', 't', 'x'} - testChainID = ids.ID{'t', 'e', 's', 't', 'c', 'h', 'a', 'i', 'n'} + testCChainID = ids.ID{'c', 'c', 'h', 'a', 'i', 'n', 't', 'e', 's', 't'} + testXChainID = ids.ID{'t', 'e', 's', 't', 'x'} + testChainID = ids.ID{'t', 'e', 's', 't', 'c', 'h', 'a', 'i', 'n'} + TestAvaxAssetID = ids.ID{1, 2, 3} ) func TestSnowContext() *snow.Context { @@ -31,19 +34,27 @@ func TestSnowContext() *snow.Context { } pk := sk.PublicKey() networkID := constants.UnitTestID - chainID := testChainID + chainID := testCChainID + + aliaser := ids.NewAliaser() + _ = aliaser.Alias(testCChainID, "C") + _ = aliaser.Alias(testCChainID, testCChainID.String()) + _ = aliaser.Alias(testXChainID, "X") + _ = aliaser.Alias(testXChainID, testXChainID.String()) ctx := &snow.Context{ NetworkID: networkID, SubnetID: ids.Empty, ChainID: chainID, + AVAXAssetID: TestAvaxAssetID, NodeID: ids.GenerateTestNodeID(), + SharedMemory: TestSharedMemory(), XChainID: testXChainID, CChainID: testCChainID, PublicKey: pk, WarpSigner: warp.NewSigner(sk, networkID, chainID), Log: logging.NoLog{}, - BCLookup: ids.NewAliaser(), + BCLookup: aliaser, Metrics: metrics.NewPrefixGatherer(), ChainDataDir: "", ValidatorState: NewTestValidatorState(), @@ -76,3 +87,8 @@ func NewTestValidatorState() *validatorstest.State { }, } } + +func TestSharedMemory() atomic.SharedMemory { + m := atomic.NewMemory(memdb.New()) + return m.NewSharedMemory(testCChainID) +} From 2b13c8ec2ff27948c6c0b9fe80b69ab96ecd329d Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Mon, 23 Dec 2024 18:54:26 +0300 Subject: [PATCH 11/27] bump avago --- go.mod | 12 ++++++------ go.sum | 7 +++++++ scripts/versions.sh | 2 +- 3 files changed, 14 insertions(+), 7 deletions(-) diff --git a/go.mod b/go.mod index ea7f6cc744..0e194918c2 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.20241211144846-f3ca1a0f8bb1 + github.com/ava-labs/avalanchego v1.12.2-0.20241223154608-e8356d529cbe 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 @@ -38,11 +38,11 @@ require ( github.com/urfave/cli/v2 v2.25.7 go.uber.org/goleak v1.3.0 go.uber.org/mock v0.4.0 - golang.org/x/crypto v0.26.0 + golang.org/x/crypto v0.31.0 golang.org/x/exp v0.0.0-20231127185646-65229373498e - golang.org/x/sync v0.8.0 - golang.org/x/sys v0.24.0 - golang.org/x/text v0.17.0 + golang.org/x/sync v0.10.0 + golang.org/x/sys v0.28.0 + golang.org/x/text v0.21.0 golang.org/x/time v0.3.0 google.golang.org/protobuf v1.34.2 gopkg.in/natefinch/lumberjack.v2 v2.0.0 @@ -121,7 +121,7 @@ require ( go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.26.0 // indirect golang.org/x/net v0.28.0 // indirect - golang.org/x/term v0.23.0 // indirect + golang.org/x/term v0.27.0 // indirect gonum.org/v1/gonum v0.11.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20240604185151-ef581f913117 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240827150818-7e3bb234dfed // indirect diff --git a/go.sum b/go.sum index 435d1113c8..3648cd7306 100644 --- a/go.sum +++ b/go.sum @@ -58,6 +58,8 @@ github.com/ava-labs/avalanchego v1.12.1-0.20241209214115-1dc4192013aa h1:8eSy+te 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/ava-labs/avalanchego v1.12.2-0.20241223154608-e8356d529cbe h1:fkdavyY3MNRF64ZlxCaDUy4/WhprH7PA8tbjb4IsvLc= +github.com/ava-labs/avalanchego v1.12.2-0.20241223154608-e8356d529cbe/go.mod h1:cDoT0Hq3P+/XfCyVvzrBj66yoid2I5LnMuj7LIkap+o= 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= @@ -605,6 +607,7 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= +golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -712,6 +715,7 @@ golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -780,10 +784,12 @@ golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.23.0 h1:F6D4vR+EHoL9/sWAWgAR1H2DcHr4PareCbAaCo1RpuU= golang.org/x/term v0.23.0/go.mod h1:DgV24QBUrK6jhZXl+20l6UWznPlwAHm1Q1mGHtydmSk= +golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -796,6 +802,7 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= diff --git a/scripts/versions.sh b/scripts/versions.sh index ce7b1cdb05..8ea089db0f 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:-'f3ca1a0f8bb1'} +AVALANCHE_VERSION=${AVALANCHE_VERSION:-'e8356d529cbe'} From 2798ad67da1f5d04f3ed0a0e3c542114fbb67e9d Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Mon, 23 Dec 2024 19:02:01 +0300 Subject: [PATCH 12/27] go mod tidy --- go.sum | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/go.sum b/go.sum index 3648cd7306..f4e79a03f9 100644 --- a/go.sum +++ b/go.sum @@ -54,10 +54,6 @@ github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156 h1:eMwmnE/GDgah4HI848JfFxHt+iPb26b4zyfspmqY0/8= github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM= 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/ava-labs/avalanchego v1.12.2-0.20241223154608-e8356d529cbe h1:fkdavyY3MNRF64ZlxCaDUy4/WhprH7PA8tbjb4IsvLc= github.com/ava-labs/avalanchego v1.12.2-0.20241223154608-e8356d529cbe/go.mod h1:cDoT0Hq3P+/XfCyVvzrBj66yoid2I5LnMuj7LIkap+o= github.com/aymerick/raymond v2.0.3-0.20180322193309-b565731e1464+incompatible/go.mod h1:osfaiScAUVup+UC9Nfq76eWqDhXlp+4UYaA8uhTBO6g= @@ -605,8 +601,7 @@ golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= -golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= +golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -713,8 +708,7 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= -golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -782,13 +776,11 @@ golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= -golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.23.0 h1:F6D4vR+EHoL9/sWAWgAR1H2DcHr4PareCbAaCo1RpuU= -golang.org/x/term v0.23.0/go.mod h1:DgV24QBUrK6jhZXl+20l6UWznPlwAHm1Q1mGHtydmSk= +golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q= golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -800,8 +792,7 @@ golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= -golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= -golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= From 278e055f835ff3da1f1050da418fc2b301f59d05 Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Mon, 23 Dec 2024 19:25:52 +0300 Subject: [PATCH 13/27] update releases md --- RELEASES.md | 1 + 1 file changed, 1 insertion(+) diff --git a/RELEASES.md b/RELEASES.md index b977d63812..7f8393b5de 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -2,6 +2,7 @@ ## [v0.14.1](https://github.com/ava-labs/coreth/releases/tag/v0.14.1) - Remove API eth_getAssetBalance that was used to query ANT balances (deprecated since v0.10.0) +- Remove legacy gossip handler and metrics (deprecated since v0.10.0) ## [v0.14.0](https://github.com/ava-labs/coreth/releases/tag/v0.14.0) - Minor version update to correspond to avalanchego v1.12.0 / Etna. From e5f2c278bf01fea39533759d34b098a0ca41965d Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Tue, 24 Dec 2024 21:07:30 +0300 Subject: [PATCH 14/27] use address methods from avago --- go.mod | 2 +- go.sum | 4 ++-- plugin/evm/atomic/export_tx.go | 7 +++---- plugin/evm/client/client.go | 6 ++++-- plugin/evm/client/utils.go | 13 +++++++++++++ plugin/evm/export_tx_test.go | 31 +++++++++++++++---------------- plugin/evm/import_tx_test.go | 7 +++---- plugin/evm/service.go | 5 ++--- plugin/evm/tx_gossip_test.go | 18 +++++++++--------- plugin/evm/user.go | 3 +-- plugin/evm/vm_test.go | 22 +++++++++++----------- scripts/versions.sh | 2 +- utils/utils.go | 31 ------------------------------- 13 files changed, 65 insertions(+), 86 deletions(-) create mode 100644 plugin/evm/client/utils.go delete mode 100644 utils/utils.go diff --git a/go.mod b/go.mod index 0e194918c2..b750d0d897 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.2-0.20241223154608-e8356d529cbe + github.com/ava-labs/avalanchego v1.12.2-0.20241224161435-3998475d671d 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 f4e79a03f9..01fbb024fa 100644 --- a/go.sum +++ b/go.sum @@ -54,8 +54,8 @@ github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156 h1:eMwmnE/GDgah4HI848JfFxHt+iPb26b4zyfspmqY0/8= github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= -github.com/ava-labs/avalanchego v1.12.2-0.20241223154608-e8356d529cbe h1:fkdavyY3MNRF64ZlxCaDUy4/WhprH7PA8tbjb4IsvLc= -github.com/ava-labs/avalanchego v1.12.2-0.20241223154608-e8356d529cbe/go.mod h1:cDoT0Hq3P+/XfCyVvzrBj66yoid2I5LnMuj7LIkap+o= +github.com/ava-labs/avalanchego v1.12.2-0.20241224161435-3998475d671d h1:QCtjS4ANcNfCdL6Z2sKpanDVJNt1MU0bUyVdW0g5zuU= +github.com/ava-labs/avalanchego v1.12.2-0.20241224161435-3998475d671d/go.mod h1:cDoT0Hq3P+/XfCyVvzrBj66yoid2I5LnMuj7LIkap+o= 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= diff --git a/plugin/evm/atomic/export_tx.go b/plugin/evm/atomic/export_tx.go index 906753265c..4938ddc53e 100644 --- a/plugin/evm/atomic/export_tx.go +++ b/plugin/evm/atomic/export_tx.go @@ -10,7 +10,6 @@ import ( "math/big" "github.com/ava-labs/coreth/params" - "github.com/ava-labs/coreth/utils" "github.com/holiman/uint256" "github.com/ava-labs/avalanchego/chains/atomic" @@ -241,7 +240,7 @@ func (utx *UnsignedExportTx) SemanticVerify( if err != nil { return err } - if input.Address != utils.PublicKeyToEthAddress(pubKey) { + if input.Address != pubKey.EthAddress() { return errPublicKeySignatureMismatch } } @@ -432,7 +431,7 @@ func GetSpendableFunds( if amount == 0 { break } - addr := utils.GetEthAddress(key) + addr := key.EthAddress() var balance uint64 if assetID == ctx.AVAXAssetID { // If the asset is AVAX, we divide by the x2cRate to convert back to the correct @@ -515,7 +514,7 @@ func GetSpendableAVAXWithFee( additionalFee := newFee - prevFee - addr := utils.GetEthAddress(key) + addr := key.EthAddress() // 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() diff --git a/plugin/evm/client/client.go b/plugin/evm/client/client.go index 93ac27ed5b..ddf05184d7 100644 --- a/plugin/evm/client/client.go +++ b/plugin/evm/client/client.go @@ -5,6 +5,7 @@ package client import ( "context" + "errors" "fmt" "github.com/ethereum/go-ethereum/common" @@ -19,12 +20,13 @@ import ( "github.com/ava-labs/avalanchego/utils/json" "github.com/ava-labs/avalanchego/utils/rpc" "github.com/ava-labs/coreth/plugin/evm/atomic" - "github.com/ava-labs/coreth/utils" ) // Interface compliance var _ Client = (*client)(nil) +var errInvalidAddr = errors.New("invalid hex address") + // Client interface for interacting with EVM [chain] type Client interface { IssueTx(ctx context.Context, txBytes []byte, options ...rpc.Option) (ids.ID, error) @@ -180,7 +182,7 @@ func (c *client) ImportKey(ctx context.Context, user api.UserPass, privateKey *s if err != nil { return common.Address{}, err } - return utils.ParseEthAddress(res.Address) + return ParseEthAddress(res.Address) } // ImportArgs are arguments for passing into Import requests diff --git a/plugin/evm/client/utils.go b/plugin/evm/client/utils.go new file mode 100644 index 0000000000..5ea43f4a20 --- /dev/null +++ b/plugin/evm/client/utils.go @@ -0,0 +1,13 @@ +// (c) 2019-2024, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package client + +import "github.com/ethereum/go-ethereum/common" + +func ParseEthAddress(addrStr string) (common.Address, error) { + if !common.IsHexAddress(addrStr) { + return common.Address{}, errInvalidAddr + } + return common.HexToAddress(addrStr), nil +} diff --git a/plugin/evm/export_tx_test.go b/plugin/evm/export_tx_test.go index 61c80fb7dd..eabbb05ede 100644 --- a/plugin/evm/export_tx_test.go +++ b/plugin/evm/export_tx_test.go @@ -19,7 +19,6 @@ import ( "github.com/ava-labs/avalanchego/vms/secp256k1fx" "github.com/ava-labs/coreth/params" "github.com/ava-labs/coreth/plugin/evm/atomic" - "github.com/ava-labs/coreth/utils" "github.com/ethereum/go-ethereum/common" "github.com/holiman/uint256" ) @@ -35,7 +34,7 @@ func createExportTxOptions(t *testing.T, vm *VM, issuer chan engCommon.Message, Amt: uint64(50000000), OutputOwners: secp256k1fx.OutputOwners{ Threshold: 1, - Addrs: []ids.ShortID{testKeys[0].PublicKey().Address()}, + Addrs: []ids.ShortID{testKeys[0].Address()}, }, }, } @@ -50,7 +49,7 @@ func createExportTxOptions(t *testing.T, vm *VM, issuer chan engCommon.Message, Key: inputID[:], Value: utxoBytes, Traits: [][]byte{ - testKeys[0].PublicKey().Address().Bytes(), + testKeys[0].Address().Bytes(), }, }}}}); err != nil { t.Fatal(err) @@ -104,8 +103,8 @@ func createExportTxOptions(t *testing.T, vm *VM, issuer chan engCommon.Message, func TestExportTxEVMStateTransfer(t *testing.T) { key := testKeys[0] - addr := key.PublicKey().Address() - ethAddr := utils.GetEthAddress(key) + addr := key.Address() + ethAddr := key.EthAddress() avaxAmount := 50 * units.MilliAvax avaxUTXOID := avax.UTXOID{ @@ -452,7 +451,7 @@ func TestExportTxSemanticVerify(t *testing.T) { parent := vm.LastAcceptedBlockInternal().(*Block) key := testKeys[0] - addr := key.PublicKey().Address() + addr := key.Address() ethAddr := testEthAddrs[0] var ( @@ -949,7 +948,7 @@ func TestExportTxAccept(t *testing.T) { }() key := testKeys[0] - addr := key.PublicKey().Address() + addr := key.Address() ethAddr := testEthAddrs[0] var ( @@ -1716,7 +1715,7 @@ func TestNewExportTx(t *testing.T) { Amt: importAmount, OutputOwners: secp256k1fx.OutputOwners{ Threshold: 1, - Addrs: []ids.ShortID{testKeys[0].PublicKey().Address()}, + Addrs: []ids.ShortID{testKeys[0].Address()}, }, }, } @@ -1731,7 +1730,7 @@ func TestNewExportTx(t *testing.T) { Key: inputID[:], Value: utxoBytes, Traits: [][]byte{ - testKeys[0].PublicKey().Address().Bytes(), + testKeys[0].Address().Bytes(), }, }}}}); err != nil { t.Fatal(err) @@ -1823,7 +1822,7 @@ func TestNewExportTx(t *testing.T) { t.Fatal(err) } - addr := utils.GetEthAddress(testKeys[0]) + addr := testKeys[0].EthAddress() 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)) } @@ -1889,7 +1888,7 @@ func TestNewExportTxMulticoin(t *testing.T) { Amt: importAmount, OutputOwners: secp256k1fx.OutputOwners{ Threshold: 1, - Addrs: []ids.ShortID{testKeys[0].PublicKey().Address()}, + Addrs: []ids.ShortID{testKeys[0].Address()}, }, }, } @@ -1910,7 +1909,7 @@ func TestNewExportTxMulticoin(t *testing.T) { Amt: importAmount2, OutputOwners: secp256k1fx.OutputOwners{ Threshold: 1, - Addrs: []ids.ShortID{testKeys[0].PublicKey().Address()}, + Addrs: []ids.ShortID{testKeys[0].Address()}, }, }, } @@ -1926,14 +1925,14 @@ func TestNewExportTxMulticoin(t *testing.T) { Key: inputID[:], Value: utxoBytes, Traits: [][]byte{ - testKeys[0].PublicKey().Address().Bytes(), + testKeys[0].Address().Bytes(), }, }, { Key: inputID2[:], Value: utxoBytes2, Traits: [][]byte{ - testKeys[0].PublicKey().Address().Bytes(), + testKeys[0].Address().Bytes(), }, }, }}}); err != nil { @@ -1971,7 +1970,7 @@ func TestNewExportTxMulticoin(t *testing.T) { parent = vm.LastAcceptedBlockInternal().(*Block) exportAmount := uint64(5000000) - testKeys0Addr := utils.GetEthAddress(testKeys[0]) + testKeys0Addr := testKeys[0].EthAddress() exportId, err := ids.ToShortID(testKeys0Addr[:]) if err != nil { t.Fatal(err) @@ -2023,7 +2022,7 @@ func TestNewExportTxMulticoin(t *testing.T) { t.Fatal(err) } - addr := utils.GetEthAddress(testKeys[0]) + addr := testKeys[0].EthAddress() 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/import_tx_test.go b/plugin/evm/import_tx_test.go index 58f7baa6fc..3195fd7dae 100644 --- a/plugin/evm/import_tx_test.go +++ b/plugin/evm/import_tx_test.go @@ -9,7 +9,6 @@ import ( "github.com/ava-labs/coreth/params" "github.com/ava-labs/coreth/plugin/evm/atomic" - "github.com/ava-labs/coreth/utils" "github.com/ethereum/go-ethereum/common" "github.com/holiman/uint256" @@ -33,7 +32,7 @@ func createImportTxOptions(t *testing.T, vm *VM, sharedMemory *avalancheatomic.M Amt: uint64(50000000), OutputOwners: secp256k1fx.OutputOwners{ Threshold: 1, - Addrs: []ids.ShortID{testKeys[0].PublicKey().Address()}, + Addrs: []ids.ShortID{testKeys[0].Address()}, }, }, } @@ -48,7 +47,7 @@ func createImportTxOptions(t *testing.T, vm *VM, sharedMemory *avalancheatomic.M Key: inputID[:], Value: utxoBytes, Traits: [][]byte{ - testKeys[0].PublicKey().Address().Bytes(), + testKeys[0].Address().Bytes(), }, }}}}); err != nil { t.Fatal(err) @@ -499,7 +498,7 @@ func TestNewImportTx(t *testing.T) { expectedRemainingBalance := new(uint256.Int).Mul( uint256.NewInt(importAmount-actualAVAXBurned), atomic.X2CRate) - addr := utils.GetEthAddress(testKeys[0]) + addr := testKeys[0].EthAddress() if actualBalance := sdb.GetBalance(addr); actualBalance.Cmp(expectedRemainingBalance) != 0 { t.Fatalf("address remaining balance %s equal %s not %s", addr.String(), actualBalance, expectedRemainingBalance) } diff --git a/plugin/evm/service.go b/plugin/evm/service.go index 92529e32c2..40f09314f4 100644 --- a/plugin/evm/service.go +++ b/plugin/evm/service.go @@ -18,7 +18,6 @@ import ( "github.com/ava-labs/coreth/params" "github.com/ava-labs/coreth/plugin/evm/atomic" "github.com/ava-labs/coreth/plugin/evm/client" - "github.com/ava-labs/coreth/utils" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/log" @@ -97,7 +96,7 @@ func (service *AvaxAPI) Version(r *http.Request, _ *struct{}, reply *VersionRepl func (service *AvaxAPI) ExportKey(r *http.Request, args *client.ExportKeyArgs, reply *client.ExportKeyReply) error { log.Info("EVM: ExportKey called") - address, err := utils.ParseEthAddress(args.Address) + address, err := client.ParseEthAddress(args.Address) if err != nil { return fmt.Errorf("couldn't parse %s to address: %s", args.Address, err) } @@ -128,7 +127,7 @@ func (service *AvaxAPI) ImportKey(r *http.Request, args *client.ImportKeyArgs, r return errMissingPrivateKey } - reply.Address = utils.GetEthAddress(args.PrivateKey).Hex() + reply.Address = args.PrivateKey.EthAddress().Hex() service.vm.ctx.Lock.Lock() defer service.vm.ctx.Lock.Unlock() diff --git a/plugin/evm/tx_gossip_test.go b/plugin/evm/tx_gossip_test.go index 99cef8beb3..514b7fadbd 100644 --- a/plugin/evm/tx_gossip_test.go +++ b/plugin/evm/tx_gossip_test.go @@ -48,7 +48,7 @@ func TestEthTxGossip(t *testing.T) { pk, err := secp256k1.NewPrivateKey() require.NoError(err) - address := utils.GetEthAddress(pk) + address := pk.EthAddress() genesis := newPrefundedGenesis(100_000_000_000_000_000, address) genesisBytes, err := genesis.MarshalJSON() require.NoError(err) @@ -176,7 +176,7 @@ func TestAtomicTxGossip(t *testing.T) { pk, err := secp256k1.NewPrivateKey() require.NoError(err) - address := utils.GetEthAddress(pk) + address := pk.EthAddress() genesis := newPrefundedGenesis(100_000_000_000_000_000, address) genesisBytes, err := genesis.MarshalJSON() require.NoError(err) @@ -265,7 +265,7 @@ func TestAtomicTxGossip(t *testing.T) { 0, snowCtx.AVAXAssetID, 100_000_000_000, - pk.PublicKey().Address(), + pk.Address(), ) require.NoError(err) tx, err := atomic.NewImportTx(vm.ctx, vm.currentRules(), vm.clock.Unix(), vm.ctx.XChainID, address, initialBaseFee, secp256k1fx.NewKeychain(pk), []*avax.UTXO{utxo}) @@ -315,7 +315,7 @@ func TestEthTxPushGossipOutbound(t *testing.T) { pk, err := secp256k1.NewPrivateKey() require.NoError(err) - address := utils.GetEthAddress(pk) + address := pk.EthAddress() genesis := newPrefundedGenesis(100_000_000_000_000_000, address) genesisBytes, err := genesis.MarshalJSON() require.NoError(err) @@ -375,7 +375,7 @@ func TestEthTxPushGossipInbound(t *testing.T) { pk, err := secp256k1.NewPrivateKey() require.NoError(err) - address := utils.GetEthAddress(pk) + address := pk.EthAddress() genesis := newPrefundedGenesis(100_000_000_000_000_000, address) genesisBytes, err := genesis.MarshalJSON() require.NoError(err) @@ -434,7 +434,7 @@ func TestAtomicTxPushGossipOutbound(t *testing.T) { pk, err := secp256k1.NewPrivateKey() require.NoError(err) - address := utils.GetEthAddress(pk) + address := pk.EthAddress() genesis := newPrefundedGenesis(100_000_000_000_000_000, address) genesisBytes, err := genesis.MarshalJSON() require.NoError(err) @@ -473,7 +473,7 @@ func TestAtomicTxPushGossipOutbound(t *testing.T) { 0, snowCtx.AVAXAssetID, 100_000_000_000, - pk.PublicKey().Address(), + pk.Address(), ) require.NoError(err) tx, err := atomic.NewImportTx(vm.ctx, vm.currentRules(), vm.clock.Unix(), vm.ctx.XChainID, address, initialBaseFee, secp256k1fx.NewKeychain(pk), []*avax.UTXO{utxo}) @@ -507,7 +507,7 @@ func TestAtomicTxPushGossipInbound(t *testing.T) { pk, err := secp256k1.NewPrivateKey() require.NoError(err) - address := utils.GetEthAddress(pk) + address := pk.EthAddress() genesis := newPrefundedGenesis(100_000_000_000_000_000, address) genesisBytes, err := genesis.MarshalJSON() require.NoError(err) @@ -544,7 +544,7 @@ func TestAtomicTxPushGossipInbound(t *testing.T) { 0, snowCtx.AVAXAssetID, 100_000_000_000, - pk.PublicKey().Address(), + pk.Address(), ) require.NoError(err) tx, err := atomic.NewImportTx(vm.ctx, vm.currentRules(), vm.clock.Unix(), vm.ctx.XChainID, address, initialBaseFee, secp256k1fx.NewKeychain(pk), []*avax.UTXO{utxo}) diff --git a/plugin/evm/user.go b/plugin/evm/user.go index 4a68eca2fb..8cb3f73f34 100644 --- a/plugin/evm/user.go +++ b/plugin/evm/user.go @@ -10,7 +10,6 @@ import ( "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/utils/crypto/secp256k1" "github.com/ava-labs/coreth/plugin/evm/atomic" - "github.com/ava-labs/coreth/utils" "github.com/ethereum/go-ethereum/common" ) @@ -71,7 +70,7 @@ func (u *user) putAddress(privKey *secp256k1.PrivateKey) error { return errKeyNil } - address := utils.GetEthAddress(privKey) // address the privKey controls + address := privKey.EthAddress() // address the privKey controls controlsAddress, err := u.controlsAddress(address) if err != nil { return err diff --git a/plugin/evm/vm_test.go b/plugin/evm/vm_test.go index 668a1b31c2..38f4de9f3c 100644 --- a/plugin/evm/vm_test.go +++ b/plugin/evm/vm_test.go @@ -161,8 +161,8 @@ func init() { b, _ = cb58.Decode(key) pk, _ := secp256k1.ToPrivateKey(b) testKeys = append(testKeys, pk) - testEthAddrs = append(testEthAddrs, utils.GetEthAddress(pk)) - testShortIDAddrs = append(testShortIDAddrs, pk.PublicKey().Address()) + testEthAddrs = append(testEthAddrs, pk.EthAddress()) + testShortIDAddrs = append(testShortIDAddrs, pk.Address()) } } @@ -1390,10 +1390,10 @@ func TestConflictingTransitiveAncestryWithGap(t *testing.T) { } key0 := testKeys[0] - addr0 := key0.PublicKey().Address() + addr0 := key0.Address() key1 := testKeys[1] - addr1 := key1.PublicKey().Address() + addr1 := key1.Address() importAmount := uint64(1000000000) @@ -1533,7 +1533,7 @@ func TestBonusBlocksTxs(t *testing.T) { Amt: importAmount, OutputOwners: secp256k1fx.OutputOwners{ Threshold: 1, - Addrs: []ids.ShortID{testKeys[0].PublicKey().Address()}, + Addrs: []ids.ShortID{testKeys[0].Address()}, }, }, } @@ -1548,7 +1548,7 @@ func TestBonusBlocksTxs(t *testing.T) { Key: inputID[:], Value: utxoBytes, Traits: [][]byte{ - testKeys[0].PublicKey().Address().Bytes(), + testKeys[0].Address().Bytes(), }, }}}}); err != nil { t.Fatal(err) @@ -3071,7 +3071,7 @@ func TestBuildInvalidBlockHead(t *testing.T) { }() key0 := testKeys[0] - addr0 := key0.PublicKey().Address() + addr0 := key0.Address() // Create the transaction utx := &atomic.UnsignedImportTx{ @@ -3228,7 +3228,7 @@ func TestBuildApricotPhase4Block(t *testing.T) { Amt: importAmount, OutputOwners: secp256k1fx.OutputOwners{ Threshold: 1, - Addrs: []ids.ShortID{testKeys[0].PublicKey().Address()}, + Addrs: []ids.ShortID{testKeys[0].Address()}, }, }, } @@ -3243,7 +3243,7 @@ func TestBuildApricotPhase4Block(t *testing.T) { Key: inputID[:], Value: utxoBytes, Traits: [][]byte{ - testKeys[0].PublicKey().Address().Bytes(), + testKeys[0].Address().Bytes(), }, }}}}); err != nil { t.Fatal(err) @@ -3398,7 +3398,7 @@ func TestBuildApricotPhase5Block(t *testing.T) { Amt: importAmount, OutputOwners: secp256k1fx.OutputOwners{ Threshold: 1, - Addrs: []ids.ShortID{testKeys[0].PublicKey().Address()}, + Addrs: []ids.ShortID{testKeys[0].Address()}, }, }, } @@ -3413,7 +3413,7 @@ func TestBuildApricotPhase5Block(t *testing.T) { Key: inputID[:], Value: utxoBytes, Traits: [][]byte{ - testKeys[0].PublicKey().Address().Bytes(), + testKeys[0].Address().Bytes(), }, }}}}); err != nil { t.Fatal(err) diff --git a/scripts/versions.sh b/scripts/versions.sh index 8ea089db0f..46f7f2376e 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:-'e8356d529cbe'} +AVALANCHE_VERSION=${AVALANCHE_VERSION:-'3998475d671d'} diff --git a/utils/utils.go b/utils/utils.go deleted file mode 100644 index af2c0f822d..0000000000 --- a/utils/utils.go +++ /dev/null @@ -1,31 +0,0 @@ -// (c) 2019-2024, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package utils - -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") - -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())) -} From 71babe6231298708d7ad3a2419086f11af438bcc Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Tue, 24 Dec 2024 21:26:34 +0300 Subject: [PATCH 15/27] bump avago --- go.mod | 2 +- go.sum | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index b750d0d897..5d184fd9e5 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.2-0.20241224161435-3998475d671d + github.com/ava-labs/avalanchego v1.12.2-0.20241224181600-fade5be3051d 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 01fbb024fa..40a7c0d3cf 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.2-0.20241224161435-3998475d671d h1:QCtjS4ANcNfCdL6Z2sKpanDVJNt1MU0bUyVdW0g5zuU= github.com/ava-labs/avalanchego v1.12.2-0.20241224161435-3998475d671d/go.mod h1:cDoT0Hq3P+/XfCyVvzrBj66yoid2I5LnMuj7LIkap+o= +github.com/ava-labs/avalanchego v1.12.2-0.20241224181600-fade5be3051d h1:iPlsqC9pIy4emCo8wyI/VmVmfljpzmw58ZqahVdcehI= +github.com/ava-labs/avalanchego v1.12.2-0.20241224181600-fade5be3051d/go.mod h1:dKawab3nXqwI7ZcOFatTOv//l1V0t8MRBnhXoOqbN4E= 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 aab47b6ca331abfe2b3947c2ff47b7ae66766786 Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Tue, 24 Dec 2024 21:32:24 +0300 Subject: [PATCH 16/27] bump e2e avago version --- scripts/versions.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/versions.sh b/scripts/versions.sh index 46f7f2376e..2018ff1398 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:-'3998475d671d'} +AVALANCHE_VERSION=${AVALANCHE_VERSION:-'fade5be3051d'} From 6f1adc8852f69e6b346a24de5682f98cbe92e008 Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Tue, 24 Dec 2024 22:01:31 +0300 Subject: [PATCH 17/27] Update plugin/evm/atomic/gossip_test.go Co-authored-by: Quentin McGaw Signed-off-by: Ceyhun Onur --- plugin/evm/atomic/gossip_test.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugin/evm/atomic/gossip_test.go b/plugin/evm/atomic/gossip_test.go index edd88bae18..c1db9435bb 100644 --- a/plugin/evm/atomic/gossip_test.go +++ b/plugin/evm/atomic/gossip_test.go @@ -27,7 +27,8 @@ func TestGossipAtomicTxMarshaller(t *testing.T) { key0, err := secp256k1.NewPrivateKey() require.NoError(err) - require.NoError(want.Tx.Sign(Codec, [][]*secp256k1.PrivateKey{{key0}})) + err = want.Tx.Sign(Codec, [][]*secp256k1.PrivateKey{{key0}}) + require.NoError(err) bytes, err := marshaller.MarshalGossip(want) require.NoError(err) From 876716ff3ec4568f16b0e8b58dc73e7448dce093 Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Tue, 24 Dec 2024 22:14:31 +0300 Subject: [PATCH 18/27] Update plugin/evm/atomic/mempool.go Co-authored-by: Quentin McGaw Signed-off-by: Ceyhun Onur --- plugin/evm/atomic/mempool.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugin/evm/atomic/mempool.go b/plugin/evm/atomic/mempool.go index 69e1e509b6..f74eacda7e 100644 --- a/plugin/evm/atomic/mempool.go +++ b/plugin/evm/atomic/mempool.go @@ -184,7 +184,7 @@ func (m *Mempool) AddLocalTx(tx *Tx) error { return err } -// forceAddTx forcibly adds a *Tx to the mempool and bypasses all verification. +// ForceAddTx forcibly adds a *Tx to the mempool and bypasses all verification. func (m *Mempool) ForceAddTx(tx *Tx) error { m.lock.Lock() defer m.lock.Unlock() From 682e4bc0e360835feb7a9b0305e20b791eaff756 Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Tue, 24 Dec 2024 22:18:55 +0300 Subject: [PATCH 19/27] Update plugin/evm/config/config.go Co-authored-by: Quentin McGaw Signed-off-by: Ceyhun Onur --- plugin/evm/config/config.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugin/evm/config/config.go b/plugin/evm/config/config.go index 748f9e115a..3b8dca3807 100644 --- a/plugin/evm/config/config.go +++ b/plugin/evm/config/config.go @@ -307,7 +307,7 @@ func (c *Config) Validate(networkID uint32) error { // Ensure that non-standard commit interval is not allowed for production networks if constants.ProductionNetworkIDs.Contains(networkID) { if c.CommitInterval != defaultCommitInterval { - return fmt.Errorf("cannot start non-local network with commit interval %d", c.CommitInterval) + return fmt.Errorf("cannot start non-local network with commit interval %d different than %d", c.CommitInterval, defaultCommitInterval) } if c.StateSyncCommitInterval != defaultSyncableCommitInterval { return fmt.Errorf("cannot start non-local network with syncable interval %d", c.StateSyncCommitInterval) From 38ad86848cf4bc0bea77ea3bbb52b91440ed8f4d Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Tue, 24 Dec 2024 22:19:06 +0300 Subject: [PATCH 20/27] Update plugin/evm/atomic/tx_heap.go Co-authored-by: Quentin McGaw Signed-off-by: Ceyhun Onur --- plugin/evm/atomic/tx_heap.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugin/evm/atomic/tx_heap.go b/plugin/evm/atomic/tx_heap.go index 58cbcf0c0b..bcec314cd7 100644 --- a/plugin/evm/atomic/tx_heap.go +++ b/plugin/evm/atomic/tx_heap.go @@ -1,4 +1,4 @@ -// (c) 2020-2021, Ava Labs, Inc. All rights reserved. +// (c) 2020-2025, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package atomic From de328e93e341cd56b1329369b4e473197ef061d3 Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Tue, 24 Dec 2024 22:19:30 +0300 Subject: [PATCH 21/27] Update plugin/evm/config/config.go Co-authored-by: Quentin McGaw Signed-off-by: Ceyhun Onur --- plugin/evm/config/config.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugin/evm/config/config.go b/plugin/evm/config/config.go index 3b8dca3807..a92405bcb5 100644 --- a/plugin/evm/config/config.go +++ b/plugin/evm/config/config.go @@ -310,7 +310,7 @@ func (c *Config) Validate(networkID uint32) error { return fmt.Errorf("cannot start non-local network with commit interval %d different than %d", c.CommitInterval, defaultCommitInterval) } if c.StateSyncCommitInterval != defaultSyncableCommitInterval { - return fmt.Errorf("cannot start non-local network with syncable interval %d", c.StateSyncCommitInterval) + return fmt.Errorf("cannot start non-local network with syncable interval %d different than %d", c.StateSyncCommitInterval, defaultSyncableCommitInterval) } } From 55c0be98a71794d1b2ef3accbe79bc6ed0e5f48e Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Tue, 24 Dec 2024 22:20:20 +0300 Subject: [PATCH 22/27] fix reviews --- peer/network.go | 1 - peer/network_test.go | 20 +++++++++++--------- plugin/evm/atomic/gossip_test.go | 20 ++++++++------------ plugin/evm/atomic/test_tx.go | 2 -- plugin/evm/atomic_trie_iterator_test.go | 3 +-- 5 files changed, 20 insertions(+), 26 deletions(-) diff --git a/peer/network.go b/peer/network.go index ebe067fe0c..a4dfd015f6 100644 --- a/peer/network.go +++ b/peer/network.go @@ -340,7 +340,6 @@ func (n *network) markRequestFulfilled(requestID uint32) (message.ResponseHandle // from a peer. An error returned by this function is treated as fatal by the // engine. func (n *network) AppGossip(ctx context.Context, nodeID ids.NodeID, gossipBytes []byte) error { - log.Debug("forwarding AppGossip to SDK network", "nodeID", nodeID, "gossipLen", len(gossipBytes)) return n.p2pNetwork.AppGossip(ctx, nodeID, gossipBytes) } diff --git a/peer/network_test.go b/peer/network_test.go index 3d13c6e679..6f357be874 100644 --- a/peer/network_test.go +++ b/peer/network_test.go @@ -8,7 +8,7 @@ import ( "errors" "fmt" "sync" - syncatomic "sync/atomic" + "sync/atomic" "testing" "time" @@ -30,6 +30,10 @@ import ( "github.com/ava-labs/avalanchego/version" ) +const ( + codecVersion uint16 = 0 +) + var ( defaultPeerVersion = &version.Application{ Major: 1, @@ -37,8 +41,6 @@ var ( Patch: 0, } - codecVersion uint16 = 0 - _ message.Request = &HelloRequest{} _ = &HelloResponse{} _ = &GreetingRequest{} @@ -85,7 +87,7 @@ func TestRequestAnyRequestsRoutingAndResponse(t *testing.T) { if err := net.AppResponse(context.Background(), nodeID, requestID, responseBytes); err != nil { panic(err) } - syncatomic.AddUint32(&callNum, 1) + atomic.AddUint32(&callNum, 1) }() return nil }, @@ -130,7 +132,7 @@ func TestRequestAnyRequestsRoutingAndResponse(t *testing.T) { requestWg.Wait() senderWg.Wait() - assert.Equal(t, totalCalls, int(syncatomic.LoadUint32(&callNum))) + assert.Equal(t, totalCalls, int(atomic.LoadUint32(&callNum))) } func TestAppRequestOnCtxCancellation(t *testing.T) { @@ -190,7 +192,7 @@ func TestRequestRequestsRoutingAndResponse(t *testing.T) { if err := net.AppResponse(context.Background(), nodeID, requestID, responseBytes); err != nil { panic(err) } - syncatomic.AddUint32(&callNum, 1) + atomic.AddUint32(&callNum, 1) }() return nil }, @@ -245,7 +247,7 @@ func TestRequestRequestsRoutingAndResponse(t *testing.T) { requestWg.Wait() senderWg.Wait() - assert.Equal(t, totalCalls, int(syncatomic.LoadUint32(&callNum))) + assert.Equal(t, totalCalls, int(atomic.LoadUint32(&callNum))) for _, nodeID := range nodes { if _, exists := contactedNodes[nodeID]; !exists { t.Fatalf("expected nodeID %s to be contacted but was not", nodeID) @@ -386,13 +388,13 @@ func TestRequestMinVersion(t *testing.T) { var net Network sender := testAppSender{ sendAppRequestFn: func(_ context.Context, nodes set.Set[ids.NodeID], reqID uint32, messageBytes []byte) error { - syncatomic.AddUint32(&callNum, 1) + atomic.AddUint32(&callNum, 1) assert.True(t, nodes.Contains(nodeID), "request nodes should contain expected nodeID") assert.Len(t, nodes, 1, "request nodes should contain exactly one node") go func() { time.Sleep(200 * time.Millisecond) - syncatomic.AddUint32(&callNum, 1) + atomic.AddUint32(&callNum, 1) responseBytes, err := codecManager.Marshal(codecVersion, TestMessage{Message: "this is a response"}) if err != nil { panic(err) diff --git a/plugin/evm/atomic/gossip_test.go b/plugin/evm/atomic/gossip_test.go index edd88bae18..920244432b 100644 --- a/plugin/evm/atomic/gossip_test.go +++ b/plugin/evm/atomic/gossip_test.go @@ -56,11 +56,10 @@ func TestAtomicMempoolIterate(t *testing.T) { } tests := []struct { - name string - add []*GossipAtomicTx - f func(tx *GossipAtomicTx) bool - possibleValues []*GossipAtomicTx - expectedLen int + name string + add []*GossipAtomicTx + f func(tx *GossipAtomicTx) bool + expectedTxs []*GossipAtomicTx }{ { name: "func matches nothing", @@ -68,7 +67,7 @@ func TestAtomicMempoolIterate(t *testing.T) { f: func(*GossipAtomicTx) bool { return false }, - possibleValues: nil, + expectedTxs: []*GossipAtomicTx{}, }, { name: "func matches all", @@ -76,8 +75,7 @@ func TestAtomicMempoolIterate(t *testing.T) { f: func(*GossipAtomicTx) bool { return true }, - possibleValues: txs, - expectedLen: 2, + expectedTxs: txs, }, { name: "func matches subset", @@ -85,8 +83,7 @@ func TestAtomicMempoolIterate(t *testing.T) { f: func(tx *GossipAtomicTx) bool { return tx.Tx == txs[0].Tx }, - possibleValues: txs, - expectedLen: 1, + expectedTxs: []*GossipAtomicTx{txs[0]}, }, } @@ -113,8 +110,7 @@ func TestAtomicMempoolIterate(t *testing.T) { m.Iterate(f) - require.Len(matches, tt.expectedLen) - require.Subset(tt.possibleValues, matches) + require.ElementsMatch(tt.expectedTxs, matches) }) } } diff --git a/plugin/evm/atomic/test_tx.go b/plugin/evm/atomic/test_tx.go index 50af59e09f..dcf62258b8 100644 --- a/plugin/evm/atomic/test_tx.go +++ b/plugin/evm/atomic/test_tx.go @@ -29,8 +29,6 @@ func init() { errs := wrappers.Errs{} errs.Add( c.RegisterType(&TestUnsignedTx{}), - c.RegisterType(&avalancheatomic.Element{}), - c.RegisterType(&avalancheatomic.Requests{}), TestTxCodec.RegisterCodec(atomic.CodecVersion, c), ) diff --git a/plugin/evm/atomic_trie_iterator_test.go b/plugin/evm/atomic_trie_iterator_test.go index 50ba586ffd..21ec9913f9 100644 --- a/plugin/evm/atomic_trie_iterator_test.go +++ b/plugin/evm/atomic_trie_iterator_test.go @@ -11,11 +11,10 @@ import ( "github.com/ava-labs/avalanchego/database/versiondb" "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/utils" + "github.com/ava-labs/coreth/plugin/evm/atomic" "github.com/ethereum/go-ethereum/common" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - - "github.com/ava-labs/coreth/plugin/evm/atomic" ) func testSharedMemory() avalancheatomic.SharedMemory { From 803e50a1c3de57d614c8f92fe7a72a7d3c5640dd Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Sun, 29 Dec 2024 11:34:45 +0300 Subject: [PATCH 23/27] fix linter --- utils/snow.go | 1 - 1 file changed, 1 deletion(-) diff --git a/utils/snow.go b/utils/snow.go index e24c884b16..7f92db66a9 100644 --- a/utils/snow.go +++ b/utils/snow.go @@ -23,7 +23,6 @@ import ( var ( testCChainID = ids.ID{'c', 'c', 'h', 'a', 'i', 'n', 't', 'e', 's', 't'} testXChainID = ids.ID{'t', 'e', 's', 't', 'x'} - testChainID = ids.ID{'t', 'e', 's', 't', 'c', 'h', 'a', 'i', 'n'} TestAvaxAssetID = ids.ID{1, 2, 3} ) From 0b4018573527f506cba29af686fe2f3a31821b96 Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Sun, 29 Dec 2024 13:30:52 +0300 Subject: [PATCH 24/27] nits --- plugin/evm/atomic/atomic_backend.go | 10 -------- plugin/evm/atomic/atomic_syncer.go | 9 ++++++++ plugin/evm/atomic/test_shared_memories.go | 1 + plugin/evm/database/wrapped_database.go | 28 +++++++++++++++++------ plugin/evm/syncervm_client.go | 2 +- 5 files changed, 32 insertions(+), 18 deletions(-) diff --git a/plugin/evm/atomic/atomic_backend.go b/plugin/evm/atomic/atomic_backend.go index eefb254321..2532261a10 100644 --- a/plugin/evm/atomic/atomic_backend.go +++ b/plugin/evm/atomic/atomic_backend.go @@ -4,7 +4,6 @@ package atomic import ( - "context" "encoding/binary" "fmt" "time" @@ -22,15 +21,6 @@ import ( "github.com/ethereum/go-ethereum/log" ) -// Syncer represents a step in state sync, -// along with Start/Done methods to control -// and monitor progress. -// Error returns an error if any was encountered. -type Syncer interface { - Start(ctx context.Context) error - Done() <-chan error -} - var _ AtomicBackend = &atomicBackend{} var ( diff --git a/plugin/evm/atomic/atomic_syncer.go b/plugin/evm/atomic/atomic_syncer.go index daffb9d771..52a8376319 100644 --- a/plugin/evm/atomic/atomic_syncer.go +++ b/plugin/evm/atomic/atomic_syncer.go @@ -24,6 +24,15 @@ var ( _ syncclient.LeafSyncTask = &atomicSyncerLeafTask{} ) +// Syncer represents a step in state sync, +// along with Start/Done methods to control +// and monitor progress. +// Error returns an error if any was encountered. +type Syncer interface { + Start(ctx context.Context) error + Done() <-chan error +} + // atomicSyncer is used to sync the atomic trie from the network. The CallbackLeafSyncer // is responsible for orchestrating the sync while atomicSyncer is responsible for maintaining // the state of progress and writing the actual atomic trie to the trieDB. diff --git a/plugin/evm/atomic/test_shared_memories.go b/plugin/evm/atomic/test_shared_memories.go index f526748b03..76aa1bcf87 100644 --- a/plugin/evm/atomic/test_shared_memories.go +++ b/plugin/evm/atomic/test_shared_memories.go @@ -68,6 +68,7 @@ func (s *SharedMemories) assertOpsNotApplied(t *testing.T, ops map[ids.ID]*atomi } } +// TODO: once tetsts are moved to atomic package, unexport this function func NewSharedMemories(atomicMemory *atomic.Memory, thisChainID, peerChainID ids.ID) *SharedMemories { return &SharedMemories{ thisChain: atomicMemory.NewSharedMemory(thisChainID), diff --git a/plugin/evm/database/wrapped_database.go b/plugin/evm/database/wrapped_database.go index f8a36913bb..9421e514a8 100644 --- a/plugin/evm/database/wrapped_database.go +++ b/plugin/evm/database/wrapped_database.go @@ -17,15 +17,23 @@ var ( ) // ethDbWrapper implements ethdb.Database -type ethDbWrapper struct{ database.Database } +type ethDbWrapper struct { + database.Database +} -func WrapDatabase(db database.Database) ethdb.KeyValueStore { return ethDbWrapper{db} } +func WrapDatabase(db database.Database) ethdb.KeyValueStore { + return ethDbWrapper{db} +} // Stat implements ethdb.Database -func (db ethDbWrapper) Stat(string) (string, error) { return "", database.ErrNotFound } +func (db ethDbWrapper) Stat(string) (string, error) { + return "", database.ErrNotFound +} // NewBatch implements ethdb.Database -func (db ethDbWrapper) NewBatch() ethdb.Batch { return wrappedBatch{db.Database.NewBatch()} } +func (db ethDbWrapper) NewBatch() ethdb.Batch { + return wrappedBatch{db.Database.NewBatch()} +} // NewBatchWithSize implements ethdb.Database // TODO: propagate size through avalanchego Database interface @@ -59,10 +67,16 @@ func (db ethDbWrapper) NewIteratorWithStart(start []byte) ethdb.Iterator { } // wrappedBatch implements ethdb.wrappedBatch -type wrappedBatch struct{ database.Batch } +type wrappedBatch struct { + database.Batch +} // ValueSize implements ethdb.Batch -func (batch wrappedBatch) ValueSize() int { return batch.Batch.Size() } +func (batch wrappedBatch) ValueSize() int { + return batch.Batch.Size() +} // Replay implements ethdb.Batch -func (batch wrappedBatch) Replay(w ethdb.KeyValueWriter) error { return batch.Batch.Replay(w) } +func (batch wrappedBatch) Replay(w ethdb.KeyValueWriter) error { + return batch.Batch.Replay(w) +} diff --git a/plugin/evm/syncervm_client.go b/plugin/evm/syncervm_client.go index 15a5b384f5..ba9922d33d 100644 --- a/plugin/evm/syncervm_client.go +++ b/plugin/evm/syncervm_client.go @@ -348,7 +348,7 @@ func (client *stateSyncerClient) finishSync() error { evmBlockGetter, ok := wrappedBlock.(EthBlockWrapper) if !ok { - return fmt.Errorf("could not convert block(%T) to evm.Block", stateBlock) + return fmt.Errorf("could not convert block(%T) to evm.EthBlockWrapper", stateBlock) } block := evmBlockGetter.GetEthBlock() From 2f1dbc5fccff317cf0f2516216d04279ae04fd8a Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Fri, 3 Jan 2025 13:30:38 +0300 Subject: [PATCH 25/27] Update plugin/evm/vm_test.go Co-authored-by: Darioush Jalali Signed-off-by: Ceyhun Onur --- plugin/evm/vm_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugin/evm/vm_test.go b/plugin/evm/vm_test.go index bcb62d42af..2f6d6e08aa 100644 --- a/plugin/evm/vm_test.go +++ b/plugin/evm/vm_test.go @@ -1545,7 +1545,7 @@ func TestBonusBlocksTxs(t *testing.T) { // Make [blk] a bonus block. wrappedBackend := &wrappedBackend{ AtomicBackend: vm.atomicBackend, - registeredBonusBlocks: map[uint64]common.Hash{1: common.Hash(blk.ID())}, + registeredBonusBlocks: map[uint64]common.Hash{blk.Height(): common.Hash(blk.ID())}, } vm.atomicBackend = wrappedBackend From 936bb5901b4fe53a50ba60f11cda71624e0543d6 Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Fri, 3 Jan 2025 13:30:50 +0300 Subject: [PATCH 26/27] Update plugin/evm/syncervm_test.go Co-authored-by: Darioush Jalali Signed-off-by: Ceyhun Onur --- plugin/evm/syncervm_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/plugin/evm/syncervm_test.go b/plugin/evm/syncervm_test.go index 6d07b6f721..90d38b521b 100644 --- a/plugin/evm/syncervm_test.go +++ b/plugin/evm/syncervm_test.go @@ -364,6 +364,7 @@ func createSyncServerAndClientVMs(t *testing.T, test syncTest, numBlocks int) *s serverVM.StateSyncServer.(*stateSyncServer).syncableInterval = test.syncableInterval // initialise [syncerVM] with blank genesis state + // we also override [syncerVM]'s commit interval so the atomic trie works correctly. stateSyncEnabledJSON := fmt.Sprintf(`{"state-sync-enabled":true, "state-sync-min-blocks": %d, "tx-lookup-limit": %d, "commit-interval": %d}`, test.stateSyncMinBlocks, 4, test.syncableInterval) syncerEngineChan, syncerVM, syncerDB, syncerAtomicMemory, syncerAppSender := GenesisVMWithUTXOs( t, false, "", stateSyncEnabledJSON, "", alloc, From 4019a343f23fe8f8501a94dbf0c9e19bb4c07eda Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Fri, 3 Jan 2025 14:08:56 +0300 Subject: [PATCH 27/27] review fix --- plugin/evm/atomic/atomic_syncer_test.go | 2 +- .../evm/atomic/atomic_trie_iterator_test.go | 10 ++--- plugin/evm/atomic/atomic_trie_test.go | 40 +++++++++---------- .../evm/atomic/atomic_tx_repository_test.go | 10 ++--- plugin/evm/atomic/test_shared_memories.go | 1 + plugin/evm/atomic/test_tx.go | 6 +-- plugin/evm/block.go | 2 +- plugin/evm/export_tx_test.go | 2 +- plugin/evm/import_tx_test.go | 2 +- plugin/evm/syncervm_client.go | 12 ++++-- plugin/evm/syncervm_test.go | 8 ++-- plugin/evm/vm.go | 4 +- plugin/evm/vm_test.go | 7 +--- utils/snow.go | 10 ++--- 14 files changed, 59 insertions(+), 57 deletions(-) diff --git a/plugin/evm/atomic/atomic_syncer_test.go b/plugin/evm/atomic/atomic_syncer_test.go index 140a627710..6ff0da417a 100644 --- a/plugin/evm/atomic/atomic_syncer_test.go +++ b/plugin/evm/atomic/atomic_syncer_test.go @@ -57,7 +57,7 @@ func testAtomicSyncer(t *testing.T, serverTrieDB *triedb.Database, targetHeight if err != nil { t.Fatal("could not initialize atomix tx repository", err) } - atomicBackend, err := NewAtomicBackend(clientDB, utils.TestSharedMemory(), nil, repo, 0, common.Hash{}, commitInterval) + atomicBackend, err := NewAtomicBackend(clientDB, utils.TestSnowContext().SharedMemory, nil, repo, 0, common.Hash{}, commitInterval) if err != nil { t.Fatal("could not initialize atomic backend", err) } diff --git a/plugin/evm/atomic/atomic_trie_iterator_test.go b/plugin/evm/atomic/atomic_trie_iterator_test.go index 8766752b28..03435d713d 100644 --- a/plugin/evm/atomic/atomic_trie_iterator_test.go +++ b/plugin/evm/atomic/atomic_trie_iterator_test.go @@ -20,7 +20,7 @@ import ( func TestIteratorCanIterate(t *testing.T) { lastAcceptedHeight := uint64(1000) db := versiondb.New(memdb.New()) - codec := TestTxCodec + codec := testTxCodec repo, err := NewAtomicTxRepository(db, codec, lastAcceptedHeight) assert.NoError(t, err) @@ -32,7 +32,7 @@ func TestIteratorCanIterate(t *testing.T) { // create an atomic trie // on create it will initialize all the transactions from the above atomic repository - atomicBackend, err := NewAtomicBackend(db, utils.TestSharedMemory(), nil, repo, lastAcceptedHeight, common.Hash{}, 100) + atomicBackend, err := NewAtomicBackend(db, utils.TestSnowContext().SharedMemory, nil, repo, lastAcceptedHeight, common.Hash{}, 100) assert.NoError(t, err) atomicTrie1 := atomicBackend.AtomicTrie() @@ -45,7 +45,7 @@ func TestIteratorCanIterate(t *testing.T) { // iterate on a new atomic trie to make sure there is no resident state affecting the data and the // iterator - atomicBackend2, err := NewAtomicBackend(db, utils.TestSharedMemory(), nil, repo, lastAcceptedHeight, common.Hash{}, 100) + atomicBackend2, err := NewAtomicBackend(db, utils.TestSnowContext().SharedMemory, nil, repo, lastAcceptedHeight, common.Hash{}, 100) assert.NoError(t, err) atomicTrie2 := atomicBackend2.AtomicTrie() lastCommittedHash2, lastCommittedHeight2 := atomicTrie2.LastCommitted() @@ -60,7 +60,7 @@ func TestIteratorHandlesInvalidData(t *testing.T) { require := require.New(t) lastAcceptedHeight := uint64(1000) db := versiondb.New(memdb.New()) - codec := TestTxCodec + codec := testTxCodec repo, err := NewAtomicTxRepository(db, codec, lastAcceptedHeight) require.NoError(err) @@ -73,7 +73,7 @@ func TestIteratorHandlesInvalidData(t *testing.T) { // create an atomic trie // on create it will initialize all the transactions from the above atomic repository commitInterval := uint64(100) - atomicBackend, err := NewAtomicBackend(db, utils.TestSharedMemory(), nil, repo, lastAcceptedHeight, common.Hash{}, commitInterval) + atomicBackend, err := NewAtomicBackend(db, utils.TestSnowContext().SharedMemory, nil, repo, lastAcceptedHeight, common.Hash{}, commitInterval) require.NoError(err) atomicTrie := atomicBackend.AtomicTrie() diff --git a/plugin/evm/atomic/atomic_trie_test.go b/plugin/evm/atomic/atomic_trie_test.go index 4c9c84468a..7a48c75745 100644 --- a/plugin/evm/atomic/atomic_trie_test.go +++ b/plugin/evm/atomic/atomic_trie_test.go @@ -131,7 +131,7 @@ func TestAtomicTrieInitialize(t *testing.T) { } { t.Run(name, func(t *testing.T) { db := versiondb.New(memdb.New()) - codec := TestTxCodec + codec := testTxCodec repo, err := NewAtomicTxRepository(db, codec, test.lastAcceptedHeight) if err != nil { t.Fatal(err) @@ -140,7 +140,7 @@ func TestAtomicTrieInitialize(t *testing.T) { writeTxs(t, repo, 1, test.lastAcceptedHeight+1, test.numTxsPerBlock, nil, operationsMap) // Construct the atomic trie for the first time - atomicBackend1, err := NewAtomicBackend(db, utils.TestSharedMemory(), nil, repo, test.lastAcceptedHeight, common.Hash{}, test.commitInterval) + atomicBackend1, err := NewAtomicBackend(db, utils.TestSnowContext().SharedMemory, nil, repo, test.lastAcceptedHeight, common.Hash{}, test.commitInterval) if err != nil { t.Fatal(err) } @@ -156,7 +156,7 @@ func TestAtomicTrieInitialize(t *testing.T) { verifyOperations(t, atomicTrie1, codec, rootHash1, 1, test.expectedCommitHeight, operationsMap) // Construct the atomic trie again (on the same database) and ensure the last accepted root is correct. - atomicBackend2, err := NewAtomicBackend(db, utils.TestSharedMemory(), nil, repo, test.lastAcceptedHeight, common.Hash{}, test.commitInterval) + atomicBackend2, err := NewAtomicBackend(db, utils.TestSnowContext().SharedMemory, nil, repo, test.lastAcceptedHeight, common.Hash{}, test.commitInterval) if err != nil { t.Fatal(err) } @@ -165,7 +165,7 @@ func TestAtomicTrieInitialize(t *testing.T) { // Construct the atomic trie again (on an empty database) and ensure that it produces the same hash. atomicBackend3, err := NewAtomicBackend( - versiondb.New(memdb.New()), utils.TestSharedMemory(), nil, repo, test.lastAcceptedHeight, common.Hash{}, test.commitInterval, + versiondb.New(memdb.New()), utils.TestSnowContext().SharedMemory, nil, repo, test.lastAcceptedHeight, common.Hash{}, test.commitInterval, ) if err != nil { t.Fatal(err) @@ -203,7 +203,7 @@ func TestAtomicTrieInitialize(t *testing.T) { // Generate a new atomic trie to compare the root against. atomicBackend4, err := NewAtomicBackend( - versiondb.New(memdb.New()), utils.TestSharedMemory(), nil, repo, nextCommitHeight, common.Hash{}, test.commitInterval, + versiondb.New(memdb.New()), utils.TestSnowContext().SharedMemory, nil, repo, nextCommitHeight, common.Hash{}, test.commitInterval, ) if err != nil { t.Fatal(err) @@ -220,14 +220,14 @@ func TestAtomicTrieInitialize(t *testing.T) { func TestIndexerInitializesOnlyOnce(t *testing.T) { lastAcceptedHeight := uint64(25) db := versiondb.New(memdb.New()) - codec := TestTxCodec + codec := testTxCodec repo, err := NewAtomicTxRepository(db, codec, lastAcceptedHeight) assert.NoError(t, err) operationsMap := make(map[uint64]map[ids.ID]*avalancheatomic.Requests) writeTxs(t, repo, 1, lastAcceptedHeight+1, constTxsPerHeight(2), nil, operationsMap) // Initialize atomic repository - atomicBackend, err := NewAtomicBackend(db, utils.TestSharedMemory(), nil, repo, lastAcceptedHeight, common.Hash{}, 10 /* commitInterval*/) + atomicBackend, err := NewAtomicBackend(db, utils.TestSnowContext().SharedMemory, nil, repo, lastAcceptedHeight, common.Hash{}, 10 /* commitInterval*/) assert.NoError(t, err) atomicTrie := atomicBackend.AtomicTrie() @@ -243,7 +243,7 @@ func TestIndexerInitializesOnlyOnce(t *testing.T) { assert.NoError(t, err) // Re-initialize the atomic trie - atomicBackend, err = NewAtomicBackend(db, utils.TestSharedMemory(), nil, repo, lastAcceptedHeight, common.Hash{}, 10 /* commitInterval */) + atomicBackend, err = NewAtomicBackend(db, utils.TestSnowContext().SharedMemory, nil, repo, lastAcceptedHeight, common.Hash{}, 10 /* commitInterval */) assert.NoError(t, err) atomicTrie = atomicBackend.AtomicTrie() @@ -254,11 +254,11 @@ func TestIndexerInitializesOnlyOnce(t *testing.T) { func newTestAtomicTrie(t *testing.T) AtomicTrie { db := versiondb.New(memdb.New()) - repo, err := NewAtomicTxRepository(db, TestTxCodec, 0) + repo, err := NewAtomicTxRepository(db, testTxCodec, 0) if err != nil { t.Fatal(err) } - atomicBackend, err := NewAtomicBackend(db, utils.TestSharedMemory(), nil, repo, 0, common.Hash{}, testCommitInterval) + atomicBackend, err := NewAtomicBackend(db, utils.TestSnowContext().SharedMemory, nil, repo, 0, common.Hash{}, testCommitInterval) if err != nil { t.Fatal(err) } @@ -332,7 +332,7 @@ func TestAtomicTrieDoesNotSkipBonusBlocks(t *testing.T) { commitInterval := uint64(10) expectedCommitHeight := uint64(100) db := versiondb.New(memdb.New()) - codec := TestTxCodec + codec := testTxCodec repo, err := NewAtomicTxRepository(db, codec, lastAcceptedHeight) if err != nil { t.Fatal(err) @@ -346,7 +346,7 @@ func TestAtomicTrieDoesNotSkipBonusBlocks(t *testing.T) { 14: {}, } // Construct the atomic trie for the first time - atomicBackend, err := NewAtomicBackend(db, utils.TestSharedMemory(), bonusBlocks, repo, lastAcceptedHeight, common.Hash{}, commitInterval) + atomicBackend, err := NewAtomicBackend(db, utils.TestSnowContext().SharedMemory, bonusBlocks, repo, lastAcceptedHeight, common.Hash{}, commitInterval) if err != nil { t.Fatal(err) } @@ -453,7 +453,7 @@ func TestApplyToSharedMemory(t *testing.T) { } { t.Run(name, func(t *testing.T) { db := versiondb.New(memdb.New()) - codec := TestTxCodec + codec := testTxCodec repo, err := NewAtomicTxRepository(db, codec, test.lastAcceptedHeight) assert.NoError(t, err) operationsMap := make(map[uint64]map[ids.ID]*avalancheatomic.Requests) @@ -519,7 +519,7 @@ func TestApplyToSharedMemory(t *testing.T) { func BenchmarkAtomicTrieInit(b *testing.B) { db := versiondb.New(memdb.New()) - codec := TestTxCodec + codec := testTxCodec operationsMap := make(map[uint64]map[ids.ID]*avalancheatomic.Requests) @@ -537,7 +537,7 @@ func BenchmarkAtomicTrieInit(b *testing.B) { b.ReportAllocs() b.ResetTimer() for i := 0; i < b.N; i++ { - sharedMemory := utils.TestSharedMemory() + sharedMemory := utils.TestSnowContext().SharedMemory atomicBackend, err := NewAtomicBackend(db, sharedMemory, nil, repo, lastAcceptedHeight, common.Hash{}, 5000) assert.NoError(b, err) atomicTrie = atomicBackend.AtomicTrie() @@ -554,7 +554,7 @@ func BenchmarkAtomicTrieInit(b *testing.B) { func BenchmarkAtomicTrieIterate(b *testing.B) { db := versiondb.New(memdb.New()) - codec := TestTxCodec + codec := testTxCodec operationsMap := make(map[uint64]map[ids.ID]*avalancheatomic.Requests) @@ -564,7 +564,7 @@ func BenchmarkAtomicTrieIterate(b *testing.B) { assert.NoError(b, err) writeTxs(b, repo, 1, lastAcceptedHeight, constTxsPerHeight(3), nil, operationsMap) - atomicBackend, err := NewAtomicBackend(db, utils.TestSharedMemory(), nil, repo, lastAcceptedHeight, common.Hash{}, 5000) + atomicBackend, err := NewAtomicBackend(db, utils.TestSnowContext().SharedMemory, nil, repo, lastAcceptedHeight, common.Hash{}, 5000) assert.NoError(b, err) atomicTrie := atomicBackend.AtomicTrie() @@ -633,8 +633,8 @@ func BenchmarkApplyToSharedMemory(b *testing.B) { func benchmarkApplyToSharedMemory(b *testing.B, disk database.Database, blocks uint64) { db := versiondb.New(disk) - codec := TestTxCodec - sharedMemory := utils.TestSharedMemory() + codec := testTxCodec + sharedMemory := utils.TestSnowContext().SharedMemory lastAcceptedHeight := blocks repo, err := NewAtomicTxRepository(db, codec, lastAcceptedHeight) @@ -659,7 +659,7 @@ func benchmarkApplyToSharedMemory(b *testing.B, disk database.Database, blocks u b.ReportAllocs() b.ResetTimer() for i := 0; i < b.N; i++ { - backend.(*atomicBackend).sharedMemory = utils.TestSharedMemory() + backend.(*atomicBackend).sharedMemory = utils.TestSnowContext().SharedMemory assert.NoError(b, backend.MarkApplyToSharedMemoryCursor(0)) assert.NoError(b, db.Commit()) assert.NoError(b, backend.ApplyToSharedMemory(lastAcceptedHeight)) diff --git a/plugin/evm/atomic/atomic_tx_repository_test.go b/plugin/evm/atomic/atomic_tx_repository_test.go index 4dda70ab10..209cf1d29c 100644 --- a/plugin/evm/atomic/atomic_tx_repository_test.go +++ b/plugin/evm/atomic/atomic_tx_repository_test.go @@ -182,7 +182,7 @@ func verifyOperations(t testing.TB, atomicTrie AtomicTrie, codec codec.Manager, func TestAtomicRepositoryReadWriteSingleTx(t *testing.T) { db := versiondb.New(memdb.New()) - codec := TestTxCodec + codec := testTxCodec repo, err := NewAtomicTxRepository(db, codec, 0) if err != nil { t.Fatal(err) @@ -195,7 +195,7 @@ func TestAtomicRepositoryReadWriteSingleTx(t *testing.T) { func TestAtomicRepositoryReadWriteMultipleTxs(t *testing.T) { db := versiondb.New(memdb.New()) - codec := TestTxCodec + codec := testTxCodec repo, err := NewAtomicTxRepository(db, codec, 0) if err != nil { t.Fatal(err) @@ -208,7 +208,7 @@ func TestAtomicRepositoryReadWriteMultipleTxs(t *testing.T) { func TestAtomicRepositoryPreAP5Migration(t *testing.T) { db := versiondb.New(memdb.New()) - codec := TestTxCodec + codec := testTxCodec acceptedAtomicTxDB := prefixdb.New(atomicTxIDDBPrefix, db) txMap := make(map[uint64][]*Tx) @@ -233,7 +233,7 @@ func TestAtomicRepositoryPreAP5Migration(t *testing.T) { func TestAtomicRepositoryPostAP5Migration(t *testing.T) { db := versiondb.New(memdb.New()) - codec := TestTxCodec + codec := testTxCodec acceptedAtomicTxDB := prefixdb.New(atomicTxIDDBPrefix, db) txMap := make(map[uint64][]*Tx) @@ -258,7 +258,7 @@ func TestAtomicRepositoryPostAP5Migration(t *testing.T) { func benchAtomicRepositoryIndex10_000(b *testing.B, maxHeight uint64, txsPerHeight int) { db := versiondb.New(memdb.New()) - codec := TestTxCodec + codec := testTxCodec acceptedAtomicTxDB := prefixdb.New(atomicTxIDDBPrefix, db) txMap := make(map[uint64][]*Tx) diff --git a/plugin/evm/atomic/test_shared_memories.go b/plugin/evm/atomic/test_shared_memories.go index f526748b03..2f92639a5c 100644 --- a/plugin/evm/atomic/test_shared_memories.go +++ b/plugin/evm/atomic/test_shared_memories.go @@ -68,6 +68,7 @@ func (s *SharedMemories) assertOpsNotApplied(t *testing.T, ops map[ids.ID]*atomi } } +// TODO: once tests are moved to atomic package, unexport this function func NewSharedMemories(atomicMemory *atomic.Memory, thisChainID, peerChainID ids.ID) *SharedMemories { return &SharedMemories{ thisChain: atomicMemory.NewSharedMemory(thisChainID), diff --git a/plugin/evm/atomic/test_tx.go b/plugin/evm/atomic/test_tx.go index 21adecfd39..637997b505 100644 --- a/plugin/evm/atomic/test_tx.go +++ b/plugin/evm/atomic/test_tx.go @@ -20,10 +20,10 @@ import ( "github.com/ava-labs/coreth/params" ) -var TestTxCodec codec.Manager +var testTxCodec codec.Manager func init() { - TestTxCodec = codec.NewDefaultManager() + testTxCodec = codec.NewDefaultManager() c := linearcodec.NewDefault() errs := wrappers.Errs{} @@ -31,7 +31,7 @@ func init() { c.RegisterType(&TestUnsignedTx{}), c.RegisterType(&avalancheatomic.Element{}), c.RegisterType(&avalancheatomic.Requests{}), - TestTxCodec.RegisterCodec(atomic.CodecVersion, c), + testTxCodec.RegisterCodec(atomic.CodecVersion, c), ) if errs.Errored() { diff --git a/plugin/evm/block.go b/plugin/evm/block.go index ffadf284f3..d139b640a0 100644 --- a/plugin/evm/block.go +++ b/plugin/evm/block.go @@ -158,7 +158,7 @@ func (b *Block) Accept(context.Context) error { return fmt.Errorf("chain could not accept %s: %w", b.ID(), err) } - if err := vm.PutLastAcceptedID(b.id[:]); err != nil { + if err := vm.PutLastAcceptedID(b.id); err != nil { return fmt.Errorf("failed to put %s as the last accepted block: %w", b.ID(), err) } diff --git a/plugin/evm/export_tx_test.go b/plugin/evm/export_tx_test.go index 4bc0999797..417bba57f2 100644 --- a/plugin/evm/export_tx_test.go +++ b/plugin/evm/export_tx_test.go @@ -1261,7 +1261,7 @@ func TestExportTxVerify(t *testing.T) { { Address: testEthAddrs[0], Amount: 0, - AssetID: utils.TestAvaxAssetID, + AssetID: ctx.AVAXAssetID, Nonce: 0, }, } diff --git a/plugin/evm/import_tx_test.go b/plugin/evm/import_tx_test.go index 4ce2f9284e..e2f44b3974 100644 --- a/plugin/evm/import_tx_test.go +++ b/plugin/evm/import_tx_test.go @@ -317,7 +317,7 @@ func TestImportTxVerify(t *testing.T) { { Address: testEthAddrs[0], Amount: 0, - AssetID: utils.TestAvaxAssetID, + AssetID: ctx.AVAXAssetID, }, } return &tx diff --git a/plugin/evm/syncervm_client.go b/plugin/evm/syncervm_client.go index 15a5b384f5..35e2cf5e9b 100644 --- a/plugin/evm/syncervm_client.go +++ b/plugin/evm/syncervm_client.go @@ -30,7 +30,7 @@ import ( "github.com/ethereum/go-ethereum/log" ) -// State sync fetches [StateSyncParentsToFetch] parents of the block it syncs to. +// StateSyncParentsToFetch is the number of the block parents the state syncs to. // The last 256 block hashes are necessary to support the BLOCKHASH opcode. const StateSyncParentsToFetch = 256 @@ -40,14 +40,14 @@ var ( ) type BlockAcceptor interface { - PutLastAcceptedID([]byte) error + PutLastAcceptedID(ids.ID) error } type EthBlockWrapper interface { GetEthBlock() *types.Block } -// StateSyncClientConfig defines the options and dependencies needed to construct a StateSyncerClient +// StateSyncClientConfig defines the options and dependencies needed to construct a StateSyncClient type StateSyncClientConfig struct { Enabled bool SkipResume bool @@ -407,7 +407,11 @@ func (client *stateSyncerClient) updateVMMarkers() error { return err } client.AtomicBackend.SetLastAccepted(client.syncSummary.BlockHash) - if err := client.Acceptor.PutLastAcceptedID(client.syncSummary.BlockHash.Bytes()); err != nil { + id, err := ids.ToID(client.syncSummary.BlockHash.Bytes()) + if err != nil { + return err + } + if err := client.Acceptor.PutLastAcceptedID(id); err != nil { return err } if err := client.metadataDB.Delete(stateSyncSummaryKey); err != nil { diff --git a/plugin/evm/syncervm_test.go b/plugin/evm/syncervm_test.go index 6d07b6f721..10f55031ba 100644 --- a/plugin/evm/syncervm_test.go +++ b/plugin/evm/syncervm_test.go @@ -285,6 +285,10 @@ func createSyncServerAndClientVMs(t *testing.T, test syncTest, numBlocks int) *s testShortIDAddrs[0]: importAmount, } ) + + // override serverAtomicTrie's commitInterval so the call to [serverAtomicTrie.Index] + // creates a commit at the height [syncableInterval]. This is necessary to support + // fetching a state summary. config := fmt.Sprintf(`{"commit-interval": %d}`, test.syncableInterval) _, serverVM, _, serverAtomicMemory, serverAppSender := GenesisVMWithUTXOs( t, true, "", config, "", alloc, @@ -330,9 +334,6 @@ func createSyncServerAndClientVMs(t *testing.T, test syncTest, numBlocks int) *s } }, nil) - // override serverAtomicTrie's commitInterval so the call to [serverAtomicTrie.Index] - // creates a commit at the height [syncableInterval]. This is necessary to support - // fetching a state summary. serverAtomicTrie := serverVM.atomicTrie require.True(serverAtomicTrie.AcceptTrie(test.syncableInterval, serverAtomicTrie.LastAcceptedRoot())) require.NoError(serverVM.db.Commit()) @@ -364,6 +365,7 @@ func createSyncServerAndClientVMs(t *testing.T, test syncTest, numBlocks int) *s serverVM.StateSyncServer.(*stateSyncServer).syncableInterval = test.syncableInterval // initialise [syncerVM] with blank genesis state + // we also override [syncerVM]'s commit interval so the atomic trie works correctly. stateSyncEnabledJSON := fmt.Sprintf(`{"state-sync-enabled":true, "state-sync-min-blocks": %d, "tx-lookup-limit": %d, "commit-interval": %d}`, test.stateSyncMinBlocks, 4, test.syncableInterval) syncerEngineChan, syncerVM, syncerDB, syncerAtomicMemory, syncerAppSender := GenesisVMWithUTXOs( t, false, "", stateSyncEnabledJSON, "", alloc, diff --git a/plugin/evm/vm.go b/plugin/evm/vm.go index aebc94814c..aaae53d721 100644 --- a/plugin/evm/vm.go +++ b/plugin/evm/vm.go @@ -1910,6 +1910,6 @@ func (vm *VM) newExportTx( return tx, nil } -func (vm *VM) PutLastAcceptedID(ID []byte) error { - return vm.acceptedBlockDB.Put(lastAcceptedKey, ID) +func (vm *VM) PutLastAcceptedID(ID ids.ID) error { + return vm.acceptedBlockDB.Put(lastAcceptedKey, ID[:]) } diff --git a/plugin/evm/vm_test.go b/plugin/evm/vm_test.go index 0a4f9ebc20..bf3171d2ab 100644 --- a/plugin/evm/vm_test.go +++ b/plugin/evm/vm_test.go @@ -1479,11 +1479,8 @@ type wrappedBackend struct { } func (w *wrappedBackend) IsBonus(blockHeight uint64, blockHash common.Hash) bool { - // Check if the block is a bonus block - if hash, ok := w.registeredBonusBlocks[blockHeight]; ok { - return blockHash.Cmp(hash) == 0 - } - return false + hash, ok := w.registeredBonusBlocks[blockHeight] + return ok && blockHash.Cmp(hash) == 0 } func TestBonusBlocksTxs(t *testing.T) { diff --git a/utils/snow.go b/utils/snow.go index e24c884b16..6338bc8dcf 100644 --- a/utils/snow.go +++ b/utils/snow.go @@ -42,13 +42,16 @@ func TestSnowContext() *snow.Context { _ = aliaser.Alias(testXChainID, "X") _ = aliaser.Alias(testXChainID, testXChainID.String()) + m := atomic.NewMemory(memdb.New()) + sm := m.NewSharedMemory(testCChainID) + ctx := &snow.Context{ NetworkID: networkID, SubnetID: ids.Empty, ChainID: chainID, AVAXAssetID: TestAvaxAssetID, NodeID: ids.GenerateTestNodeID(), - SharedMemory: TestSharedMemory(), + SharedMemory: sm, XChainID: testXChainID, CChainID: testCChainID, PublicKey: pk, @@ -87,8 +90,3 @@ func NewTestValidatorState() *validatorstest.State { }, } } - -func TestSharedMemory() atomic.SharedMemory { - m := atomic.NewMemory(memdb.New()) - return m.NewSharedMemory(testCChainID) -}