diff --git a/go.mod b/go.mod index 37f53deb2b..15b17b5693 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.20 require ( github.com/VictoriaMetrics/fastcache v1.10.0 - github.com/ava-labs/avalanchego v1.10.12-0.20231005190735-e911af925f6c + github.com/ava-labs/avalanchego v1.10.12-rc.5 github.com/cespare/cp v0.1.0 github.com/cockroachdb/pebble v0.0.0-20230209160836-829675f94811 github.com/davecgh/go-spew v1.1.1 diff --git a/go.sum b/go.sum index bb94df62ab..5144c18571 100644 --- a/go.sum +++ b/go.sum @@ -55,8 +55,8 @@ github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156 h1:eMwmnE/GDgah github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= -github.com/ava-labs/avalanchego v1.10.12-0.20231005190735-e911af925f6c h1:8V65SWrP+IsMLwECc+2vN+3SIKzLFFz/y3PZ7erG5so= -github.com/ava-labs/avalanchego v1.10.12-0.20231005190735-e911af925f6c/go.mod h1:SrcFAwibYWdUnS7ZRbENDBzQgHYCROuX6w/lhGmJcPo= +github.com/ava-labs/avalanchego v1.10.12-rc.5 h1:lOljTUF59rZ/SKlTSZ3XQwuy4uGVNikHIEMQmYHzws4= +github.com/ava-labs/avalanchego v1.10.12-rc.5/go.mod h1:SrcFAwibYWdUnS7ZRbENDBzQgHYCROuX6w/lhGmJcPo= github.com/aymerick/raymond v2.0.3-0.20180322193309-b565731e1464+incompatible/go.mod h1:osfaiScAUVup+UC9Nfq76eWqDhXlp+4UYaA8uhTBO6g= github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= diff --git a/params/config.go b/params/config.go index 86aef28504..54e0f564d1 100644 --- a/params/config.go +++ b/params/config.go @@ -531,7 +531,9 @@ type ChainConfig struct { BanffBlockTimestamp *uint64 `json:"banffBlockTimestamp,omitempty"` // Cortina increases the block gas limit to 15M. (nil = no fork, 0 = already activated) CortinaBlockTimestamp *uint64 `json:"cortinaBlockTimestamp,omitempty"` - // DUpgrade activates the Shanghai upgrade from Ethereum. (nil = no fork, 0 = already activated) + // DUpgrade activates the Shanghai Execution Spec Upgrade from Ethereum (https://github.com/ethereum/execution-specs/blob/master/network-upgrades/mainnet-upgrades/shanghai.md#included-eips) + // and Avalanche Warp Messaging. (nil = no fork, 0 = already activated) + // Note: EIP-4895 is excluded since withdrawals are not relevant to the Avalanche C-Chain or Subnets running the EVM. DUpgradeBlockTimestamp *uint64 `json:"dUpgradeBlockTimestamp,omitempty"` // Cancun activates the Cancun upgrade from Ethereum. (nil = no fork, 0 = already activated) CancunTime *uint64 `json:"cancunTime,omitempty"` diff --git a/plugin/evm/admin.go b/plugin/evm/admin.go index 0028398ed5..fd8d7f8d6e 100644 --- a/plugin/evm/admin.go +++ b/plugin/evm/admin.go @@ -29,6 +29,9 @@ func NewAdminService(vm *VM, performanceDir string) *Admin { func (p *Admin) StartCPUProfiler(_ *http.Request, _ *struct{}, _ *api.EmptyReply) error { log.Info("Admin: StartCPUProfiler called") + p.vm.ctx.Lock.Lock() + defer p.vm.ctx.Lock.Unlock() + return p.profiler.StartCPUProfiler() } @@ -36,6 +39,9 @@ func (p *Admin) StartCPUProfiler(_ *http.Request, _ *struct{}, _ *api.EmptyReply func (p *Admin) StopCPUProfiler(r *http.Request, _ *struct{}, _ *api.EmptyReply) error { log.Info("Admin: StopCPUProfiler called") + p.vm.ctx.Lock.Lock() + defer p.vm.ctx.Lock.Unlock() + return p.profiler.StopCPUProfiler() } @@ -43,6 +49,9 @@ func (p *Admin) StopCPUProfiler(r *http.Request, _ *struct{}, _ *api.EmptyReply) func (p *Admin) MemoryProfile(_ *http.Request, _ *struct{}, _ *api.EmptyReply) error { log.Info("Admin: MemoryProfile called") + p.vm.ctx.Lock.Lock() + defer p.vm.ctx.Lock.Unlock() + return p.profiler.MemoryProfile() } @@ -50,6 +59,9 @@ func (p *Admin) MemoryProfile(_ *http.Request, _ *struct{}, _ *api.EmptyReply) e func (p *Admin) LockProfile(_ *http.Request, _ *struct{}, _ *api.EmptyReply) error { log.Info("Admin: LockProfile called") + p.vm.ctx.Lock.Lock() + defer p.vm.ctx.Lock.Unlock() + return p.profiler.LockProfile() } @@ -59,6 +71,10 @@ type SetLogLevelArgs struct { func (p *Admin) SetLogLevel(_ *http.Request, args *SetLogLevelArgs, reply *api.EmptyReply) error { log.Info("EVM: SetLogLevel called", "logLevel", args.Level) + + p.vm.ctx.Lock.Lock() + defer p.vm.ctx.Lock.Unlock() + if err := p.vm.logger.SetLogLevel(args.Level); err != nil { return fmt.Errorf("failed to parse log level: %w ", err) } diff --git a/plugin/evm/block.go b/plugin/evm/block.go index c47fa02899..aa7ca8752d 100644 --- a/plugin/evm/block.go +++ b/plugin/evm/block.go @@ -175,7 +175,7 @@ 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 { b.vm.mempool.RemoveTx(tx) - if err := b.vm.issueTx(tx, false /* set local to false when re-issuing */); err != nil { + if err := b.vm.mempool.AddTx(tx); err != nil { log.Debug("Failed to re-issue transaction in rejected block", "txID", tx.ID(), "err", err) } } diff --git a/plugin/evm/export_tx_test.go b/plugin/evm/export_tx_test.go index 319831c7d7..918cb711ef 100644 --- a/plugin/evm/export_tx_test.go +++ b/plugin/evm/export_tx_test.go @@ -59,7 +59,7 @@ func createExportTxOptions(t *testing.T, vm *VM, issuer chan engCommon.Message, t.Fatal(err) } - if err := vm.issueTx(importTx, true /*=local*/); err != nil { + if err := vm.mempool.AddLocalTx(importTx); err != nil { t.Fatal(err) } @@ -371,7 +371,7 @@ func TestExportTxEVMStateTransfer(t *testing.T) { t.Fatal(err) } - if err := vm.issueTx(tx, true /*=local*/); err != nil { + if err := vm.mempool.AddLocalTx(tx); err != nil { t.Fatal(err) } @@ -1726,7 +1726,7 @@ func TestNewExportTx(t *testing.T) { t.Fatal(err) } - if err := vm.issueTx(tx, true /*=local*/); err != nil { + if err := vm.mempool.AddLocalTx(tx); err != nil { t.Fatal(err) } @@ -1916,7 +1916,7 @@ func TestNewExportTxMulticoin(t *testing.T) { t.Fatal(err) } - if err := vm.issueTx(tx, false); err != nil { + if err := vm.mempool.AddTx(tx); err != nil { t.Fatal(err) } diff --git a/plugin/evm/gossip_mempool_test.go b/plugin/evm/gossip_mempool_test.go index b206219295..55435d37dd 100644 --- a/plugin/evm/gossip_mempool_test.go +++ b/plugin/evm/gossip_mempool_test.go @@ -92,7 +92,7 @@ func TestAtomicMempoolIterate(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { require := require.New(t) - m, err := NewMempool(ids.Empty, 10) + m, err := NewMempool(ids.Empty, 10, nil) require.NoError(err) for _, add := range tt.add { diff --git a/plugin/evm/gossiper.go b/plugin/evm/gossiper.go index 0f7d1a287d..712ee19415 100644 --- a/plugin/evm/gossiper.go +++ b/plugin/evm/gossiper.go @@ -465,7 +465,7 @@ func (h *GossipHandler) HandleAtomicTx(nodeID ids.NodeID, msg message.AtomicTxGo } h.stats.IncAtomicGossipReceivedNew() - if err := h.vm.issueTx(&tx, false /*=local*/); err != nil { + if err := h.vm.mempool.AddTx(&tx); err != nil { log.Trace( "AppGossip provided invalid transaction", "peerID", nodeID, diff --git a/plugin/evm/gossiper_atomic_gossiping_test.go b/plugin/evm/gossiper_atomic_gossiping_test.go index 6ded11967b..7cfbb55e17 100644 --- a/plugin/evm/gossiper_atomic_gossiping_test.go +++ b/plugin/evm/gossiper_atomic_gossiping_test.go @@ -60,7 +60,7 @@ func TestMempoolAtmTxsIssueTxAndGossiping(t *testing.T) { assert.NoError(vm.SetState(context.Background(), snow.NormalOp)) // Optimistically gossip raw tx - assert.NoError(vm.issueTx(tx, true /*=local*/)) + assert.NoError(vm.mempool.AddLocalTx(tx)) time.Sleep(500 * time.Millisecond) gossipedLock.Lock() assert.Equal(1, gossiped) @@ -74,7 +74,7 @@ func TestMempoolAtmTxsIssueTxAndGossiping(t *testing.T) { gossipedLock.Unlock() // Attempt to gossip conflicting tx - assert.ErrorIs(vm.issueTx(conflictingTx, true /*=local*/), errConflictingAtomicTx) + assert.ErrorIs(vm.mempool.AddLocalTx(conflictingTx), errConflictingAtomicTx) gossipedLock.Lock() assert.Equal(1, gossiped) gossipedLock.Unlock() diff --git a/plugin/evm/mempool.go b/plugin/evm/mempool.go index 8c1c56474e..d96c8feb81 100644 --- a/plugin/evm/mempool.go +++ b/plugin/evm/mempool.go @@ -75,10 +75,12 @@ type Mempool struct { bloom *gossip.BloomFilter metrics *mempoolMetrics + + verify func(tx *Tx) error } // NewMempool returns a Mempool with [maxSize] -func NewMempool(AVAXAssetID ids.ID, maxSize int) (*Mempool, error) { +func NewMempool(AVAXAssetID ids.ID, maxSize int, verify func(tx *Tx) error) (*Mempool, error) { bloom, err := gossip.NewBloomFilter(txGossipBloomMaxItems, txGossipBloomFalsePositiveRate) if err != nil { return nil, fmt.Errorf("failed to initialize bloom filter: %w", err) @@ -95,6 +97,7 @@ func NewMempool(AVAXAssetID ids.ID, maxSize int) (*Mempool, error) { utxoSpenders: make(map[ids.ID]*Tx), bloom: bloom, metrics: newMempoolMetrics(), + verify: verify, }, nil } @@ -145,6 +148,24 @@ func (m *Mempool) AddTx(tx *Tx) error { m.lock.Lock() defer m.lock.Unlock() + err := m.addTx(tx, false) + if err != nil { + // unlike local txs, invalid remote txs are recorded as discarded + // so that they won't be requested again + txID := tx.ID() + m.discardedTxs.Put(tx.ID(), tx) + log.Debug("failed to issue remote tx to mempool", + "txID", txID, + "err", err, + ) + } + return err +} + +func (m *Mempool) AddLocalTx(tx *Tx) error { + m.lock.Lock() + defer m.lock.Unlock() + return m.addTx(tx, false) } @@ -203,6 +224,11 @@ func (m *Mempool) addTx(tx *Tx, force bool) error { if _, exists := m.txHeap.Get(txID); exists { return nil } + if !force && m.verify != nil { + if err := m.verify(tx); err != nil { + return err + } + } utxoSet := tx.InputUTXOs() gasPrice, _ := m.atomicTxGasPrice(tx) diff --git a/plugin/evm/mempool_atomic_gossiping_test.go b/plugin/evm/mempool_atomic_gossiping_test.go index a0f82a8c01..741c177b90 100644 --- a/plugin/evm/mempool_atomic_gossiping_test.go +++ b/plugin/evm/mempool_atomic_gossiping_test.go @@ -5,16 +5,12 @@ package evm import ( "context" + "math/big" "testing" - "github.com/ava-labs/coreth/params" - "github.com/ava-labs/avalanchego/ids" - "github.com/ava-labs/avalanchego/utils" "github.com/ava-labs/avalanchego/utils/crypto/secp256k1" - "github.com/ava-labs/avalanchego/vms/components/avax" "github.com/ava-labs/avalanchego/vms/components/chain" - "github.com/ava-labs/avalanchego/vms/secp256k1fx" "github.com/stretchr/testify/assert" ) @@ -49,13 +45,13 @@ func TestMempoolAddLocallyCreateAtomicTx(t *testing.T) { conflictingTxID := conflictingTx.ID() // add a tx to the mempool - err := vm.issueTx(tx, true /*=local*/) + err := vm.mempool.AddLocalTx(tx) assert.NoError(err) has := mempool.has(txID) assert.True(has, "valid tx not recorded into mempool") // try to add a conflicting tx - err = vm.issueTx(conflictingTx, true /*=local*/) + err = vm.mempool.AddLocalTx(conflictingTx) assert.ErrorIs(err, errConflictingAtomicTx) has = mempool.has(conflictingTxID) assert.False(has, "conflicting tx in mempool") @@ -118,73 +114,16 @@ func TestMempoolMaxMempoolSizeHandling(t *testing.T) { assert.True(mempool.has(tx.ID())) } -func createImportTx(t *testing.T, vm *VM, txID ids.ID, feeAmount uint64) *Tx { - var importAmount uint64 = 10000000 - importTx := &UnsignedImportTx{ - NetworkID: testNetworkID, - BlockchainID: testCChainID, - SourceChain: testXChainID, - ImportedInputs: []*avax.TransferableInput{ - { - UTXOID: avax.UTXOID{ - TxID: txID, - OutputIndex: uint32(0), - }, - Asset: avax.Asset{ID: testAvaxAssetID}, - In: &secp256k1fx.TransferInput{ - Amt: importAmount, - Input: secp256k1fx.Input{ - SigIndices: []uint32{0}, - }, - }, - }, - { - UTXOID: avax.UTXOID{ - TxID: txID, - OutputIndex: uint32(1), - }, - Asset: avax.Asset{ID: testAvaxAssetID}, - In: &secp256k1fx.TransferInput{ - Amt: importAmount, - Input: secp256k1fx.Input{ - SigIndices: []uint32{0}, - }, - }, - }, - }, - Outs: []EVMOutput{ - { - Address: testEthAddrs[0], - Amount: importAmount - feeAmount, - AssetID: testAvaxAssetID, - }, - { - Address: testEthAddrs[1], - Amount: importAmount, - AssetID: testAvaxAssetID, - }, - }, - } - - // Sort the inputs and outputs to ensure the transaction is canonical - utils.Sort(importTx.ImportedInputs) - utils.Sort(importTx.Outs) - - tx := &Tx{UnsignedAtomicTx: importTx} - // Sign with the correct key - if err := tx.Sign(vm.codec, [][]*secp256k1.PrivateKey{{testKeys[0]}}); err != nil { - t.Fatal(err) - } - - return tx -} - // mempool will drop transaction with the lowest fee func TestMempoolPriorityDrop(t *testing.T) { assert := assert.New(t) // we use AP3 genesis here to not trip any block fees - _, vm, _, _, _ := GenesisVM(t, true, genesisJSONApricotPhase3, "", "") + importAmount := uint64(50000000) + _, vm, _, _, _ := GenesisVMWithUTXOs(t, true, genesisJSONApricotPhase3, "", "", map[ids.ShortID]uint64{ + testShortIDAddrs[0]: importAmount, + testShortIDAddrs[1]: importAmount, + }) defer func() { err := vm.Shutdown(context.Background()) assert.NoError(err) @@ -192,14 +131,25 @@ func TestMempoolPriorityDrop(t *testing.T) { mempool := vm.mempool mempool.maxSize = 1 - tx1 := createImportTx(t, vm, ids.ID{1}, params.AvalancheAtomicTxFee) + 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.True(mempool.has(tx1.ID())) - tx2 := createImportTx(t, vm, ids.ID{2}, params.AvalancheAtomicTxFee) + + 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.True(mempool.has(tx1.ID())) assert.False(mempool.has(tx2.ID())) - tx3 := createImportTx(t, vm, ids.ID{3}, 2*params.AvalancheAtomicTxFee) + + tx3, err := vm.newImportTx(vm.ctx.XChainID, testEthAddrs[1], new(big.Int).Mul(initialBaseFee, big.NewInt(2)), []*secp256k1.PrivateKey{testKeys[1]}) + if err != nil { + t.Fatal(err) + } assert.NoError(mempool.AddTx(tx3)) assert.False(mempool.has(tx1.ID())) assert.False(mempool.has(tx2.ID())) diff --git a/plugin/evm/mempool_test.go b/plugin/evm/mempool_test.go index 6d719a0ee2..242f0e33c4 100644 --- a/plugin/evm/mempool_test.go +++ b/plugin/evm/mempool_test.go @@ -12,7 +12,7 @@ import ( func TestMempoolAddTx(t *testing.T) { require := require.New(t) - m, err := NewMempool(ids.Empty, 5_000) + m, err := NewMempool(ids.Empty, 5_000, nil) require.NoError(err) txs := make([]*GossipAtomicTx, 0) diff --git a/plugin/evm/service.go b/plugin/evm/service.go index f2dd1a552f..3a328fbd89 100644 --- a/plugin/evm/service.go +++ b/plugin/evm/service.go @@ -86,7 +86,7 @@ type VersionReply struct { } // ClientVersion returns the version of the VM running -func (service *AvaxAPI) Version(r *http.Request, args *struct{}, reply *VersionReply) error { +func (service *AvaxAPI) Version(r *http.Request, _ *struct{}, reply *VersionReply) error { reply.Version = Version return nil } @@ -113,6 +113,9 @@ func (service *AvaxAPI) ExportKey(r *http.Request, args *ExportKeyArgs, reply *E return fmt.Errorf("couldn't parse %s to address: %s", args.Address, err) } + service.vm.ctx.Lock.Lock() + defer service.vm.ctx.Lock.Unlock() + db, err := service.vm.ctx.Keystore.GetDatabase(args.Username, args.Password) if err != nil { return fmt.Errorf("problem retrieving user '%s': %w", args.Username, err) @@ -147,6 +150,9 @@ func (service *AvaxAPI) ImportKey(r *http.Request, args *ImportKeyArgs, reply *a reply.Address = GetEthAddress(args.PrivateKey).Hex() + service.vm.ctx.Lock.Lock() + defer service.vm.ctx.Lock.Unlock() + db, err := service.vm.ctx.Keystore.GetDatabase(args.Username, args.Password) if err != nil { return fmt.Errorf("problem retrieving data: %w", err) @@ -192,6 +198,9 @@ func (service *AvaxAPI) Import(_ *http.Request, args *ImportArgs, response *api. return fmt.Errorf("problem parsing chainID %q: %w", args.SourceChain, err) } + service.vm.ctx.Lock.Lock() + defer service.vm.ctx.Lock.Unlock() + // Get the user's info db, err := service.vm.ctx.Keystore.GetDatabase(args.Username, args.Password) if err != nil { @@ -225,7 +234,7 @@ func (service *AvaxAPI) Import(_ *http.Request, args *ImportArgs, response *api. } response.TxID = tx.ID() - return service.vm.issueTx(tx, true /*=local*/) + return service.vm.mempool.AddLocalTx(tx) } // ExportAVAXArgs are the arguments to ExportAVAX @@ -290,6 +299,9 @@ func (service *AvaxAPI) Export(_ *http.Request, args *ExportArgs, response *api. } } + service.vm.ctx.Lock.Lock() + defer service.vm.ctx.Lock.Unlock() + // Get this user's data db, err := service.vm.ctx.Keystore.GetDatabase(args.Username, args.Password) if err != nil { @@ -331,7 +343,7 @@ func (service *AvaxAPI) Export(_ *http.Request, args *ExportArgs, response *api. } response.TxID = tx.ID() - return service.vm.issueTx(tx, true /*=local*/) + return service.vm.mempool.AddLocalTx(tx) } // GetUTXOs gets all utxos for passed in addresses @@ -377,6 +389,9 @@ func (service *AvaxAPI) GetUTXOs(r *http.Request, args *api.GetUTXOsArgs, reply } } + service.vm.ctx.Lock.Lock() + defer service.vm.ctx.Lock.Unlock() + utxos, endAddr, endUTXOID, err := service.vm.GetAtomicUTXOs( sourceChain, addrSet, @@ -413,7 +428,6 @@ func (service *AvaxAPI) GetUTXOs(r *http.Request, args *api.GetUTXOsArgs, reply return nil } -// IssueTx ... func (service *AvaxAPI) IssueTx(r *http.Request, args *api.FormattedTx, response *api.JSONTxID) error { log.Info("EVM: IssueTx called") @@ -431,7 +445,11 @@ func (service *AvaxAPI) IssueTx(r *http.Request, args *api.FormattedTx, response } response.TxID = tx.ID() - return service.vm.issueTx(tx, true /*=local*/) + + service.vm.ctx.Lock.Lock() + defer service.vm.ctx.Lock.Unlock() + + return service.vm.mempool.AddLocalTx(tx) } // GetAtomicTxStatusReply defines the GetAtomicTxStatus replies returned from the API @@ -448,6 +466,9 @@ func (service *AvaxAPI) GetAtomicTxStatus(r *http.Request, args *api.JSONTxID, r return errNilTxID } + service.vm.ctx.Lock.Lock() + defer service.vm.ctx.Lock.Unlock() + _, status, height, _ := service.vm.getAtomicTx(args.TxID) reply.Status = status @@ -471,6 +492,9 @@ func (service *AvaxAPI) GetAtomicTx(r *http.Request, args *api.GetTxArgs, reply return errNilTxID } + service.vm.ctx.Lock.Lock() + defer service.vm.ctx.Lock.Unlock() + tx, status, height, err := service.vm.getAtomicTx(args.TxID) if err != nil { return err diff --git a/plugin/evm/syncervm_test.go b/plugin/evm/syncervm_test.go index dab0fd505a..34fd55023f 100644 --- a/plugin/evm/syncervm_test.go +++ b/plugin/evm/syncervm_test.go @@ -287,7 +287,7 @@ func createSyncServerAndClientVMs(t *testing.T, test syncTest) *syncVMSetup { // spend the UTXOs from shared memory importTx, err = serverVM.newImportTx(serverVM.ctx.XChainID, testEthAddrs[0], initialBaseFee, []*secp256k1.PrivateKey{testKeys[0]}) require.NoError(err) - require.NoError(serverVM.issueTx(importTx, true /*=local*/)) + require.NoError(serverVM.mempool.AddLocalTx(importTx)) case 1: // export some of the imported UTXOs to test exportTx is properly synced exportTx, err = serverVM.newExportTx( @@ -299,7 +299,7 @@ func createSyncServerAndClientVMs(t *testing.T, test syncTest) *syncVMSetup { []*secp256k1.PrivateKey{testKeys[0]}, ) require.NoError(err) - require.NoError(serverVM.issueTx(exportTx, true /*=local*/)) + require.NoError(serverVM.mempool.AddLocalTx(exportTx)) default: // Generate simple transfer transactions. pk := testKeys[0].ToECDSA() tx := types.NewTransaction(gen.TxNonce(testEthAddrs[0]), testEthAddrs[1], common.Big1, params.TxGas, initialBaseFee, nil) diff --git a/plugin/evm/tx_gossip_test.go b/plugin/evm/tx_gossip_test.go index ac3872a03a..2930839fd2 100644 --- a/plugin/evm/tx_gossip_test.go +++ b/plugin/evm/tx_gossip_test.go @@ -49,7 +49,7 @@ func TestEthTxGossip(t *testing.T) { importTx, err := vm.newImportTx(vm.ctx.XChainID, testEthAddrs[0], initialBaseFee, []*secp256k1.PrivateKey{testKeys[0]}) require.NoError(err) - require.NoError(vm.issueTx(importTx, true)) + require.NoError(vm.mempool.AddLocalTx(importTx)) <-issuer blk, err := vm.BuildBlock(context.Background()) @@ -228,7 +228,7 @@ func TestAtomicTxGossip(t *testing.T) { importTx, err := vm.newImportTx(vm.ctx.XChainID, testEthAddrs[0], initialBaseFee, []*secp256k1.PrivateKey{testKeys[0]}) require.NoError(err) - require.NoError(vm.issueTx(importTx, true /*=local*/)) + require.NoError(vm.mempool.AddLocalTx(importTx)) <-issuer // wait so we aren't throttled by the vm diff --git a/plugin/evm/tx_test.go b/plugin/evm/tx_test.go index 769690d272..e4d1dfd67b 100644 --- a/plugin/evm/tx_test.go +++ b/plugin/evm/tx_test.go @@ -155,7 +155,7 @@ func executeTxTest(t *testing.T, test atomicTxTest) { } } - if err := vm.issueTx(tx, true /*=local*/); err != nil { + if err := vm.mempool.AddLocalTx(tx); err != nil { t.Fatal(err) } <-issuer diff --git a/plugin/evm/vm.go b/plugin/evm/vm.go index 3885d1d87b..eff649465e 100644 --- a/plugin/evm/vm.go +++ b/plugin/evm/vm.go @@ -10,6 +10,7 @@ import ( "fmt" "io" "math/big" + "net/http" "os" "path/filepath" "strings" @@ -541,7 +542,7 @@ func (vm *VM) Initialize( vm.codec = Codec // TODO: read size from settings - vm.mempool, err = NewMempool(chainCtx.AVAXAssetID, defaultMempoolSize) + vm.mempool, err = NewMempool(chainCtx.AVAXAssetID, defaultMempoolSize, vm.verifyTxAtTip) if err != nil { return fmt.Errorf("failed to initialize mempool: %w", err) } @@ -1299,26 +1300,15 @@ func (vm *VM) BackfillBlocks(ctx context.Context, blocks [][]byte) error { // - The handler's functionality is defined by [service] // [service] should be a gorilla RPC service (see https://www.gorillatoolkit.org/pkg/rpc/v2) // - The name of the service is [name] -// - The LockOption is the first element of [lockOption] -// By default the LockOption is WriteLock -// [lockOption] should have either 0 or 1 elements. Elements beside the first are ignored. -func newHandler(name string, service interface{}, lockOption ...commonEng.LockOption) (*commonEng.HTTPHandler, error) { +func newHandler(name string, service interface{}) (http.Handler, error) { server := avalancheRPC.NewServer() server.RegisterCodec(avalancheJSON.NewCodec(), "application/json") server.RegisterCodec(avalancheJSON.NewCodec(), "application/json;charset=UTF-8") - if err := server.RegisterService(service, name); err != nil { - return nil, err - } - - var lock commonEng.LockOption = commonEng.WriteLock - if len(lockOption) != 0 { - lock = lockOption[0] - } - return &commonEng.HTTPHandler{LockOptions: lock, Handler: server}, nil + return server, server.RegisterService(service, name) } // CreateHandlers makes new http handlers that can handle API calls -func (vm *VM) CreateHandlers(context.Context) (map[string]*commonEng.HTTPHandler, error) { +func (vm *VM) CreateHandlers(context.Context) (map[string]http.Handler, error) { handler := rpc.NewServer(vm.config.APIMaxDuration.Duration) enabledAPIs := vm.config.EthAPIs() if err := attachEthService(handler, vm.eth.APIs(), enabledAPIs); err != nil { @@ -1329,7 +1319,7 @@ func (vm *VM) CreateHandlers(context.Context) (map[string]*commonEng.HTTPHandler if err != nil { return nil, fmt.Errorf("failed to get primary alias for chain due to %w", err) } - apis := make(map[string]*commonEng.HTTPHandler) + apis := make(map[string]http.Handler) avaxAPI, err := newHandler("avax", &AvaxAPI{vm}) if err != nil { return nil, fmt.Errorf("failed to register service for AVAX API due to %w", err) @@ -1354,32 +1344,26 @@ func (vm *VM) CreateHandlers(context.Context) (map[string]*commonEng.HTTPHandler } log.Info(fmt.Sprintf("Enabled APIs: %s", strings.Join(enabledAPIs, ", "))) - apis[ethRPCEndpoint] = &commonEng.HTTPHandler{ - LockOptions: commonEng.NoLock, - Handler: handler, - } - apis[ethWSEndpoint] = &commonEng.HTTPHandler{ - LockOptions: commonEng.NoLock, - Handler: handler.WebsocketHandlerWithDuration( - []string{"*"}, - vm.config.APIMaxDuration.Duration, - vm.config.WSCPURefillRate.Duration, - vm.config.WSCPUMaxStored.Duration, - ), - } + apis[ethRPCEndpoint] = handler + apis[ethWSEndpoint] = handler.WebsocketHandlerWithDuration( + []string{"*"}, + vm.config.APIMaxDuration.Duration, + vm.config.WSCPURefillRate.Duration, + vm.config.WSCPUMaxStored.Duration, + ) return apis, nil } // CreateStaticHandlers makes new http handlers that can handle API calls -func (vm *VM) CreateStaticHandlers(context.Context) (map[string]*commonEng.HTTPHandler, error) { +func (vm *VM) CreateStaticHandlers(context.Context) (map[string]http.Handler, error) { handler := rpc.NewServer(0) if err := handler.RegisterName("static", &StaticService{}); err != nil { return nil, err } - return map[string]*commonEng.HTTPHandler{ - "/rpc": {LockOptions: commonEng.NoLock, Handler: handler}, + return map[string]http.Handler{ + "/rpc": handler, }, nil } @@ -1475,44 +1459,19 @@ func (vm *VM) ParseAddress(addrStr string) (ids.ID, ids.ShortID, error) { return chainID, addr, nil } -// issueTx verifies [tx] as valid to be issued on top of the currently preferred block -// and then issues [tx] into the mempool if valid. -func (vm *VM) issueTx(tx *Tx, local bool) error { - if err := vm.verifyTxAtTip(tx); err != nil { - if !local { - // unlike local txs, invalid remote txs are recorded as discarded - // so that they won't be requested again - txID := tx.ID() - vm.mempool.discardedTxs.Put(txID, tx) - log.Debug("failed to verify remote tx being issued to the mempool", - "txID", txID, - "err", err, - ) - return nil - } - return err +// verifyTxAtTip verifies that [tx] is valid to be issued on top of the currently preferred block +func (vm *VM) verifyTxAtTip(tx *Tx) error { + if txByteLen := len(tx.SignedBytes()); txByteLen > targetAtomicTxsSize { + return fmt.Errorf("tx size (%d) exceeds total atomic txs size target (%d)", txByteLen, targetAtomicTxsSize) } - // add to mempool and possibly re-gossip - if err := vm.mempool.AddTx(tx); err != nil { - if !local { - // unlike local txs, invalid remote txs are recorded as discarded - // so that they won't be requested again - txID := tx.ID() - vm.mempool.discardedTxs.Put(tx.ID(), tx) - log.Debug("failed to issue remote tx to mempool", - "txID", txID, - "err", err, - ) - return nil - } + gasUsed, err := tx.GasUsed(true) + if err != nil { return err } + if new(big.Int).SetUint64(gasUsed).Cmp(params.AtomicGasLimit) > 0 { + return fmt.Errorf("tx gas usage (%d) exceeds atomic gas limit (%d)", gasUsed, params.AtomicGasLimit.Uint64()) + } - return nil -} - -// verifyTxAtTip verifies that [tx] is valid to be issued on top of the currently preferred block -func (vm *VM) verifyTxAtTip(tx *Tx) error { // Note: we fetch the current block and then the state at that block instead of the current state directly // since we need the header of the current block below. preferredBlock := vm.blockChain.CurrentBlock() diff --git a/plugin/evm/vm_test.go b/plugin/evm/vm_test.go index bd3792df26..20cbd4ff68 100644 --- a/plugin/evm/vm_test.go +++ b/plugin/evm/vm_test.go @@ -377,7 +377,7 @@ func TestCrossChainMessagestoVM(t *testing.T) { importTx, err := vm.newImportTx(vm.ctx.XChainID, testEthAddrs[0], initialBaseFee, []*secp256k1.PrivateKey{testKeys[0]}) require.NoError(err) - err = vm.issueTx(importTx, true /*=local*/) + err = vm.mempool.AddLocalTx(importTx) require.NoError(err) <-issuer @@ -661,7 +661,7 @@ func TestImportMissingUTXOs(t *testing.T) { importTx, err := vm.newImportTx(vm.ctx.XChainID, testEthAddrs[0], initialBaseFee, []*secp256k1.PrivateKey{testKeys[0]}) require.NoError(t, err) - err = vm.issueTx(importTx, true /*=local*/) + err = vm.mempool.AddLocalTx(importTx) require.NoError(t, err) <-issuer blk, err := vm.BuildBlock(context.Background()) @@ -704,7 +704,7 @@ func TestIssueAtomicTxs(t *testing.T) { t.Fatal(err) } - if err := vm.issueTx(importTx, true /*=local*/); err != nil { + if err := vm.mempool.AddLocalTx(importTx); err != nil { t.Fatal(err) } @@ -763,7 +763,7 @@ func TestIssueAtomicTxs(t *testing.T) { t.Fatal(err) } - if err := vm.issueTx(exportTx, true /*=local*/); err != nil { + if err := vm.mempool.AddLocalTx(exportTx); err != nil { t.Fatal(err) } @@ -830,7 +830,7 @@ func TestBuildEthTxBlock(t *testing.T) { t.Fatal(err) } - if err := vm.issueTx(importTx, true /*=local*/); err != nil { + if err := vm.mempool.AddLocalTx(importTx); err != nil { t.Fatal(err) } @@ -1005,7 +1005,7 @@ func testConflictingImportTxs(t *testing.T, genesis string) { t.Fatal(err) } for i, tx := range importTxs[:2] { - if err := vm.issueTx(tx, true /*=local*/); err != nil { + if err := vm.mempool.AddLocalTx(tx); err != nil { t.Fatal(err) } @@ -1039,7 +1039,7 @@ func testConflictingImportTxs(t *testing.T, genesis string) { // the VM returns an error when it attempts to issue the conflict into the mempool // and when it attempts to build a block with the conflict force added to the mempool. for i, tx := range conflictTxs[:2] { - if err := vm.issueTx(tx, true /*=local*/); err == nil { + if err := vm.mempool.AddLocalTx(tx); err == nil { t.Fatal("Expected issueTx to fail due to conflicting transaction") } // Force issue transaction directly to the mempool @@ -1061,7 +1061,7 @@ func testConflictingImportTxs(t *testing.T, genesis string) { // Generate one more valid block so that we can copy the header to create an invalid block // with modified extra data. This new block will be invalid for more than one reason (invalid merkle root) // so we check to make sure that the expected error is returned from block verification. - if err := vm.issueTx(importTxs[2], true); err != nil { + if err := vm.mempool.AddLocalTx(importTxs[2]); err != nil { t.Fatal(err) } <-issuer @@ -1169,10 +1169,10 @@ func TestReissueAtomicTxHigherGasPrice(t *testing.T) { t.Fatal(err) } - if err := vm.issueTx(tx1, true); err != nil { + if err := vm.mempool.AddLocalTx(tx1); err != nil { t.Fatal(err) } - if err := vm.issueTx(tx2, true); err != nil { + if err := vm.mempool.AddLocalTx(tx2); err != nil { t.Fatal(err) } @@ -1196,10 +1196,10 @@ func TestReissueAtomicTxHigherGasPrice(t *testing.T) { t.Fatal(err) } - if err := vm.issueTx(tx1, true); err != nil { + if err := vm.mempool.AddLocalTx(tx1); err != nil { t.Fatal(err) } - if err := vm.issueTx(tx2, true); err != nil { + if err := vm.mempool.AddLocalTx(tx2); err != nil { t.Fatal(err) } @@ -1229,15 +1229,15 @@ func TestReissueAtomicTxHigherGasPrice(t *testing.T) { if err != nil { t.Fatal(err) } - if err := vm.issueTx(importTx1, true /*=local*/); err != nil { + if err := vm.mempool.AddLocalTx(importTx1); err != nil { t.Fatal(err) } - if err := vm.issueTx(importTx2, true /*=local*/); err != nil { + if err := vm.mempool.AddLocalTx(importTx2); err != nil { t.Fatal(err) } - if err := vm.issueTx(reissuanceTx1, true /*=local*/); !errors.Is(err, errConflictingAtomicTx) { + if err := vm.mempool.AddLocalTx(reissuanceTx1); !errors.Is(err, errConflictingAtomicTx) { t.Fatalf("Expected to fail with err: %s, but found err: %s", errConflictingAtomicTx, err) } @@ -1249,7 +1249,7 @@ func TestReissueAtomicTxHigherGasPrice(t *testing.T) { if err != nil { t.Fatal(err) } - if err := vm.issueTx(reissuanceTx2, true /*=local*/); err != nil { + if err := vm.mempool.AddLocalTx(reissuanceTx2); err != nil { t.Fatal(err) } @@ -1329,7 +1329,7 @@ func TestSetPreferenceRace(t *testing.T) { t.Fatal(err) } - if err := vm1.issueTx(importTx, true /*=local*/); err != nil { + if err := vm1.mempool.AddLocalTx(importTx); err != nil { t.Fatal(err) } @@ -1578,7 +1578,7 @@ func TestConflictingTransitiveAncestryWithGap(t *testing.T) { t.Fatal(err) } - if err := vm.issueTx(importTx0A, true /*=local*/); err != nil { + if err := vm.mempool.AddLocalTx(importTx0A); err != nil { t.Fatalf("Failed to issue importTx0A: %s", err) } @@ -1636,7 +1636,7 @@ func TestConflictingTransitiveAncestryWithGap(t *testing.T) { t.Fatalf("Failed to issue importTx1 due to: %s", err) } - if err := vm.issueTx(importTx1, true /*=local*/); err != nil { + if err := vm.mempool.AddLocalTx(importTx1); err != nil { t.Fatal(err) } @@ -1655,7 +1655,7 @@ func TestConflictingTransitiveAncestryWithGap(t *testing.T) { t.Fatal(err) } - if err := vm.issueTx(importTx0B, true /*=local*/); err == nil { + if err := vm.mempool.AddLocalTx(importTx0B); err == nil { t.Fatalf("Should not have been able to issue import tx with conflict") } // Force issue transaction directly into the mempool @@ -1715,7 +1715,7 @@ func TestBonusBlocksTxs(t *testing.T) { t.Fatal(err) } - if err := vm.issueTx(importTx, true /*=local*/); err != nil { + if err := vm.mempool.AddLocalTx(importTx); err != nil { t.Fatal(err) } @@ -1808,7 +1808,7 @@ func TestReorgProtection(t *testing.T) { t.Fatal(err) } - if err := vm1.issueTx(importTx, true /*=local*/); err != nil { + if err := vm1.mempool.AddLocalTx(importTx); err != nil { t.Fatal(err) } @@ -1990,7 +1990,7 @@ func TestNonCanonicalAccept(t *testing.T) { t.Fatal(err) } - if err := vm1.issueTx(importTx, true /*=local*/); err != nil { + if err := vm1.mempool.AddLocalTx(importTx); err != nil { t.Fatal(err) } @@ -2165,7 +2165,7 @@ func TestStickyPreference(t *testing.T) { t.Fatal(err) } - if err := vm1.issueTx(importTx, true /*=local*/); err != nil { + if err := vm1.mempool.AddLocalTx(importTx); err != nil { t.Fatal(err) } @@ -2438,7 +2438,7 @@ func TestUncleBlock(t *testing.T) { t.Fatal(err) } - if err := vm1.issueTx(importTx, true /*=local*/); err != nil { + if err := vm1.mempool.AddLocalTx(importTx); err != nil { t.Fatal(err) } @@ -2621,7 +2621,7 @@ func TestEmptyBlock(t *testing.T) { t.Fatal(err) } - if err := vm.issueTx(importTx, true /*=local*/); err != nil { + if err := vm.mempool.AddLocalTx(importTx); err != nil { t.Fatal(err) } @@ -2702,7 +2702,7 @@ func TestAcceptReorg(t *testing.T) { t.Fatal(err) } - if err := vm1.issueTx(importTx, true /*=local*/); err != nil { + if err := vm1.mempool.AddLocalTx(importTx); err != nil { t.Fatal(err) } @@ -2897,7 +2897,7 @@ func TestFutureBlock(t *testing.T) { t.Fatal(err) } - if err := vm.issueTx(importTx, true /*=local*/); err != nil { + if err := vm.mempool.AddLocalTx(importTx); err != nil { t.Fatal(err) } @@ -2962,7 +2962,7 @@ func TestBuildApricotPhase1Block(t *testing.T) { t.Fatal(err) } - if err := vm.issueTx(importTx, true /*=local*/); err != nil { + if err := vm.mempool.AddLocalTx(importTx); err != nil { t.Fatal(err) } @@ -3078,7 +3078,7 @@ func TestLastAcceptedBlockNumberAllow(t *testing.T) { t.Fatal(err) } - if err := vm.issueTx(importTx, true /*=local*/); err != nil { + if err := vm.mempool.AddLocalTx(importTx); err != nil { t.Fatal(err) } @@ -3156,7 +3156,7 @@ func TestReissueAtomicTx(t *testing.T) { t.Fatal(err) } - if err := vm.issueTx(importTx, true /*=local*/); err != nil { + if err := vm.mempool.AddLocalTx(importTx); err != nil { t.Fatal(err) } @@ -3258,7 +3258,7 @@ func TestAtomicTxFailsEVMStateTransferBuildBlock(t *testing.T) { exportTxs := createExportTxOptions(t, vm, issuer, sharedMemory) exportTx1, exportTx2 := exportTxs[0], exportTxs[1] - if err := vm.issueTx(exportTx1, true /*=local*/); err != nil { + if err := vm.mempool.AddLocalTx(exportTx1); err != nil { t.Fatal(err) } <-issuer @@ -3274,7 +3274,7 @@ func TestAtomicTxFailsEVMStateTransferBuildBlock(t *testing.T) { t.Fatal(err) } - if err := vm.issueTx(exportTx2, true /*=local*/); err == nil { + if err := vm.mempool.AddLocalTx(exportTx2); err == nil { t.Fatal("Should have failed to issue due to an invalid export tx") } @@ -3337,11 +3337,11 @@ func TestBuildInvalidBlockHead(t *testing.T) { // Verify that the transaction fails verification when attempting to issue // it into the atomic mempool. - if err := vm.issueTx(tx, true /*=local*/); err == nil { + if err := vm.mempool.AddLocalTx(tx); err == nil { t.Fatal("Should have failed to issue invalid transaction") } // Force issue the transaction directly to the mempool - if err := vm.mempool.AddTx(tx); err != nil { + if err := vm.mempool.ForceAddTx(tx); err != nil { t.Fatal(err) } @@ -3485,7 +3485,7 @@ func TestBuildApricotPhase4Block(t *testing.T) { t.Fatal(err) } - if err := vm.issueTx(importTx, true /*=local*/); err != nil { + if err := vm.mempool.AddLocalTx(importTx); err != nil { t.Fatal(err) } @@ -3667,7 +3667,7 @@ func TestBuildApricotPhase5Block(t *testing.T) { t.Fatal(err) } - if err := vm.issueTx(importTx, true /*=local*/); err != nil { + if err := vm.mempool.AddLocalTx(importTx); err != nil { t.Fatal(err) } @@ -3807,7 +3807,7 @@ func TestConsecutiveAtomicTransactionsRevertSnapshot(t *testing.T) { importTxs := createImportTxOptions(t, vm, sharedMemory) // Issue the first import transaction, build, and accept the block. - if err := vm.issueTx(importTxs[0], true); err != nil { + if err := vm.mempool.AddLocalTx(importTxs[0]); err != nil { t.Fatal(err) } @@ -3873,7 +3873,7 @@ func TestAtomicTxBuildBlockDropsConflicts(t *testing.T) { if err != nil { t.Fatal(err) } - if err := vm.issueTx(importTx, true /*=local*/); err != nil { + if err := vm.mempool.AddLocalTx(importTx); err != nil { t.Fatal(err) } conflictSets[index].Add(importTx.ID()) @@ -3881,7 +3881,7 @@ func TestAtomicTxBuildBlockDropsConflicts(t *testing.T) { if err != nil { t.Fatal(err) } - if err := vm.issueTx(conflictTx, true /*=local*/); err == nil { + if err := vm.mempool.AddLocalTx(conflictTx); err == nil { t.Fatal("should conflict with the utxoSet in the mempool") } // force add the tx @@ -3942,7 +3942,7 @@ func TestBuildBlockDoesNotExceedAtomicGasLimit(t *testing.T) { if err != nil { t.Fatal(err) } - if err := vm.issueTx(importTx, true); err != nil { + if err := vm.mempool.AddLocalTx(importTx); err != nil { t.Fatal(err) } } @@ -4001,7 +4001,7 @@ func TestExtraStateChangeAtomicGasLimitExceeded(t *testing.T) { if err != nil { t.Fatal(err) } - if err := vm1.issueTx(importTx, true); err != nil { + if err := vm1.mempool.ForceAddTx(importTx); err != nil { t.Fatal(err) } @@ -4064,7 +4064,7 @@ func TestSkipChainConfigCheckCompatible(t *testing.T) { // accept one block to test the SkipUpgradeCheck functionality. importTx, err := vm.newImportTx(vm.ctx.XChainID, testEthAddrs[0], initialBaseFee, []*secp256k1.PrivateKey{testKeys[0]}) require.NoError(t, err) - require.NoError(t, vm.issueTx(importTx, true /*=local*/)) + require.NoError(t, vm.mempool.AddLocalTx(importTx)) <-issuer blk, err := vm.BuildBlock(context.Background()) diff --git a/scripts/versions.sh b/scripts/versions.sh index 4fddd68a99..7549135fa9 100644 --- a/scripts/versions.sh +++ b/scripts/versions.sh @@ -1,4 +1,4 @@ #!/usr/bin/env bash # Don't export them as they're used in the context of other calls -avalanche_version=${AVALANCHE_VERSION:-'v1.10.11'} +avalanche_version=${AVALANCHE_VERSION:-'v1.10.12-rc.5'}