From 0a126ef0ca54c99ada3fa44a5761e7f3552f58b2 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Wed, 24 Dec 2025 16:03:46 -0500 Subject: [PATCH 01/35] Add ws handler --- sae/http.go | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/sae/http.go b/sae/http.go index da10dd5e2..105de2aeb 100644 --- a/sae/http.go +++ b/sae/http.go @@ -6,6 +6,8 @@ package sae import ( "context" "net/http" + + "github.com/ava-labs/avalanchego/snow/engine/common" ) const ( @@ -22,13 +24,21 @@ func (vm *VM) CreateHandlers(ctx context.Context) (map[string]http.Handler, erro } return map[string]http.Handler{ rpcHTTPExtensionPath: s, - // TODO(arr4n): add websocket support at [wsHTTPExtensionPath] + // TODO(StephenButtolph) coreth and subnet-evm have modified the ws + // handler to introduce CPU limiting and maximum request durations. We + // should either include those modifications into libevm, or determine + // that those restrictions were not required. + wsHTTPExtensionPath: s.WebsocketHandler([]string{"*"}), }, nil } // NewHTTPHandler returns the HTTP handler that will be invoked if a client // passes this VM's chain ID via the routing header described in the [common.VM] // documentation for this method. -func (vm *VM) NewHTTPHandler(context.Context) (http.Handler, error) { - return nil, errUnimplemented +// +// Ethereum-compatible VMs don't typically utilize HTTP2, so [VM.CreateHandlers] +// is used instead. +func (*VM) NewHTTPHandler(context.Context) (http.Handler, error) { + var _ common.VM // maintain import for [comment] rendering + return nil, nil } From 3120f57f301a3b9144151e420ea9647adb08afc1 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Fri, 26 Dec 2025 17:54:47 -0500 Subject: [PATCH 02/35] WIP Support all APIs --- go.mod | 2 + sae/rpc.go | 467 +++++++++++++++++++++++++++++++++++++++++++++-- sae/temporary.go | 9 - saexec/saexec.go | 5 + 4 files changed, 459 insertions(+), 24 deletions(-) diff --git a/go.mod b/go.mod index 9e23c4464..5696e728d 100644 --- a/go.mod +++ b/go.mod @@ -166,3 +166,5 @@ require ( gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) + +replace github.com/ava-labs/libevm => /Users/stephen/go/src/github.com/ava-labs/libevm diff --git a/sae/rpc.go b/sae/rpc.go index 581f0c7a5..89cadfdfe 100644 --- a/sae/rpc.go +++ b/sae/rpc.go @@ -4,49 +4,486 @@ package sae import ( + "context" "fmt" + "math/big" + "strconv" + "time" + "github.com/ava-labs/avalanchego/network/p2p" + "github.com/ava-labs/avalanchego/version" + ethereum "github.com/ava-labs/libevm" + "github.com/ava-labs/libevm/accounts" + "github.com/ava-labs/libevm/common" + "github.com/ava-labs/libevm/common/hexutil" + "github.com/ava-labs/libevm/consensus" + "github.com/ava-labs/libevm/core" + "github.com/ava-labs/libevm/core/bloombits" + "github.com/ava-labs/libevm/core/state" "github.com/ava-labs/libevm/core/types" + "github.com/ava-labs/libevm/core/vm" + "github.com/ava-labs/libevm/eth/filters" + "github.com/ava-labs/libevm/eth/tracers" + "github.com/ava-labs/libevm/ethdb" + "github.com/ava-labs/libevm/event" + "github.com/ava-labs/libevm/libevm/debug" "github.com/ava-labs/libevm/libevm/ethapi" + "github.com/ava-labs/libevm/node" "github.com/ava-labs/libevm/params" "github.com/ava-labs/libevm/rpc" + "go.uber.org/zap" "github.com/ava-labs/strevm/txgossip" ) func (vm *VM) ethRPCServer() (*rpc.Server, error) { - b := ðAPIBackend{ + b := &apiBackend{ vm: vm, Set: vm.mempool, } + filterSystem := filters.NewFilterSystem(b, filters.Config{Timeout: 5 * time.Minute}) + apis := []struct { + namespace string + api any + }{ + // https://ethereum.org/developers/docs/apis/json-rpc/#web3_clientversion + // https://ethereum.org/developers/docs/apis/json-rpc/#web3_sha3 + {"web3", node.NewWeb3API(version.Current.String())}, + // https://ethereum.org/developers/docs/apis/json-rpc#net_version + // https://ethereum.org/developers/docs/apis/json-rpc#net_listening + // https://ethereum.org/developers/docs/apis/json-rpc#net_peercount + // + // TODO: Populate p2p.Peers once we have P2P networking integrated. + {"net", newNetAPI(nil, vm.exec.ChainConfig().ChainID.Uint64())}, + // https://ethereum.org/developers/docs/apis/json-rpc#eth_syncing + // https://ethereum.org/developers/docs/apis/json-rpc#eth_gasprice + {"eth", ethapi.NewEthereumAPI(b)}, + // https://ethereum.org/developers/docs/apis/json-rpc#eth_chainid + // https://ethereum.org/developers/docs/apis/json-rpc#eth_blocknumber + // https://ethereum.org/developers/docs/apis/json-rpc#eth_getbalance + // https://ethereum.org/developers/docs/apis/json-rpc#eth_getstorageat + // https://ethereum.org/developers/docs/apis/json-rpc#eth_getunclecountbyblockhash + // https://ethereum.org/developers/docs/apis/json-rpc#eth_getunclecountbyblocknumber + // https://ethereum.org/developers/docs/apis/json-rpc#eth_getcode + // https://ethereum.org/developers/docs/apis/json-rpc#eth_call + // https://ethereum.org/developers/docs/apis/json-rpc#eth_estimategas + // https://ethereum.org/developers/docs/apis/json-rpc#eth_getblockbyhash + // https://ethereum.org/developers/docs/apis/json-rpc#eth_getblockbynumber + // https://ethereum.org/developers/docs/apis/json-rpc#eth_getunclebyblockhashandindex + // https://ethereum.org/developers/docs/apis/json-rpc#eth_getunclebyblocknumberandindex + // + // https://geth.ethereum.org/docs/interacting-with-geth/rpc/ns-eth#eth-createaccesslist + // https://geth.ethereum.org/docs/interacting-with-geth/rpc/ns-eth#ethgetheaderbynumber + // https://geth.ethereum.org/docs/interacting-with-geth/rpc/ns-eth#ethgetheaderbyhash + {"eth", ethapi.NewBlockChainAPI(b)}, + // https://ethereum.org/developers/docs/apis/json-rpc#eth_gettransactioncount + // https://ethereum.org/developers/docs/apis/json-rpc#eth_getblocktransactioncountbyhash + // https://ethereum.org/developers/docs/apis/json-rpc#eth_getblocktransactioncountbynumber + // https://ethereum.org/developers/docs/apis/json-rpc#eth_sign + // https://ethereum.org/developers/docs/apis/json-rpc#eth_signtransaction + // https://ethereum.org/developers/docs/apis/json-rpc#eth_sendtransaction + // https://ethereum.org/developers/docs/apis/json-rpc#eth_sendrawtransaction + // https://ethereum.org/developers/docs/apis/json-rpc#eth_gettransactionbyhash + // https://ethereum.org/developers/docs/apis/json-rpc#eth_gettransactionbyblockhashandindex + // https://ethereum.org/developers/docs/apis/json-rpc#eth_gettransactionbyblocknumberandindex + // https://ethereum.org/developers/docs/apis/json-rpc#eth_gettransactionreceipt + {"eth", ethapi.NewTransactionAPI(b, new(ethapi.AddrLocker))}, + // https://ethereum.org/developers/docs/apis/json-rpc#eth_newfilter + // https://ethereum.org/developers/docs/apis/json-rpc#eth_newblockfilter + // https://ethereum.org/developers/docs/apis/json-rpc#eth_newpendingtransactionfilter + // https://ethereum.org/developers/docs/apis/json-rpc#eth_uninstallfilter + // https://ethereum.org/developers/docs/apis/json-rpc#eth_getfilterchanges + // https://ethereum.org/developers/docs/apis/json-rpc#eth_getfilterlogs + // https://ethereum.org/developers/docs/apis/json-rpc#eth_getlogs + {"eth", filters.NewFilterAPI(filterSystem, false)}, + // https://geth.ethereum.org/docs/interacting-with-geth/rpc/ns-txpool#txpool-content + // https://geth.ethereum.org/docs/interacting-with-geth/rpc/ns-txpool#txpool-contentfrom + // https://geth.ethereum.org/docs/interacting-with-geth/rpc/ns-txpool#txpool-inspect + // https://geth.ethereum.org/docs/interacting-with-geth/rpc/ns-txpool#txpool-status + {"txpool", ethapi.NewTxPoolAPI(b)}, + // https://geth.ethereum.org/docs/interacting-with-geth/rpc/ns-debug#debugchaindbcompact + // https://geth.ethereum.org/docs/interacting-with-geth/rpc/ns-debug#debugchaindbproperty + // https://geth.ethereum.org/docs/interacting-with-geth/rpc/ns-debug#debugdbancient + // https://geth.ethereum.org/docs/interacting-with-geth/rpc/ns-debug#debugdbancients + // https://geth.ethereum.org/docs/interacting-with-geth/rpc/ns-debug#debugdbget + // https://geth.ethereum.org/docs/interacting-with-geth/rpc/ns-debug#debuggetrawblock + // https://geth.ethereum.org/docs/interacting-with-geth/rpc/ns-debug#debuggetrawheader + // https://geth.ethereum.org/docs/interacting-with-geth/rpc/ns-debug#debuggetrawtransaction + // https://geth.ethereum.org/docs/interacting-with-geth/rpc/ns-debug#debuggetrawreceipts + // https://geth.ethereum.org/docs/interacting-with-geth/rpc/ns-debug#debugprintblock + // https://geth.ethereum.org/docs/interacting-with-geth/rpc/ns-debug#debugsethead + {"debug", ethapi.NewDebugAPI(b)}, + // https://geth.ethereum.org/docs/interacting-with-geth/rpc/ns-debug#debugintermediateroots + // https://geth.ethereum.org/docs/interacting-with-geth/rpc/ns-debug#debugstandardtraceblocktofile + // https://geth.ethereum.org/docs/interacting-with-geth/rpc/ns-debug#debugstandardtracebadblocktofile + // https://geth.ethereum.org/docs/interacting-with-geth/rpc/ns-debug#debugtracebadblock + // https://geth.ethereum.org/docs/interacting-with-geth/rpc/ns-debug#debugtraceblock + // https://geth.ethereum.org/docs/interacting-with-geth/rpc/ns-debug#debugtraceblockbynumber + // https://geth.ethereum.org/docs/interacting-with-geth/rpc/ns-debug#debugtraceblockbyhash + // https://geth.ethereum.org/docs/interacting-with-geth/rpc/ns-debug#debugtraceblockfromfile + // https://geth.ethereum.org/docs/interacting-with-geth/rpc/ns-debug#debugtracecall + // https://geth.ethereum.org/docs/interacting-with-geth/rpc/ns-debug#debugtracechain + // https://geth.ethereum.org/docs/interacting-with-geth/rpc/ns-debug#debugtracetransaction + {"debug", tracers.NewAPI(b)}, + // https://geth.ethereum.org/docs/interacting-with-geth/rpc/ns-debug#debugblockprofile + // https://geth.ethereum.org/docs/interacting-with-geth/rpc/ns-debug#debugcpuprofile + // https://geth.ethereum.org/docs/interacting-with-geth/rpc/ns-debug#debugfreeosmemory + // https://geth.ethereum.org/docs/interacting-with-geth/rpc/ns-debug#debuggcstats + // https://geth.ethereum.org/docs/interacting-with-geth/rpc/ns-debug#debuggotrace + // https://geth.ethereum.org/docs/interacting-with-geth/rpc/ns-debug#debugmemstats + // https://geth.ethereum.org/docs/interacting-with-geth/rpc/ns-debug#debugmutexprofile + // https://geth.ethereum.org/docs/interacting-with-geth/rpc/ns-debug#debugsetblockprofilerate + // https://geth.ethereum.org/docs/interacting-with-geth/rpc/ns-debug#debugsetgcpercent + // https://geth.ethereum.org/docs/interacting-with-geth/rpc/ns-debug#debugsetmutexprofilefraction + // https://geth.ethereum.org/docs/interacting-with-geth/rpc/ns-debug#debugstacks + // https://geth.ethereum.org/docs/interacting-with-geth/rpc/ns-debug#debugstartcpuprofile + // https://geth.ethereum.org/docs/interacting-with-geth/rpc/ns-debug#debugstartgotrace + // https://geth.ethereum.org/docs/interacting-with-geth/rpc/ns-debug#debugstopcpuprofile + // https://geth.ethereum.org/docs/interacting-with-geth/rpc/ns-debug#debugstopgotrace + // https://geth.ethereum.org/docs/interacting-with-geth/rpc/ns-debug#debugverbosity + // https://geth.ethereum.org/docs/interacting-with-geth/rpc/ns-debug#debugvmodule + // https://geth.ethereum.org/docs/interacting-with-geth/rpc/ns-debug#debugwriteblockprofile + // https://geth.ethereum.org/docs/interacting-with-geth/rpc/ns-debug#debugwritememprofile + // https://geth.ethereum.org/docs/interacting-with-geth/rpc/ns-debug#debugwritemutexprofile + {"debug", debug.Handler}, + } + // Unsupported APIs: + // + // "Standard" Ethereum node APIs: + // https://ethereum.org/developers/docs/apis/json-rpc#eth_protocolversion + // https://ethereum.org/developers/docs/apis/json-rpc#eth_coinbase + // https://ethereum.org/developers/docs/apis/json-rpc#eth_mining + // https://ethereum.org/developers/docs/apis/json-rpc#eth_hashrate + // https://ethereum.org/developers/docs/apis/json-rpc#eth_accounts + // + // Block and state inspection APIs: + // https://geth.ethereum.org/docs/interacting-with-geth/rpc/ns-debug#debugaccountrange + // https://geth.ethereum.org/docs/interacting-with-geth/rpc/ns-debug#debugdumpblock + // https://geth.ethereum.org/docs/interacting-with-geth/rpc/ns-debug#debuggetaccessiblestate + // https://geth.ethereum.org/docs/interacting-with-geth/rpc/ns-debug#debuggetbadblocks + // https://geth.ethereum.org/docs/interacting-with-geth/rpc/ns-debug#debuggetmodifiedaccountsbyhash + // https://geth.ethereum.org/docs/interacting-with-geth/rpc/ns-debug#debuggetmodifiedaccountsbynumber + // https://geth.ethereum.org/docs/interacting-with-geth/rpc/ns-debug#debuggettrieflushinterval + // https://geth.ethereum.org/docs/interacting-with-geth/rpc/ns-debug#debugpreimage + // https://geth.ethereum.org/docs/interacting-with-geth/rpc/ns-debug#debugsettrieflushinterval + // https://geth.ethereum.org/docs/interacting-with-geth/rpc/ns-debug#debugstoragerangeat + // + // The admin namespace. + // The clique namespace. + // The les namespace. + // The miner namespace. + // The personal namespace. s := rpc.NewServer() + for _, api := range apis { + if err := s.RegisterName(api.namespace, api.api); err != nil { + return nil, fmt.Errorf("%T.RegisterName(%q, %T): %v", s, api.namespace, api.api, err) + } + } + return s, nil +} - txAPI := ethapi.NewTransactionAPI(b, new(ethapi.AddrLocker)) - if err := s.RegisterName("eth", txAPI); err != nil { - return nil, fmt.Errorf("%T.RegisterName(%q, %T): %v", s, "eth", txAPI, err) +// netAPI offers the `net` RPCs. +type netAPI struct { + peers *p2p.Peers + chainID string +} + +func newNetAPI(peers *p2p.Peers, chainID uint64) *netAPI { + return &netAPI{ + peers: peers, + chainID: strconv.FormatUint(chainID, 10), } +} - return s, nil +func (s *netAPI) Version() string { + return s.chainID +} + +func (s *netAPI) Listening() bool { + return true // The node is always listening for p2p connections. } -type ethAPIBackend struct { - vm *VM - ethapi.Backend // TODO(arr4n) remove in favour of `var _ ethapi.Backend = (*ethAPIBackend)(nil)` +func (s *netAPI) PeerCount() hexutil.Uint { + // TODO(StephenButtolph) Once [avalanchego#4792] is merged, we should report + // the correct number of peers. + return 0 +} + +var ( + _ ethapi.Backend = (*apiBackend)(nil) + _ tracers.Backend = (*apiBackend)(nil) +) + +type apiBackend struct { *txgossip.Set + vm *VM +} + +func (a *apiBackend) SyncProgress() ethereum.SyncProgress { + // Avalanchego does not expose APIs until after the node has fully synced, + // so we can just report that syncing is complete. + return ethereum.SyncProgress{} +} + +func (a *apiBackend) SuggestGasTipCap(ctx context.Context) (*big.Int, error) { + panic(errUnimplemented) +} + +func (a *apiBackend) FeeHistory(ctx context.Context, blockCount uint64, lastBlock rpc.BlockNumber, rewardPercentiles []float64) (*big.Int, [][]*big.Int, []*big.Int, []float64, error) { + panic(errUnimplemented) } -func (b *ethAPIBackend) ChainConfig() *params.ChainConfig { - return b.vm.exec.ChainConfig() +func (a *apiBackend) ChainDb() ethdb.Database { + return a.vm.exec.DB() } -func (b *ethAPIBackend) RPCTxFeeCap() float64 { - return 0 // TODO(arr4n) +func (a *apiBackend) AccountManager() *accounts.Manager { + panic(errUnimplemented) } -func (b *ethAPIBackend) UnprotectedAllowed() bool { +func (a *apiBackend) ExtRPCEnabled() bool { + // We never recommend to expose the RPC externally. Additionally, this is + // only used as an additional security measure for the personal API, which + // we do not support in its entirety. return false } -func (b *ethAPIBackend) CurrentBlock() *types.Header { - return types.CopyHeader(b.vm.exec.LastExecuted().Header()) +func (a *apiBackend) RPCGasCap() uint64 { + // TODO(StephenButtolph) Expose this as a config. + return 25_000_000 +} + +func (a *apiBackend) RPCEVMTimeout() time.Duration { + // TODO(StephenButtolph) Expose this as a config. + return 5 * time.Second +} + +func (a *apiBackend) RPCTxFeeCap() float64 { + // TODO(StephenButtolph) Expose this as a config. + return 1 // 1 AVAX +} + +func (a *apiBackend) UnprotectedAllowed() bool { + // TODO(StephenButtolph) Expose this as a config and default to false. + return true +} + +func (a *apiBackend) SetHead(number uint64) { + // SAE does not support reorgs, so we ignore attempts to override the chain + // head. + a.vm.log().Warn("ignoring attempt to override the chain head", + zap.Uint64("number", number), + ) +} + +// HeaderByNumber implements ethapi.Backend. +// Subtle: this method shadows the method (apiBackend).HeaderByNumber of ethAPIBackend2.apiBackend. +func (a *apiBackend) HeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Header, error) { + panic(errUnimplemented) +} + +// HeaderByHash implements ethapi.Backend. +// Subtle: this method shadows the method (apiBackend).HeaderByHash of ethAPIBackend2.apiBackend. +func (a *apiBackend) HeaderByHash(ctx context.Context, hash common.Hash) (*types.Header, error) { + panic(errUnimplemented) +} + +// HeaderByNumberOrHash implements ethapi.Backend. +// Subtle: this method shadows the method (apiBackend).HeaderByNumberOrHash of ethAPIBackend2.apiBackend. +func (a *apiBackend) HeaderByNumberOrHash(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (*types.Header, error) { + panic(errUnimplemented) +} + +// CurrentHeader implements ethapi.Backend. +// Subtle: this method shadows the method (apiBackend).CurrentHeader of ethAPIBackend2.apiBackend. +func (a *apiBackend) CurrentHeader() *types.Header { + panic(errUnimplemented) +} + +// CurrentBlock implements ethapi.Backend. +// Subtle: this method shadows the method (apiBackend).CurrentBlock of ethAPIBackend2.apiBackend. +func (a *apiBackend) CurrentBlock() *types.Header { + return types.CopyHeader(a.vm.exec.LastExecuted().Header()) +} + +// BlockByNumber implements ethapi.Backend. +// Subtle: this method shadows the method (apiBackend).BlockByNumber of ethAPIBackend2.apiBackend. +func (a *apiBackend) BlockByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Block, error) { + panic(errUnimplemented) +} + +// BlockByHash implements ethapi.Backend. +// Subtle: this method shadows the method (apiBackend).BlockByHash of ethAPIBackend2.apiBackend. +func (a *apiBackend) BlockByHash(ctx context.Context, hash common.Hash) (*types.Block, error) { + panic(errUnimplemented) +} + +// BlockByNumberOrHash implements ethapi.Backend. +// Subtle: this method shadows the method (apiBackend).BlockByNumberOrHash of ethAPIBackend2.apiBackend. +func (a *apiBackend) BlockByNumberOrHash(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (*types.Block, error) { + panic(errUnimplemented) +} + +// StateAndHeaderByNumber implements ethapi.Backend. +// Subtle: this method shadows the method (apiBackend).StateAndHeaderByNumber of ethAPIBackend2.apiBackend. +func (a *apiBackend) StateAndHeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*state.StateDB, *types.Header, error) { + panic(errUnimplemented) +} + +// StateAndHeaderByNumberOrHash implements ethapi.Backend. +// Subtle: this method shadows the method (apiBackend).StateAndHeaderByNumberOrHash of ethAPIBackend2.apiBackend. +func (a *apiBackend) StateAndHeaderByNumberOrHash(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (*state.StateDB, *types.Header, error) { + panic(errUnimplemented) +} + +// PendingBlockAndReceipts implements ethapi.Backend. +// Subtle: this method shadows the method (apiBackend).PendingBlockAndReceipts of ethAPIBackend2.apiBackend. +func (a *apiBackend) PendingBlockAndReceipts() (*types.Block, types.Receipts) { + panic(errUnimplemented) +} + +// GetReceipts implements ethapi.Backend. +// Subtle: this method shadows the method (apiBackend).GetReceipts of ethAPIBackend2.apiBackend. +func (a *apiBackend) GetReceipts(ctx context.Context, hash common.Hash) (types.Receipts, error) { + panic(errUnimplemented) +} + +// GetTd implements ethapi.Backend. +// Subtle: this method shadows the method (apiBackend).GetTd of ethAPIBackend2.apiBackend. +func (a *apiBackend) GetTd(ctx context.Context, hash common.Hash) *big.Int { + panic(errUnimplemented) +} + +// GetEVM implements ethapi.Backend. +// Subtle: this method shadows the method (apiBackend).GetEVM of ethAPIBackend2.apiBackend. +func (a *apiBackend) GetEVM(ctx context.Context, msg *core.Message, state *state.StateDB, header *types.Header, vmConfig *vm.Config, blockCtx *vm.BlockContext) *vm.EVM { + panic(errUnimplemented) +} + +// SubscribeChainEvent implements ethapi.Backend. +// Subtle: this method shadows the method (apiBackend).SubscribeChainEvent of ethAPIBackend2.apiBackend. +func (a *apiBackend) SubscribeChainEvent(ch chan<- core.ChainEvent) event.Subscription { + panic(errUnimplemented) +} + +// SubscribeChainHeadEvent implements ethapi.Backend. +// Subtle: this method shadows the method (apiBackend).SubscribeChainHeadEvent of ethAPIBackend2.apiBackend. +func (a *apiBackend) SubscribeChainHeadEvent(ch chan<- core.ChainHeadEvent) event.Subscription { + panic(errUnimplemented) +} + +// SubscribeChainSideEvent implements ethapi.Backend. +// Subtle: this method shadows the method (apiBackend).SubscribeChainSideEvent of ethAPIBackend2.apiBackend. +func (a *apiBackend) SubscribeChainSideEvent(ch chan<- core.ChainSideEvent) event.Subscription { + panic(errUnimplemented) +} + +// GetTransaction implements ethapi.Backend. +// Subtle: this method shadows the method (apiBackend).GetTransaction of ethAPIBackend2.apiBackend. +func (a *apiBackend) GetTransaction(ctx context.Context, txHash common.Hash) (bool, *types.Transaction, common.Hash, uint64, uint64, error) { + panic(errUnimplemented) +} + +// GetPoolTransactions implements ethapi.Backend. +// Subtle: this method shadows the method (apiBackend).GetPoolTransactions of ethAPIBackend2.apiBackend. +func (a *apiBackend) GetPoolTransactions() (types.Transactions, error) { + panic(errUnimplemented) +} + +// GetPoolTransaction implements ethapi.Backend. +// Subtle: this method shadows the method (apiBackend).GetPoolTransaction of ethAPIBackend2.apiBackend. +func (a *apiBackend) GetPoolTransaction(txHash common.Hash) *types.Transaction { + panic(errUnimplemented) +} + +// GetPoolNonce implements ethapi.Backend. +// Subtle: this method shadows the method (apiBackend).GetPoolNonce of ethAPIBackend2.apiBackend. +func (a *apiBackend) GetPoolNonce(ctx context.Context, addr common.Address) (uint64, error) { + panic(errUnimplemented) +} + +// Stats implements ethapi.Backend. +// Subtle: this method shadows the method (apiBackend).Stats of ethAPIBackend2.apiBackend. +func (a *apiBackend) Stats() (pending int, queued int) { + panic(errUnimplemented) +} + +// TxPoolContent implements ethapi.Backend. +// Subtle: this method shadows the method (apiBackend).TxPoolContent of ethAPIBackend2.apiBackend. +func (a *apiBackend) TxPoolContent() (map[common.Address][]*types.Transaction, map[common.Address][]*types.Transaction) { + panic(errUnimplemented) +} + +// TxPoolContentFrom implements ethapi.Backend. +// Subtle: this method shadows the method (apiBackend).TxPoolContentFrom of ethAPIBackend2.apiBackend. +func (a *apiBackend) TxPoolContentFrom(addr common.Address) ([]*types.Transaction, []*types.Transaction) { + panic(errUnimplemented) +} + +// SubscribeNewTxsEvent implements ethapi.Backend. +// Subtle: this method shadows the method (apiBackend).SubscribeNewTxsEvent of ethAPIBackend2.apiBackend. +func (a *apiBackend) SubscribeNewTxsEvent(chan<- core.NewTxsEvent) event.Subscription { + panic(errUnimplemented) +} + +func (a *apiBackend) ChainConfig() *params.ChainConfig { + return a.vm.exec.ChainConfig() +} + +// Engine implements ethapi.Backend. +// Subtle: this method shadows the method (apiBackend).Engine of ethAPIBackend2.apiBackend. +func (a *apiBackend) Engine() consensus.Engine { + panic(errUnimplemented) +} + +// GetBody implements ethapi.Backend. +// Subtle: this method shadows the method (apiBackend).GetBody of ethAPIBackend2.apiBackend. +func (a *apiBackend) GetBody(ctx context.Context, hash common.Hash, number rpc.BlockNumber) (*types.Body, error) { + panic(errUnimplemented) +} + +// GetLogs implements ethapi.Backend. +// Subtle: this method shadows the method (apiBackend).GetLogs of ethAPIBackend2.apiBackend. +func (a *apiBackend) GetLogs(ctx context.Context, blockHash common.Hash, number uint64) ([][]*types.Log, error) { + panic(errUnimplemented) +} + +// SubscribeRemovedLogsEvent implements ethapi.Backend. +// Subtle: this method shadows the method (apiBackend).SubscribeRemovedLogsEvent of ethAPIBackend2.apiBackend. +func (a *apiBackend) SubscribeRemovedLogsEvent(ch chan<- core.RemovedLogsEvent) event.Subscription { + panic(errUnimplemented) +} + +// SubscribeLogsEvent implements ethapi.Backend. +// Subtle: this method shadows the method (apiBackend).SubscribeLogsEvent of ethAPIBackend2.apiBackend. +func (a *apiBackend) SubscribeLogsEvent(ch chan<- []*types.Log) event.Subscription { + panic(errUnimplemented) +} + +// SubscribePendingLogsEvent implements ethapi.Backend. +// Subtle: this method shadows the method (apiBackend).SubscribePendingLogsEvent of ethAPIBackend2.apiBackend. +func (a *apiBackend) SubscribePendingLogsEvent(ch chan<- []*types.Log) event.Subscription { + panic(errUnimplemented) +} + +// BloomStatus implements ethapi.Backend. +// Subtle: this method shadows the method (apiBackend).BloomStatus of ethAPIBackend2.apiBackend. +func (a *apiBackend) BloomStatus() (uint64, uint64) { + panic(errUnimplemented) +} + +// ServiceFilter implements ethapi.Backend. +// Subtle: this method shadows the method (apiBackend).ServiceFilter of ethAPIBackend2.apiBackend. +func (a *apiBackend) ServiceFilter(ctx context.Context, session *bloombits.MatcherSession) { + panic(errUnimplemented) +} + +// StateAtBlock implements tracers.Backend. +func (a *apiBackend) StateAtBlock(ctx context.Context, block *types.Block, reexec uint64, base *state.StateDB, readOnly bool, preferDisk bool) (*state.StateDB, tracers.StateReleaseFunc, error) { + panic(errUnimplemented) +} + +// StateAtTransaction implements tracers.Backend. +func (a *apiBackend) StateAtTransaction(ctx context.Context, block *types.Block, txIndex int, reexec uint64) (*core.Message, vm.BlockContext, *state.StateDB, tracers.StateReleaseFunc, error) { + panic(errUnimplemented) } diff --git a/sae/temporary.go b/sae/temporary.go index c84c6a8a1..0a1ef7208 100644 --- a/sae/temporary.go +++ b/sae/temporary.go @@ -7,16 +7,7 @@ package sae // interim identifiers needed for development over multiple PRs. import ( - "context" "errors" - - "github.com/ava-labs/libevm/core/types" ) var errUnimplemented = errors.New("unimplemented") - -// TODO(arr4n) remove this method once no longer embedding [ethapi.Backend] in -// [ethAPIBackend] as it's only required for disambiguation. -func (b *ethAPIBackend) SendTx(ctx context.Context, tx *types.Transaction) error { - return b.Set.SendTx(ctx, tx) -} diff --git a/saexec/saexec.go b/saexec/saexec.go index 7aea840cc..86d2424ce 100644 --- a/saexec/saexec.go +++ b/saexec/saexec.go @@ -117,6 +117,11 @@ func (e *Executor) ChainConfig() *params.ChainConfig { return e.chainConfig } +// DB returns the db originally passed to [New]. +func (e *Executor) DB() ethdb.Database { + return e.db +} + // StateCache returns caching database underpinning execution. func (e *Executor) StateCache() state.Database { return e.stateCache From 81ae52fc1f5f6cba9492f00db77938ebfdea8cbb Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Fri, 26 Dec 2025 18:36:17 -0500 Subject: [PATCH 03/35] WIP Support all APIs --- sae/rpc.go | 96 +++++++++++++----------------------------------------- 1 file changed, 22 insertions(+), 74 deletions(-) diff --git a/sae/rpc.go b/sae/rpc.go index 89cadfdfe..c7774e339 100644 --- a/sae/rpc.go +++ b/sae/rpc.go @@ -20,6 +20,7 @@ import ( "github.com/ava-labs/libevm/core" "github.com/ava-labs/libevm/core/bloombits" "github.com/ava-labs/libevm/core/state" + "github.com/ava-labs/libevm/core/txpool" "github.com/ava-labs/libevm/core/types" "github.com/ava-labs/libevm/core/vm" "github.com/ava-labs/libevm/eth/filters" @@ -276,152 +277,117 @@ func (a *apiBackend) SetHead(number uint64) { ) } -// HeaderByNumber implements ethapi.Backend. -// Subtle: this method shadows the method (apiBackend).HeaderByNumber of ethAPIBackend2.apiBackend. func (a *apiBackend) HeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Header, error) { panic(errUnimplemented) } -// HeaderByHash implements ethapi.Backend. -// Subtle: this method shadows the method (apiBackend).HeaderByHash of ethAPIBackend2.apiBackend. func (a *apiBackend) HeaderByHash(ctx context.Context, hash common.Hash) (*types.Header, error) { panic(errUnimplemented) } -// HeaderByNumberOrHash implements ethapi.Backend. -// Subtle: this method shadows the method (apiBackend).HeaderByNumberOrHash of ethAPIBackend2.apiBackend. func (a *apiBackend) HeaderByNumberOrHash(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (*types.Header, error) { panic(errUnimplemented) } -// CurrentHeader implements ethapi.Backend. -// Subtle: this method shadows the method (apiBackend).CurrentHeader of ethAPIBackend2.apiBackend. func (a *apiBackend) CurrentHeader() *types.Header { panic(errUnimplemented) } -// CurrentBlock implements ethapi.Backend. -// Subtle: this method shadows the method (apiBackend).CurrentBlock of ethAPIBackend2.apiBackend. func (a *apiBackend) CurrentBlock() *types.Header { return types.CopyHeader(a.vm.exec.LastExecuted().Header()) } -// BlockByNumber implements ethapi.Backend. -// Subtle: this method shadows the method (apiBackend).BlockByNumber of ethAPIBackend2.apiBackend. func (a *apiBackend) BlockByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Block, error) { panic(errUnimplemented) } -// BlockByHash implements ethapi.Backend. -// Subtle: this method shadows the method (apiBackend).BlockByHash of ethAPIBackend2.apiBackend. func (a *apiBackend) BlockByHash(ctx context.Context, hash common.Hash) (*types.Block, error) { panic(errUnimplemented) } -// BlockByNumberOrHash implements ethapi.Backend. -// Subtle: this method shadows the method (apiBackend).BlockByNumberOrHash of ethAPIBackend2.apiBackend. func (a *apiBackend) BlockByNumberOrHash(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (*types.Block, error) { panic(errUnimplemented) } -// StateAndHeaderByNumber implements ethapi.Backend. -// Subtle: this method shadows the method (apiBackend).StateAndHeaderByNumber of ethAPIBackend2.apiBackend. func (a *apiBackend) StateAndHeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*state.StateDB, *types.Header, error) { panic(errUnimplemented) } -// StateAndHeaderByNumberOrHash implements ethapi.Backend. -// Subtle: this method shadows the method (apiBackend).StateAndHeaderByNumberOrHash of ethAPIBackend2.apiBackend. func (a *apiBackend) StateAndHeaderByNumberOrHash(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (*state.StateDB, *types.Header, error) { panic(errUnimplemented) } -// PendingBlockAndReceipts implements ethapi.Backend. -// Subtle: this method shadows the method (apiBackend).PendingBlockAndReceipts of ethAPIBackend2.apiBackend. func (a *apiBackend) PendingBlockAndReceipts() (*types.Block, types.Receipts) { panic(errUnimplemented) } -// GetReceipts implements ethapi.Backend. -// Subtle: this method shadows the method (apiBackend).GetReceipts of ethAPIBackend2.apiBackend. func (a *apiBackend) GetReceipts(ctx context.Context, hash common.Hash) (types.Receipts, error) { panic(errUnimplemented) } -// GetTd implements ethapi.Backend. -// Subtle: this method shadows the method (apiBackend).GetTd of ethAPIBackend2.apiBackend. func (a *apiBackend) GetTd(ctx context.Context, hash common.Hash) *big.Int { panic(errUnimplemented) } -// GetEVM implements ethapi.Backend. -// Subtle: this method shadows the method (apiBackend).GetEVM of ethAPIBackend2.apiBackend. func (a *apiBackend) GetEVM(ctx context.Context, msg *core.Message, state *state.StateDB, header *types.Header, vmConfig *vm.Config, blockCtx *vm.BlockContext) *vm.EVM { panic(errUnimplemented) } -// SubscribeChainEvent implements ethapi.Backend. -// Subtle: this method shadows the method (apiBackend).SubscribeChainEvent of ethAPIBackend2.apiBackend. func (a *apiBackend) SubscribeChainEvent(ch chan<- core.ChainEvent) event.Subscription { panic(errUnimplemented) } -// SubscribeChainHeadEvent implements ethapi.Backend. -// Subtle: this method shadows the method (apiBackend).SubscribeChainHeadEvent of ethAPIBackend2.apiBackend. func (a *apiBackend) SubscribeChainHeadEvent(ch chan<- core.ChainHeadEvent) event.Subscription { panic(errUnimplemented) } -// SubscribeChainSideEvent implements ethapi.Backend. -// Subtle: this method shadows the method (apiBackend).SubscribeChainSideEvent of ethAPIBackend2.apiBackend. func (a *apiBackend) SubscribeChainSideEvent(ch chan<- core.ChainSideEvent) event.Subscription { panic(errUnimplemented) } -// GetTransaction implements ethapi.Backend. -// Subtle: this method shadows the method (apiBackend).GetTransaction of ethAPIBackend2.apiBackend. func (a *apiBackend) GetTransaction(ctx context.Context, txHash common.Hash) (bool, *types.Transaction, common.Hash, uint64, uint64, error) { panic(errUnimplemented) } -// GetPoolTransactions implements ethapi.Backend. -// Subtle: this method shadows the method (apiBackend).GetPoolTransactions of ethAPIBackend2.apiBackend. func (a *apiBackend) GetPoolTransactions() (types.Transactions, error) { - panic(errUnimplemented) + pending := a.Pool.Pending(txpool.PendingFilter{}) + + var pendingCount int + for _, batch := range pending { + pendingCount += len(batch) + } + + txs := make(types.Transactions, 0, pendingCount) + for _, batch := range pending { + for _, lazy := range batch { + if tx := lazy.Resolve(); tx != nil { + txs = append(txs, tx) + } + } + } + return txs, nil } -// GetPoolTransaction implements ethapi.Backend. -// Subtle: this method shadows the method (apiBackend).GetPoolTransaction of ethAPIBackend2.apiBackend. func (a *apiBackend) GetPoolTransaction(txHash common.Hash) *types.Transaction { - panic(errUnimplemented) + return a.Pool.Get(txHash) } -// GetPoolNonce implements ethapi.Backend. -// Subtle: this method shadows the method (apiBackend).GetPoolNonce of ethAPIBackend2.apiBackend. func (a *apiBackend) GetPoolNonce(ctx context.Context, addr common.Address) (uint64, error) { - panic(errUnimplemented) + return a.Pool.Nonce(addr), nil } -// Stats implements ethapi.Backend. -// Subtle: this method shadows the method (apiBackend).Stats of ethAPIBackend2.apiBackend. func (a *apiBackend) Stats() (pending int, queued int) { - panic(errUnimplemented) + return a.Pool.Stats() } -// TxPoolContent implements ethapi.Backend. -// Subtle: this method shadows the method (apiBackend).TxPoolContent of ethAPIBackend2.apiBackend. func (a *apiBackend) TxPoolContent() (map[common.Address][]*types.Transaction, map[common.Address][]*types.Transaction) { - panic(errUnimplemented) + return a.Pool.Content() } -// TxPoolContentFrom implements ethapi.Backend. -// Subtle: this method shadows the method (apiBackend).TxPoolContentFrom of ethAPIBackend2.apiBackend. func (a *apiBackend) TxPoolContentFrom(addr common.Address) ([]*types.Transaction, []*types.Transaction) { - panic(errUnimplemented) + return a.Pool.ContentFrom(addr) } -// SubscribeNewTxsEvent implements ethapi.Backend. -// Subtle: this method shadows the method (apiBackend).SubscribeNewTxsEvent of ethAPIBackend2.apiBackend. func (a *apiBackend) SubscribeNewTxsEvent(chan<- core.NewTxsEvent) event.Subscription { panic(errUnimplemented) } @@ -430,60 +396,42 @@ func (a *apiBackend) ChainConfig() *params.ChainConfig { return a.vm.exec.ChainConfig() } -// Engine implements ethapi.Backend. -// Subtle: this method shadows the method (apiBackend).Engine of ethAPIBackend2.apiBackend. func (a *apiBackend) Engine() consensus.Engine { panic(errUnimplemented) } -// GetBody implements ethapi.Backend. -// Subtle: this method shadows the method (apiBackend).GetBody of ethAPIBackend2.apiBackend. func (a *apiBackend) GetBody(ctx context.Context, hash common.Hash, number rpc.BlockNumber) (*types.Body, error) { panic(errUnimplemented) } -// GetLogs implements ethapi.Backend. -// Subtle: this method shadows the method (apiBackend).GetLogs of ethAPIBackend2.apiBackend. func (a *apiBackend) GetLogs(ctx context.Context, blockHash common.Hash, number uint64) ([][]*types.Log, error) { panic(errUnimplemented) } -// SubscribeRemovedLogsEvent implements ethapi.Backend. -// Subtle: this method shadows the method (apiBackend).SubscribeRemovedLogsEvent of ethAPIBackend2.apiBackend. func (a *apiBackend) SubscribeRemovedLogsEvent(ch chan<- core.RemovedLogsEvent) event.Subscription { panic(errUnimplemented) } -// SubscribeLogsEvent implements ethapi.Backend. -// Subtle: this method shadows the method (apiBackend).SubscribeLogsEvent of ethAPIBackend2.apiBackend. func (a *apiBackend) SubscribeLogsEvent(ch chan<- []*types.Log) event.Subscription { panic(errUnimplemented) } -// SubscribePendingLogsEvent implements ethapi.Backend. -// Subtle: this method shadows the method (apiBackend).SubscribePendingLogsEvent of ethAPIBackend2.apiBackend. func (a *apiBackend) SubscribePendingLogsEvent(ch chan<- []*types.Log) event.Subscription { panic(errUnimplemented) } -// BloomStatus implements ethapi.Backend. -// Subtle: this method shadows the method (apiBackend).BloomStatus of ethAPIBackend2.apiBackend. func (a *apiBackend) BloomStatus() (uint64, uint64) { panic(errUnimplemented) } -// ServiceFilter implements ethapi.Backend. -// Subtle: this method shadows the method (apiBackend).ServiceFilter of ethAPIBackend2.apiBackend. func (a *apiBackend) ServiceFilter(ctx context.Context, session *bloombits.MatcherSession) { panic(errUnimplemented) } -// StateAtBlock implements tracers.Backend. func (a *apiBackend) StateAtBlock(ctx context.Context, block *types.Block, reexec uint64, base *state.StateDB, readOnly bool, preferDisk bool) (*state.StateDB, tracers.StateReleaseFunc, error) { panic(errUnimplemented) } -// StateAtTransaction implements tracers.Backend. func (a *apiBackend) StateAtTransaction(ctx context.Context, block *types.Block, txIndex int, reexec uint64) (*core.Message, vm.BlockContext, *state.StateDB, tracers.StateReleaseFunc, error) { panic(errUnimplemented) } From ec2463dc18d76097c17e1ae4b974dd92fd30cfe4 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Fri, 26 Dec 2025 18:38:35 -0500 Subject: [PATCH 04/35] Add additional interface check --- sae/rpc.go | 1 + 1 file changed, 1 insertion(+) diff --git a/sae/rpc.go b/sae/rpc.go index c7774e339..15f3053ea 100644 --- a/sae/rpc.go +++ b/sae/rpc.go @@ -211,6 +211,7 @@ func (s *netAPI) PeerCount() hexutil.Uint { } var ( + _ filters.Backend = (*apiBackend)(nil) _ ethapi.Backend = (*apiBackend)(nil) _ tracers.Backend = (*apiBackend)(nil) ) From e23cad2cc147eeac2e2b661d1ba7cec231d7b6d4 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Fri, 26 Dec 2025 18:51:32 -0500 Subject: [PATCH 05/35] Add account manager --- sae/rpc.go | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/sae/rpc.go b/sae/rpc.go index 15f3053ea..73ec7513b 100644 --- a/sae/rpc.go +++ b/sae/rpc.go @@ -38,11 +38,15 @@ import ( ) func (vm *VM) ethRPCServer() (*rpc.Server, error) { + accountManager := accounts.NewManager(&accounts.Config{}) + vm.toClose = append(vm.toClose, accountManager.Close) + b := &apiBackend{ - vm: vm, - Set: vm.mempool, + Set: vm.mempool, + vm: vm, + accountManager: accountManager, } - filterSystem := filters.NewFilterSystem(b, filters.Config{Timeout: 5 * time.Minute}) + filterSystem := filters.NewFilterSystem(b, filters.Config{}) apis := []struct { namespace string api any @@ -218,7 +222,8 @@ var ( type apiBackend struct { *txgossip.Set - vm *VM + vm *VM + accountManager *accounts.Manager } func (a *apiBackend) SyncProgress() ethereum.SyncProgress { @@ -240,7 +245,7 @@ func (a *apiBackend) ChainDb() ethdb.Database { } func (a *apiBackend) AccountManager() *accounts.Manager { - panic(errUnimplemented) + return a.accountManager } func (a *apiBackend) ExtRPCEnabled() bool { @@ -295,7 +300,7 @@ func (a *apiBackend) CurrentHeader() *types.Header { } func (a *apiBackend) CurrentBlock() *types.Header { - return types.CopyHeader(a.vm.exec.LastExecuted().Header()) + return a.vm.exec.LastExecuted().Header() } func (a *apiBackend) BlockByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Block, error) { From e99e8cb029ea89c33a974e5a44bab49aaceaefa0 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Fri, 26 Dec 2025 18:53:34 -0500 Subject: [PATCH 06/35] Add comment --- sae/rpc.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sae/rpc.go b/sae/rpc.go index 73ec7513b..ca318d0f7 100644 --- a/sae/rpc.go +++ b/sae/rpc.go @@ -276,7 +276,7 @@ func (a *apiBackend) UnprotectedAllowed() bool { } func (a *apiBackend) SetHead(number uint64) { - // SAE does not support reorgs, so we ignore attempts to override the chain + // SAE does not support reorgs. We ignore any attempts to override the chain // head. a.vm.log().Warn("ignoring attempt to override the chain head", zap.Uint64("number", number), From c5ce239316cf649cabdb049a93bd23a24405fe68 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Fri, 26 Dec 2025 18:54:35 -0500 Subject: [PATCH 07/35] cleanup comment --- sae/rpc.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sae/rpc.go b/sae/rpc.go index ca318d0f7..40be7b68d 100644 --- a/sae/rpc.go +++ b/sae/rpc.go @@ -227,8 +227,8 @@ type apiBackend struct { } func (a *apiBackend) SyncProgress() ethereum.SyncProgress { - // Avalanchego does not expose APIs until after the node has fully synced, - // so we can just report that syncing is complete. + // Avalanchego does not expose APIs until after the node has fully synced. + // Just report that syncing is complete. return ethereum.SyncProgress{} } From f8f5b89d826469bc158dbc49d819e87f3df28d32 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Wed, 31 Dec 2025 13:32:15 -0500 Subject: [PATCH 08/35] Working with e2e test --- sae/p2p.go | 69 ---------------- sae/vm.go | 157 ++++++++++++++++++++++++++++++++++++ sae/vm_test.go | 214 ++++++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 369 insertions(+), 71 deletions(-) delete mode 100644 sae/p2p.go diff --git a/sae/p2p.go b/sae/p2p.go deleted file mode 100644 index 2e5032ac9..000000000 --- a/sae/p2p.go +++ /dev/null @@ -1,69 +0,0 @@ -// Copyright (C) 2025, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package sae - -import ( - "context" - "time" - - "github.com/ava-labs/avalanchego/ids" - snowcommon "github.com/ava-labs/avalanchego/snow/engine/common" - "github.com/ava-labs/avalanchego/version" -) - -// Connected notifies the VM that a p2p connection has been established with the -// specified node. -func (vm *VM) Connected( - ctx context.Context, - nodeID ids.NodeID, - nodeVersion *version.Application, -) error { - return errUnimplemented -} - -// Disconnected notifies the VM that the p2p connection with the specified node -// has terminated. -func (vm *VM) Disconnected(ctx context.Context, nodeID ids.NodeID) error { - return errUnimplemented -} - -// AppRequest notifies the VM of an incoming request from the specified node. -func (vm *VM) AppRequest( - ctx context.Context, - nodeID ids.NodeID, - requestID uint32, - deadline time.Time, - request []byte, -) error { - return errUnimplemented -} - -// AppResponse notifies the VM of an incoming response from the specified node. -func (vm *VM) AppResponse( - ctx context.Context, - nodeID ids.NodeID, - requestID uint32, - response []byte, -) error { - return errUnimplemented -} - -// AppRequestFailed notifies the VM that an outgoing request failed. -func (vm *VM) AppRequestFailed( - ctx context.Context, - nodeID ids.NodeID, - requestID uint32, - appErr *snowcommon.AppError, -) error { - return errUnimplemented -} - -// AppGossip notifies the VM of gossip from the specified node. -func (vm *VM) AppGossip( - ctx context.Context, - nodeID ids.NodeID, - msg []byte, -) error { - return errUnimplemented -} diff --git a/sae/vm.go b/sae/vm.go index 24d2c3528..7bde9af6a 100644 --- a/sae/vm.go +++ b/sae/vm.go @@ -7,15 +7,19 @@ import ( "context" "errors" "fmt" + "sync" "sync/atomic" "time" + "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/avalanchego/network/p2p" "github.com/ava-labs/avalanchego/network/p2p/gossip" "github.com/ava-labs/avalanchego/snow" snowcommon "github.com/ava-labs/avalanchego/snow/engine/common" "github.com/ava-labs/avalanchego/utils" "github.com/ava-labs/avalanchego/utils/bloom" "github.com/ava-labs/avalanchego/utils/logging" + "github.com/ava-labs/avalanchego/utils/units" "github.com/ava-labs/libevm/common" "github.com/ava-labs/libevm/core" "github.com/ava-labs/libevm/core/rawdb" @@ -37,6 +41,8 @@ import ( // which needs to be provided by a harness. In all cases, the harness MUST // provide a last-synchronous block, which MAY be the genesis. type VM struct { + *p2p.Network + config Config snowCtx *snow.Context hooks hook.Points @@ -87,6 +93,7 @@ func (vm *VM) Init( db ethdb.Database, triedbConfig *triedb.Config, lastSynchronous *blocks.Block, + sender snowcommon.AppSender, ) error { vm.snowCtx = snowCtx vm.hooks = hooks @@ -155,6 +162,156 @@ func (vm *VM) Init( vm.signalNewTxsToEngine() } + { // ========== P2P Gossip ========== + const maxValidatorSetStaleness = time.Minute + var ( + peers p2p.Peers + validatorPeers = p2p.NewValidators( + snowCtx.Log, + snowCtx.SubnetID, + snowCtx.ValidatorState, + maxValidatorSetStaleness, + ) + ) + network, err := p2p.NewNetwork( + snowCtx.Log, + sender, + vm.metrics, + "p2p", + &peers, + validatorPeers, + ) + if err != nil { + return err + } + + metrics, err := gossip.NewMetrics(vm.metrics, "gossip") + if err != nil { + return err + } + + const targetMessageSize = 20 * units.KiB + handler := gossip.NewHandler( + snowCtx.Log, + txgossip.Marshaller{}, + vm.mempool, + metrics, + targetMessageSize, + ) + + const ( + throttlingPeriod = time.Hour // seconds/period + requestPeriod = time.Second // seconds/request + requestsPerPeerPerPeriod = float64(throttlingPeriod / requestPeriod) // requests/period + ) + throttledHandler, err := p2p.NewDynamicThrottlerHandler( + snowCtx.Log, + handler, + validatorPeers, + throttlingPeriod, + requestsPerPeerPerPeriod, + vm.metrics, + "gossip", + ) + if err != nil { + return err + } + validatorOnlyHandler := p2p.NewValidatorHandler( + throttledHandler, + validatorPeers, + snowCtx.Log, + ) + + // Pull requests are filtered by validators and are throttled to prevent + // spamming. Push messages are not filtered. + type ( + appRequester interface { + AppRequest(context.Context, ids.NodeID, time.Time, []byte) ([]byte, *snowcommon.AppError) + } + appGossiper interface { + AppGossip(context.Context, ids.NodeID, []byte) + } + ) + gossipHandler := struct { + appRequester + appGossiper + }{ + appRequester: validatorOnlyHandler, + appGossiper: handler, + } + if err := network.AddHandler(p2p.TxGossipHandlerID, gossipHandler); err != nil { + return err + } + + client := network.NewClient(p2p.TxGossipHandlerID, validatorPeers) + const pollSize = 1 + pullGossiper := gossip.NewPullGossiper[txgossip.Transaction]( + vm.snowCtx.Log, + txgossip.Marshaller{}, + vm.mempool, + client, + metrics, + pollSize, + ) + pullGossiperWhenValidator := &gossip.ValidatorGossiper{ + Gossiper: pullGossiper, + NodeID: snowCtx.NodeID, + Validators: validatorPeers, + } + + pushGossipParams := gossip.BranchingFactor{ + StakePercentage: .9, + Validators: 100, + } + pushRegossipParams := gossip.BranchingFactor{ + Validators: 10, + } + const ( + discardedCacheSize = 16_384 + regossipPeriod = 30 * time.Second + ) + pushGossiper, err := gossip.NewPushGossiper( + txgossip.Marshaller{}, + vm.mempool, + validatorPeers, + client, + metrics, + pushGossipParams, + pushRegossipParams, + discardedCacheSize, + targetMessageSize, + regossipPeriod, + ) + if err != nil { + return err + } + + vm.mempool.RegisterPushGossiper(pushGossiper) + + var ( + gossipCtx, cancel = context.WithCancel(context.Background()) + wg sync.WaitGroup + ) + wg.Add(2) + go func() { + defer wg.Done() + gossip.Every(gossipCtx, snowCtx.Log, pullGossiperWhenValidator, requestPeriod) + }() + + const pushPeriod = 100 * time.Millisecond + go func() { + defer wg.Done() + gossip.Every(gossipCtx, snowCtx.Log, pushGossiper, pushPeriod) + }() + + vm.Network = network + vm.toClose = append(vm.toClose, func() error { + cancel() + wg.Wait() + return nil + }) + } + return nil } diff --git a/sae/vm_test.go b/sae/vm_test.go index eb8809e36..9b53aaff7 100644 --- a/sae/vm_test.go +++ b/sae/vm_test.go @@ -5,10 +5,12 @@ package sae import ( "context" + "maps" "math/big" "math/rand/v2" "net/http/httptest" "runtime" + "slices" "testing" "time" @@ -16,10 +18,15 @@ import ( "github.com/ava-labs/avalanchego/snow" "github.com/ava-labs/avalanchego/snow/consensus/snowman" snowcommon "github.com/ava-labs/avalanchego/snow/engine/common" + "github.com/ava-labs/avalanchego/snow/engine/enginetest" "github.com/ava-labs/avalanchego/snow/engine/snowman/block" "github.com/ava-labs/avalanchego/snow/snowtest" + "github.com/ava-labs/avalanchego/snow/validators" + "github.com/ava-labs/avalanchego/snow/validators/validatorstest" "github.com/ava-labs/avalanchego/utils/logging" "github.com/ava-labs/avalanchego/utils/set" + "github.com/ava-labs/avalanchego/utils/timer/mockable" + "github.com/ava-labs/avalanchego/version" "github.com/ava-labs/libevm/common" "github.com/ava-labs/libevm/core/rawdb" "github.com/ava-labs/libevm/core/state" @@ -67,6 +74,9 @@ type SUT struct { genesis *blocks.Block wallet *saetest.Wallet db ethdb.Database + + validators *validatorstest.State + sender *enginetest.Sender } type ( @@ -78,6 +88,8 @@ type ( sutOption = options.Option[sutConfig] ) +var chainID = ids.GenerateTestID() + func newSUT(tb testing.TB, numAccounts uint, opts ...sutOption) (context.Context, *SUT) { tb.Helper() @@ -112,11 +124,18 @@ func newSUT(tb testing.TB, numAccounts uint, opts ...sutOption) (context.Context logger := saetest.NewTBLogger(tb, logging.Warn) ctx := logger.CancelOnError(tb.Context()) - snowCtx := snowtest.Context(tb, ids.GenerateTestID()) + + snowCtx := snowtest.Context(tb, chainID) + validators := snowCtx.ValidatorState.(*validatorstest.State) + validators.GetCurrentHeightF = func(context.Context) (uint64, error) { + return 0, nil + } snowCtx.Log = logger + sender := &enginetest.Sender{} + // TODO(arr4n) change this to use [SinceGenesis.Initialize] via the `snow` variable. - require.NoError(tb, vm.Init(snowCtx, conf.hooks, config, db, &triedb.Config{}, genesis), "Init()") + require.NoError(tb, vm.Init(snowCtx, conf.hooks, config, db, &triedb.Config{}, genesis, sender), "Init()") _ = snow.Initialize handlers, err := snow.CreateHandlers(ctx) @@ -134,6 +153,9 @@ func newSUT(tb testing.TB, numAccounts uint, opts ...sutOption) (context.Context genesis: genesis, wallet: wallet, db: db, + + validators: validators, + sender: sender, } } @@ -515,3 +537,191 @@ func TestSemanticBlockChecks(t *testing.T) { }) } } + +type network struct { + validators []*SUT + nonValidators []*SUT +} + +func newNetwork(tb testing.TB, numValidators, numNonValidators int) *network { + tb.Helper() + + validatorNodes := make(map[ids.NodeID]*SUT, numValidators) + for range numValidators { + _, sut := newSUT(tb, 1) + validatorNodes[sut.rawVM.snowCtx.NodeID] = sut + } + + validatorSet := make(map[ids.NodeID]*validators.GetValidatorOutput) + for _, sut := range validatorNodes { + nodeID := sut.rawVM.snowCtx.NodeID + validatorSet[nodeID] = &validators.GetValidatorOutput{ + NodeID: nodeID, + PublicKey: sut.rawVM.snowCtx.PublicKey, + Weight: 1, + } + } + getValidatorSetF := func(context.Context, uint64, ids.ID) (map[ids.NodeID]*validators.GetValidatorOutput, error) { + return validatorSet, nil + } + + nonValidatorNodes := make(map[ids.NodeID]*SUT, numNonValidators) + for range numNonValidators { + _, sut := newSUT(tb, 1) + nonValidatorNodes[sut.rawVM.snowCtx.NodeID] = sut + } + allNodes := make(map[ids.NodeID]*SUT, numValidators+numNonValidators) + maps.Copy(allNodes, validatorNodes) + maps.Copy(allNodes, nonValidatorNodes) + for _, sut := range allNodes { + sut.validators.GetValidatorSetF = getValidatorSetF + } + + // Connect all nodes to every validator. + for nodeID, sut := range allNodes { + peers := make(map[ids.NodeID]*SUT, len(allNodes)) + peers[nodeID] = sut // self-registration + maps.Copy(peers, validatorNodes) + + // Non-validators do not connect to other non-validators. + var nonValidatorPeers map[ids.NodeID]*SUT + if _, isValidator := validatorNodes[nodeID]; isValidator { + nonValidatorPeers = nonValidatorNodes + } + maps.Copy(peers, nonValidatorPeers) + + sender := sut.sender + sender.SendAppRequestF = func(ctx context.Context, s set.Set[ids.NodeID], r uint32, b []byte) error { + go func() { + tb.Helper() + + for peerID := range s { + if peer, ok := peers[peerID]; ok { + assert.NoError(tb, peer.AppRequest(ctx, nodeID, r, mockable.MaxTime, b)) + } else { + assert.NoError(tb, sut.AppRequestFailed(ctx, peerID, r, snowcommon.ErrTimeout)) + } + } + }() + return nil + } + sender.SendAppResponseF = func(ctx context.Context, peerID ids.NodeID, r uint32, b []byte) error { + go func() { + tb.Helper() + + assert.Contains(tb, peers, peerID, "unknown peer in SendAppResponse") + peer, ok := peers[peerID] + if !ok { + return + } + + assert.NoError(tb, peer.AppResponse(ctx, nodeID, r, b)) + }() + + return nil + } + sender.SendAppErrorF = func(ctx context.Context, peerID ids.NodeID, r uint32, code int32, msg string) error { + go func() { + tb.Helper() + assert.Contains(tb, peers, peerID, "unknown peer in SendAppError") + peer, ok := peers[peerID] + if !ok { + return + } + + appErr := &snowcommon.AppError{ + Code: code, + Message: msg, + } + assert.NoError(tb, peer.AppRequestFailed(ctx, nodeID, r, appErr)) + }() + return nil + } + sender.SendAppGossipF = func(ctx context.Context, c snowcommon.SendConfig, b []byte) error { + go func() { + tb.Helper() + + for peerID := range c.NodeIDs { + if peer, ok := peers[peerID]; ok { + assert.NoError(tb, peer.AppGossip(ctx, nodeID, b)) + } + } + var sent set.Set[ids.NodeID] + sent.Union(c.NodeIDs) + + send := func(peers map[ids.NodeID]*SUT, count int) error { + for peerID := range peers { + if count <= 0 { + break + } + if sent.Contains(peerID) { + continue + } + peer, ok := peers[peerID] + if !ok { + continue + } + if err := peer.AppGossip(ctx, nodeID, b); err != nil { + return err + } + + sent.Add(peerID) + count-- + } + return nil + } + + assert.NoError(tb, send(validatorNodes, c.Validators)) + assert.NoError(tb, send(nonValidatorPeers, c.NonValidators)) + assert.NoError(tb, send(peers, c.Peers)) + }() + return nil + } + + // Connect all the peers _after_ setting up the sender functions. + defer func() { + tb.Helper() + for peerID := range peers { + require.NoErrorf(tb, sut.Connected(tb.Context(), peerID, version.Current), "Connected(%s)", peerID) + } + }() + } + + return &network{ + validators: slices.Collect(maps.Values(validatorNodes)), + nonValidators: slices.Collect(maps.Values(nonValidatorNodes)), + } +} + +func requireReceiveTx(tb testing.TB, nodes []*SUT, txHash common.Hash) { + tb.Helper() + for _, sut := range nodes { + assert.Eventuallyf(tb, func() bool { + return sut.rawVM.mempool.Has(ids.ID(txHash)) + }, 5*time.Second, 100*time.Millisecond, "tx %x not gossiped to node %s", txHash, sut.rawVM.snowCtx.NodeID) + } + require.False(tb, tb.Failed()) +} + +func requireNotReceiveTx(tb testing.TB, nodes []*SUT, txHash common.Hash) { + tb.Helper() + for _, sut := range nodes { + assert.False(tb, sut.rawVM.mempool.Has(ids.ID(txHash)), "tx %x was gossiped to node %s", txHash, sut.rawVM.snowCtx.NodeID) + } + require.False(tb, tb.Failed()) +} + +func TestGossip(t *testing.T) { + n := newNetwork(t, 2, 2) + + api := n.nonValidators[0] + tx := api.wallet.SetNonceAndSign(t, 0, &types.DynamicFeeTx{ + To: &common.Address{}, + Gas: params.TxGas, + GasFeeCap: big.NewInt(1), + Value: big.NewInt(1), + }) + api.mustSendTx(t, tx) + requireReceiveTx(t, n.validators, tx.Hash()) + requireNotReceiveTx(t, n.nonValidators[1:], tx.Hash()) +} From a46765ffdd33a012957ea65f9fa34f38f6e52b0b Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Wed, 31 Dec 2025 13:34:49 -0500 Subject: [PATCH 09/35] comment newNetwork --- sae/vm_test.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/sae/vm_test.go b/sae/vm_test.go index 9b53aaff7..172342101 100644 --- a/sae/vm_test.go +++ b/sae/vm_test.go @@ -543,6 +543,13 @@ type network struct { nonValidators []*SUT } +// newNetwork creates a network of SUTs with the specified number of validators +// and non-validators. +// +// Like in production, all nodes should be connected to all validators and mark +// themselves as connected. While non-validators can connect to other +// non-validators, they do not generally attempt to do so in practice, so this +// function does not connect them to each other. func newNetwork(tb testing.TB, numValidators, numNonValidators int) *network { tb.Helper() From 65e2a74e2e4973d7064939ebddd245b8fa2cf8ee Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Wed, 31 Dec 2025 13:44:09 -0500 Subject: [PATCH 10/35] Use shared constants --- sae/vm.go | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/sae/vm.go b/sae/vm.go index 7bde9af6a..baba66044 100644 --- a/sae/vm.go +++ b/sae/vm.go @@ -173,11 +173,12 @@ func (vm *VM) Init( maxValidatorSetStaleness, ) ) + const p2pNamespace = "p2p" network, err := p2p.NewNetwork( snowCtx.Log, sender, vm.metrics, - "p2p", + p2pNamespace, &peers, validatorPeers, ) @@ -185,7 +186,8 @@ func (vm *VM) Init( return err } - metrics, err := gossip.NewMetrics(vm.metrics, "gossip") + const gossipNamespace = "gossip" + metrics, err := gossip.NewMetrics(vm.metrics, gossipNamespace) if err != nil { return err } @@ -211,7 +213,7 @@ func (vm *VM) Init( throttlingPeriod, requestsPerPeerPerPeriod, vm.metrics, - "gossip", + gossipNamespace, ) if err != nil { return err From a7e7ad79d8cafe2855af71b0bd8e94bfc0fedb36 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Wed, 31 Dec 2025 13:55:14 -0500 Subject: [PATCH 11/35] Simplify vm.Init --- sae/p2p.go | 160 +++++++++++++++++++++++++++++++++++++++++++++++++ sae/vm.go | 132 ++-------------------------------------- sae/vm_test.go | 8 +-- 3 files changed, 168 insertions(+), 132 deletions(-) create mode 100644 sae/p2p.go diff --git a/sae/p2p.go b/sae/p2p.go new file mode 100644 index 000000000..ab76c01fa --- /dev/null +++ b/sae/p2p.go @@ -0,0 +1,160 @@ +// Copyright (C) 2025, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package sae + +import ( + "context" + "time" + + "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/avalanchego/network/p2p" + "github.com/ava-labs/avalanchego/network/p2p/gossip" + "github.com/ava-labs/avalanchego/snow" + snowcommon "github.com/ava-labs/avalanchego/snow/engine/common" + "github.com/ava-labs/avalanchego/utils/units" + "github.com/ava-labs/strevm/txgossip" + "github.com/prometheus/client_golang/prometheus" +) + +const ( + pullRequestPeriod = time.Second // seconds/request + pushGossipPeriod = 100 * time.Millisecond +) + +func newNetwork( + snowCtx *snow.Context, + sender snowcommon.AppSender, + reg *prometheus.Registry, + mempool *txgossip.Set, +) ( + *p2p.Network, + *gossip.ValidatorGossiper, + *gossip.PushGossiper[txgossip.Transaction], + error, +) { + const maxValidatorSetStaleness = time.Minute + var ( + peers p2p.Peers + validatorPeers = p2p.NewValidators( + snowCtx.Log, + snowCtx.SubnetID, + snowCtx.ValidatorState, + maxValidatorSetStaleness, + ) + ) + const p2pNamespace = "p2p" + network, err := p2p.NewNetwork( + snowCtx.Log, + sender, + reg, + p2pNamespace, + &peers, + validatorPeers, + ) + if err != nil { + return nil, nil, nil, err + } + + const gossipNamespace = "gossip" + metrics, err := gossip.NewMetrics(reg, gossipNamespace) + if err != nil { + return nil, nil, nil, err + } + + const targetMessageSize = 20 * units.KiB + handler := gossip.NewHandler( + snowCtx.Log, + txgossip.Marshaller{}, + mempool, + metrics, + targetMessageSize, + ) + + const ( + throttlingPeriod = time.Hour // seconds/period + requestsPerPeerPerPeriod = float64(throttlingPeriod / pullRequestPeriod) // requests/period + ) + throttledHandler, err := p2p.NewDynamicThrottlerHandler( + snowCtx.Log, + handler, + validatorPeers, + throttlingPeriod, + requestsPerPeerPerPeriod, + reg, + gossipNamespace, + ) + if err != nil { + return nil, nil, nil, err + } + validatorOnlyHandler := p2p.NewValidatorHandler( + throttledHandler, + validatorPeers, + snowCtx.Log, + ) + + // Pull requests are filtered by validators and are throttled to prevent + // spamming. Push messages are not filtered. + type ( + appRequester interface { + AppRequest(context.Context, ids.NodeID, time.Time, []byte) ([]byte, *snowcommon.AppError) + } + appGossiper interface { + AppGossip(context.Context, ids.NodeID, []byte) + } + ) + gossipHandler := struct { + appRequester + appGossiper + }{ + appRequester: validatorOnlyHandler, + appGossiper: handler, + } + if err := network.AddHandler(p2p.TxGossipHandlerID, gossipHandler); err != nil { + return nil, nil, nil, err + } + + client := network.NewClient(p2p.TxGossipHandlerID, validatorPeers) + const pollSize = 1 + pullGossiper := gossip.NewPullGossiper[txgossip.Transaction]( + snowCtx.Log, + txgossip.Marshaller{}, + mempool, + client, + metrics, + pollSize, + ) + pullGossiperWhenValidator := &gossip.ValidatorGossiper{ + Gossiper: pullGossiper, + NodeID: snowCtx.NodeID, + Validators: validatorPeers, + } + + pushGossipParams := gossip.BranchingFactor{ + StakePercentage: .9, + Validators: 100, + } + pushRegossipParams := gossip.BranchingFactor{ + Validators: 10, + } + const ( + discardedCacheSize = 16_384 + regossipPeriod = 30 * time.Second + ) + pushGossiper, err := gossip.NewPushGossiper( + txgossip.Marshaller{}, + mempool, + validatorPeers, + client, + metrics, + pushGossipParams, + pushRegossipParams, + discardedCacheSize, + targetMessageSize, + regossipPeriod, + ) + if err != nil { + return nil, nil, nil, err + } + return network, pullGossiperWhenValidator, pushGossiper, nil +} diff --git a/sae/vm.go b/sae/vm.go index baba66044..2d5a25572 100644 --- a/sae/vm.go +++ b/sae/vm.go @@ -11,7 +11,6 @@ import ( "sync/atomic" "time" - "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/network/p2p" "github.com/ava-labs/avalanchego/network/p2p/gossip" "github.com/ava-labs/avalanchego/snow" @@ -19,7 +18,6 @@ import ( "github.com/ava-labs/avalanchego/utils" "github.com/ava-labs/avalanchego/utils/bloom" "github.com/ava-labs/avalanchego/utils/logging" - "github.com/ava-labs/avalanchego/utils/units" "github.com/ava-labs/libevm/common" "github.com/ava-labs/libevm/core" "github.com/ava-labs/libevm/core/rawdb" @@ -163,129 +161,9 @@ func (vm *VM) Init( } { // ========== P2P Gossip ========== - const maxValidatorSetStaleness = time.Minute - var ( - peers p2p.Peers - validatorPeers = p2p.NewValidators( - snowCtx.Log, - snowCtx.SubnetID, - snowCtx.ValidatorState, - maxValidatorSetStaleness, - ) - ) - const p2pNamespace = "p2p" - network, err := p2p.NewNetwork( - snowCtx.Log, - sender, - vm.metrics, - p2pNamespace, - &peers, - validatorPeers, - ) - if err != nil { - return err - } - - const gossipNamespace = "gossip" - metrics, err := gossip.NewMetrics(vm.metrics, gossipNamespace) - if err != nil { - return err - } - - const targetMessageSize = 20 * units.KiB - handler := gossip.NewHandler( - snowCtx.Log, - txgossip.Marshaller{}, - vm.mempool, - metrics, - targetMessageSize, - ) - - const ( - throttlingPeriod = time.Hour // seconds/period - requestPeriod = time.Second // seconds/request - requestsPerPeerPerPeriod = float64(throttlingPeriod / requestPeriod) // requests/period - ) - throttledHandler, err := p2p.NewDynamicThrottlerHandler( - snowCtx.Log, - handler, - validatorPeers, - throttlingPeriod, - requestsPerPeerPerPeriod, - vm.metrics, - gossipNamespace, - ) - if err != nil { - return err - } - validatorOnlyHandler := p2p.NewValidatorHandler( - throttledHandler, - validatorPeers, - snowCtx.Log, - ) - - // Pull requests are filtered by validators and are throttled to prevent - // spamming. Push messages are not filtered. - type ( - appRequester interface { - AppRequest(context.Context, ids.NodeID, time.Time, []byte) ([]byte, *snowcommon.AppError) - } - appGossiper interface { - AppGossip(context.Context, ids.NodeID, []byte) - } - ) - gossipHandler := struct { - appRequester - appGossiper - }{ - appRequester: validatorOnlyHandler, - appGossiper: handler, - } - if err := network.AddHandler(p2p.TxGossipHandlerID, gossipHandler); err != nil { - return err - } - - client := network.NewClient(p2p.TxGossipHandlerID, validatorPeers) - const pollSize = 1 - pullGossiper := gossip.NewPullGossiper[txgossip.Transaction]( - vm.snowCtx.Log, - txgossip.Marshaller{}, - vm.mempool, - client, - metrics, - pollSize, - ) - pullGossiperWhenValidator := &gossip.ValidatorGossiper{ - Gossiper: pullGossiper, - NodeID: snowCtx.NodeID, - Validators: validatorPeers, - } - - pushGossipParams := gossip.BranchingFactor{ - StakePercentage: .9, - Validators: 100, - } - pushRegossipParams := gossip.BranchingFactor{ - Validators: 10, - } - const ( - discardedCacheSize = 16_384 - regossipPeriod = 30 * time.Second - ) - pushGossiper, err := gossip.NewPushGossiper( - txgossip.Marshaller{}, - vm.mempool, - validatorPeers, - client, - metrics, - pushGossipParams, - pushRegossipParams, - discardedCacheSize, - targetMessageSize, - regossipPeriod, - ) + network, pullGossiper, pushGossiper, err := newNetwork(snowCtx, sender, vm.metrics, vm.mempool) if err != nil { - return err + return fmt.Errorf("newNetwork(...): %v", err) } vm.mempool.RegisterPushGossiper(pushGossiper) @@ -297,13 +175,11 @@ func (vm *VM) Init( wg.Add(2) go func() { defer wg.Done() - gossip.Every(gossipCtx, snowCtx.Log, pullGossiperWhenValidator, requestPeriod) + gossip.Every(gossipCtx, snowCtx.Log, pullGossiper, pullRequestPeriod) }() - - const pushPeriod = 100 * time.Millisecond go func() { defer wg.Done() - gossip.Every(gossipCtx, snowCtx.Log, pushGossiper, pushPeriod) + gossip.Every(gossipCtx, snowCtx.Log, pushGossiper, pushGossipPeriod) }() vm.Network = network diff --git a/sae/vm_test.go b/sae/vm_test.go index 172342101..f75eca21e 100644 --- a/sae/vm_test.go +++ b/sae/vm_test.go @@ -538,7 +538,7 @@ func TestSemanticBlockChecks(t *testing.T) { } } -type network struct { +type networkSUT struct { validators []*SUT nonValidators []*SUT } @@ -550,7 +550,7 @@ type network struct { // themselves as connected. While non-validators can connect to other // non-validators, they do not generally attempt to do so in practice, so this // function does not connect them to each other. -func newNetwork(tb testing.TB, numValidators, numNonValidators int) *network { +func newNetworkSUT(tb testing.TB, numValidators, numNonValidators int) *networkSUT { tb.Helper() validatorNodes := make(map[ids.NodeID]*SUT, numValidators) @@ -694,7 +694,7 @@ func newNetwork(tb testing.TB, numValidators, numNonValidators int) *network { }() } - return &network{ + return &networkSUT{ validators: slices.Collect(maps.Values(validatorNodes)), nonValidators: slices.Collect(maps.Values(nonValidatorNodes)), } @@ -719,7 +719,7 @@ func requireNotReceiveTx(tb testing.TB, nodes []*SUT, txHash common.Hash) { } func TestGossip(t *testing.T) { - n := newNetwork(t, 2, 2) + n := newNetworkSUT(t, 2, 2) api := n.nonValidators[0] tx := api.wallet.SetNonceAndSign(t, 0, &types.DynamicFeeTx{ From dc8f38c6538db22c45599837b3851280d01a7f9d Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Wed, 31 Dec 2025 14:11:49 -0500 Subject: [PATCH 12/35] Split the network and gossiper construction --- sae/p2p.go | 29 +++++++++++++++++++---------- sae/vm.go | 9 ++++++++- 2 files changed, 27 insertions(+), 11 deletions(-) diff --git a/sae/p2p.go b/sae/p2p.go index ab76c01fa..0f294faf8 100644 --- a/sae/p2p.go +++ b/sae/p2p.go @@ -22,20 +22,18 @@ const ( pushGossipPeriod = 100 * time.Millisecond ) +// newNetwork creates the P2P network with a registered validator set. func newNetwork( snowCtx *snow.Context, sender snowcommon.AppSender, reg *prometheus.Registry, - mempool *txgossip.Set, ) ( *p2p.Network, - *gossip.ValidatorGossiper, - *gossip.PushGossiper[txgossip.Transaction], + *p2p.Validators, error, ) { const maxValidatorSetStaleness = time.Minute var ( - peers p2p.Peers validatorPeers = p2p.NewValidators( snowCtx.Log, snowCtx.SubnetID, @@ -49,13 +47,27 @@ func newNetwork( sender, reg, p2pNamespace, - &peers, validatorPeers, ) if err != nil { - return nil, nil, nil, err + return nil, nil, err } + return network, validatorPeers, nil +} +// newGossipers creates the tx gossipers for mempool dissemination. +func newGossipers( + snowCtx *snow.Context, + network *p2p.Network, + validatorPeers *p2p.Validators, + reg *prometheus.Registry, + mempool *txgossip.Set, +) ( + p2p.Handler, + *gossip.ValidatorGossiper, + *gossip.PushGossiper[txgossip.Transaction], + error, +) { const gossipNamespace = "gossip" metrics, err := gossip.NewMetrics(reg, gossipNamespace) if err != nil { @@ -110,9 +122,6 @@ func newNetwork( appRequester: validatorOnlyHandler, appGossiper: handler, } - if err := network.AddHandler(p2p.TxGossipHandlerID, gossipHandler); err != nil { - return nil, nil, nil, err - } client := network.NewClient(p2p.TxGossipHandlerID, validatorPeers) const pollSize = 1 @@ -156,5 +165,5 @@ func newNetwork( if err != nil { return nil, nil, nil, err } - return network, pullGossiperWhenValidator, pushGossiper, nil + return gossipHandler, pullGossiperWhenValidator, pushGossiper, nil } diff --git a/sae/vm.go b/sae/vm.go index 2d5a25572..8cba5efd8 100644 --- a/sae/vm.go +++ b/sae/vm.go @@ -161,10 +161,17 @@ func (vm *VM) Init( } { // ========== P2P Gossip ========== - network, pullGossiper, pushGossiper, err := newNetwork(snowCtx, sender, vm.metrics, vm.mempool) + network, validatorPeers, err := newNetwork(snowCtx, sender, vm.metrics) if err != nil { return fmt.Errorf("newNetwork(...): %v", err) } + handler, pullGossiper, pushGossiper, err := newGossipers(snowCtx, network, validatorPeers, vm.metrics, vm.mempool) + if err != nil { + return fmt.Errorf("newGossipers(...): %v", err) + } + if err := network.AddHandler(p2p.TxGossipHandlerID, handler); err != nil { + return fmt.Errorf("network.AddHandler(...): %v", err) + } vm.mempool.RegisterPushGossiper(pushGossiper) From c779c8ec88ab41a6d8eb05fa0cbf12088e52b3c0 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Wed, 31 Dec 2025 14:15:38 -0500 Subject: [PATCH 13/35] nits --- sae/p2p.go | 15 +++++++-------- sae/vm.go | 2 +- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/sae/p2p.go b/sae/p2p.go index 0f294faf8..808d7a456 100644 --- a/sae/p2p.go +++ b/sae/p2p.go @@ -18,6 +18,7 @@ import ( ) const ( + gossipHandlerID = p2p.TxGossipHandlerID pullRequestPeriod = time.Second // seconds/request pushGossipPeriod = 100 * time.Millisecond ) @@ -33,13 +34,11 @@ func newNetwork( error, ) { const maxValidatorSetStaleness = time.Minute - var ( - validatorPeers = p2p.NewValidators( - snowCtx.Log, - snowCtx.SubnetID, - snowCtx.ValidatorState, - maxValidatorSetStaleness, - ) + validatorPeers := p2p.NewValidators( + snowCtx.Log, + snowCtx.SubnetID, + snowCtx.ValidatorState, + maxValidatorSetStaleness, ) const p2pNamespace = "p2p" network, err := p2p.NewNetwork( @@ -123,7 +122,7 @@ func newGossipers( appGossiper: handler, } - client := network.NewClient(p2p.TxGossipHandlerID, validatorPeers) + client := network.NewClient(gossipHandlerID, validatorPeers) const pollSize = 1 pullGossiper := gossip.NewPullGossiper[txgossip.Transaction]( snowCtx.Log, diff --git a/sae/vm.go b/sae/vm.go index 8cba5efd8..627845a74 100644 --- a/sae/vm.go +++ b/sae/vm.go @@ -169,7 +169,7 @@ func (vm *VM) Init( if err != nil { return fmt.Errorf("newGossipers(...): %v", err) } - if err := network.AddHandler(p2p.TxGossipHandlerID, handler); err != nil { + if err := network.AddHandler(gossipHandlerID, handler); err != nil { return fmt.Errorf("network.AddHandler(...): %v", err) } From b253805cd84f1b9e812626ee1357f48c45047028 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Wed, 31 Dec 2025 14:25:50 -0500 Subject: [PATCH 14/35] nit --- sae/vm.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/sae/vm.go b/sae/vm.go index 627845a74..e71341426 100644 --- a/sae/vm.go +++ b/sae/vm.go @@ -173,8 +173,6 @@ func (vm *VM) Init( return fmt.Errorf("network.AddHandler(...): %v", err) } - vm.mempool.RegisterPushGossiper(pushGossiper) - var ( gossipCtx, cancel = context.WithCancel(context.Background()) wg sync.WaitGroup @@ -190,6 +188,7 @@ func (vm *VM) Init( }() vm.Network = network + vm.mempool.RegisterPushGossiper(pushGossiper) vm.toClose = append(vm.toClose, func() error { cancel() wg.Wait() From baaecc6cc517b129b0aa309c6c9f3e773c8a9a5f Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Wed, 31 Dec 2025 14:32:40 -0500 Subject: [PATCH 15/35] nit --- sae/p2p.go | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/sae/p2p.go b/sae/p2p.go index 808d7a456..e9f4c4209 100644 --- a/sae/p2p.go +++ b/sae/p2p.go @@ -40,12 +40,12 @@ func newNetwork( snowCtx.ValidatorState, maxValidatorSetStaleness, ) - const p2pNamespace = "p2p" + const namespace = "p2p" network, err := p2p.NewNetwork( snowCtx.Log, sender, reg, - p2pNamespace, + namespace, validatorPeers, ) if err != nil { @@ -67,16 +67,17 @@ func newGossipers( *gossip.PushGossiper[txgossip.Transaction], error, ) { - const gossipNamespace = "gossip" - metrics, err := gossip.NewMetrics(reg, gossipNamespace) + const namespace = "gossip" + metrics, err := gossip.NewMetrics(reg, namespace) if err != nil { return nil, nil, nil, err } const targetMessageSize = 20 * units.KiB + var marshaller txgossip.Marshaller handler := gossip.NewHandler( snowCtx.Log, - txgossip.Marshaller{}, + marshaller, mempool, metrics, targetMessageSize, @@ -93,7 +94,7 @@ func newGossipers( throttlingPeriod, requestsPerPeerPerPeriod, reg, - gossipNamespace, + namespace, ) if err != nil { return nil, nil, nil, err @@ -126,7 +127,7 @@ func newGossipers( const pollSize = 1 pullGossiper := gossip.NewPullGossiper[txgossip.Transaction]( snowCtx.Log, - txgossip.Marshaller{}, + marshaller, mempool, client, metrics, @@ -150,7 +151,7 @@ func newGossipers( regossipPeriod = 30 * time.Second ) pushGossiper, err := gossip.NewPushGossiper( - txgossip.Marshaller{}, + marshaller, mempool, validatorPeers, client, From 0e5c98361f64c515f77cf824f6256664d1e46fce Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Wed, 31 Dec 2025 14:44:17 -0500 Subject: [PATCH 16/35] comment --- sae/vm_test.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/sae/vm_test.go b/sae/vm_test.go index f75eca21e..86c27799d 100644 --- a/sae/vm_test.go +++ b/sae/vm_test.go @@ -543,12 +543,12 @@ type networkSUT struct { nonValidators []*SUT } -// newNetwork creates a network of SUTs with the specified number of validators -// and non-validators. +// newNetworkSUT creates a network of SUTs with the specified number of +// validators and non-validators. // -// Like in production, all nodes should be connected to all validators and mark +// Like in production, all nodes are connected to all validators and mark // themselves as connected. While non-validators can connect to other -// non-validators, they do not generally attempt to do so in practice, so this +// non-validators, they do not generally attempt to do so in production, so this // function does not connect them to each other. func newNetworkSUT(tb testing.TB, numValidators, numNonValidators int) *networkSUT { tb.Helper() From 83a2d14f60f53a432da37a3603bd66ca824cbd7c Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Wed, 31 Dec 2025 14:52:50 -0500 Subject: [PATCH 17/35] Testing cleanup --- sae/vm_test.go | 45 +++++++++++++++++++++------------------------ 1 file changed, 21 insertions(+), 24 deletions(-) diff --git a/sae/vm_test.go b/sae/vm_test.go index 86c27799d..6758b8cd3 100644 --- a/sae/vm_test.go +++ b/sae/vm_test.go @@ -553,11 +553,20 @@ type networkSUT struct { func newNetworkSUT(tb testing.TB, numValidators, numNonValidators int) *networkSUT { tb.Helper() + const numAccounts = 1 validatorNodes := make(map[ids.NodeID]*SUT, numValidators) for range numValidators { - _, sut := newSUT(tb, 1) + _, sut := newSUT(tb, numAccounts) validatorNodes[sut.rawVM.snowCtx.NodeID] = sut } + nonValidatorNodes := make(map[ids.NodeID]*SUT, numNonValidators) + for range numNonValidators { + _, sut := newSUT(tb, numAccounts) + nonValidatorNodes[sut.rawVM.snowCtx.NodeID] = sut + } + allNodes := make(map[ids.NodeID]*SUT, numValidators+numNonValidators) + maps.Copy(allNodes, validatorNodes) + maps.Copy(allNodes, nonValidatorNodes) validatorSet := make(map[ids.NodeID]*validators.GetValidatorOutput) for _, sut := range validatorNodes { @@ -571,20 +580,10 @@ func newNetworkSUT(tb testing.TB, numValidators, numNonValidators int) *networkS getValidatorSetF := func(context.Context, uint64, ids.ID) (map[ids.NodeID]*validators.GetValidatorOutput, error) { return validatorSet, nil } - - nonValidatorNodes := make(map[ids.NodeID]*SUT, numNonValidators) - for range numNonValidators { - _, sut := newSUT(tb, 1) - nonValidatorNodes[sut.rawVM.snowCtx.NodeID] = sut - } - allNodes := make(map[ids.NodeID]*SUT, numValidators+numNonValidators) - maps.Copy(allNodes, validatorNodes) - maps.Copy(allNodes, nonValidatorNodes) for _, sut := range allNodes { sut.validators.GetValidatorSetF = getValidatorSetF } - // Connect all nodes to every validator. for nodeID, sut := range allNodes { peers := make(map[ids.NodeID]*SUT, len(allNodes)) peers[nodeID] = sut // self-registration @@ -597,6 +596,8 @@ func newNetworkSUT(tb testing.TB, numValidators, numNonValidators int) *networkS } maps.Copy(peers, nonValidatorPeers) + // All messages are delivered in a new goroutine to prevent re-entrant + // calls, which would result in a deadlock. sender := sut.sender sender.SendAppRequestF = func(ctx context.Context, s set.Set[ids.NodeID], r uint32, b []byte) error { go func() { @@ -604,9 +605,9 @@ func newNetworkSUT(tb testing.TB, numValidators, numNonValidators int) *networkS for peerID := range s { if peer, ok := peers[peerID]; ok { - assert.NoError(tb, peer.AppRequest(ctx, nodeID, r, mockable.MaxTime, b)) + assert.NoErrorf(tb, peer.AppRequest(ctx, nodeID, r, mockable.MaxTime, b), "sending request to %s", peerID) } else { - assert.NoError(tb, sut.AppRequestFailed(ctx, peerID, r, snowcommon.ErrTimeout)) + assert.NoErrorf(tb, sut.AppRequestFailed(ctx, peerID, r, snowcommon.ErrTimeout), "sending request failed to %s", peerID) } } }() @@ -622,7 +623,7 @@ func newNetworkSUT(tb testing.TB, numValidators, numNonValidators int) *networkS return } - assert.NoError(tb, peer.AppResponse(ctx, nodeID, r, b)) + assert.NoErrorf(tb, peer.AppResponse(ctx, nodeID, r, b), "sending response to %s", peerID) }() return nil @@ -640,7 +641,7 @@ func newNetworkSUT(tb testing.TB, numValidators, numNonValidators int) *networkS Code: code, Message: msg, } - assert.NoError(tb, peer.AppRequestFailed(ctx, nodeID, r, appErr)) + assert.NoErrorf(tb, peer.AppRequestFailed(ctx, nodeID, r, appErr), "sending error to %s", peerID) }() return nil } @@ -650,24 +651,20 @@ func newNetworkSUT(tb testing.TB, numValidators, numNonValidators int) *networkS for peerID := range c.NodeIDs { if peer, ok := peers[peerID]; ok { - assert.NoError(tb, peer.AppGossip(ctx, nodeID, b)) + assert.NoErrorf(tb, peer.AppGossip(ctx, nodeID, b), "sending gossip to %s", peerID) } } var sent set.Set[ids.NodeID] sent.Union(c.NodeIDs) send := func(peers map[ids.NodeID]*SUT, count int) error { - for peerID := range peers { + for peerID, peer := range peers { if count <= 0 { break } if sent.Contains(peerID) { continue } - peer, ok := peers[peerID] - if !ok { - continue - } if err := peer.AppGossip(ctx, nodeID, b); err != nil { return err } @@ -678,9 +675,9 @@ func newNetworkSUT(tb testing.TB, numValidators, numNonValidators int) *networkS return nil } - assert.NoError(tb, send(validatorNodes, c.Validators)) - assert.NoError(tb, send(nonValidatorPeers, c.NonValidators)) - assert.NoError(tb, send(peers, c.Peers)) + assert.NoError(tb, send(validatorNodes, c.Validators), "sending to validators") + assert.NoError(tb, send(nonValidatorPeers, c.NonValidators), "sending to non-validators") + assert.NoError(tb, send(peers, c.Peers), "sending to peers") }() return nil } From 77a6b2df40d385f3a1eb8bf320598f3e6721f78c Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Wed, 31 Dec 2025 14:59:29 -0500 Subject: [PATCH 18/35] Sanity check --- sae/vm_test.go | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/sae/vm_test.go b/sae/vm_test.go index 6758b8cd3..caf9807cc 100644 --- a/sae/vm_test.go +++ b/sae/vm_test.go @@ -568,6 +568,15 @@ func newNetworkSUT(tb testing.TB, numValidators, numNonValidators int) *networkS maps.Copy(allNodes, validatorNodes) maps.Copy(allNodes, nonValidatorNodes) + // Sanity check that the nodes agree on the genesis block, otherwise the + // network will exhibit very weird behavior. + { + _, expectedSUT := newSUT(tb, numAccounts) + for nodeID, sut := range allNodes { + require.Equalf(tb, expectedSUT.genesis.ID(), sut.genesis.ID(), "genesis ID for node %s", nodeID) + } + } + validatorSet := make(map[ids.NodeID]*validators.GetValidatorOutput) for _, sut := range validatorNodes { nodeID := sut.rawVM.snowCtx.NodeID From e4ea46dbda2048412b7387d016592c8fa8ecce08 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Wed, 31 Dec 2025 15:02:09 -0500 Subject: [PATCH 19/35] lint --- sae/p2p.go | 9 +++++---- sae/vm_test.go | 3 ++- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/sae/p2p.go b/sae/p2p.go index e9f4c4209..68bbf37ad 100644 --- a/sae/p2p.go +++ b/sae/p2p.go @@ -11,10 +11,11 @@ import ( "github.com/ava-labs/avalanchego/network/p2p" "github.com/ava-labs/avalanchego/network/p2p/gossip" "github.com/ava-labs/avalanchego/snow" - snowcommon "github.com/ava-labs/avalanchego/snow/engine/common" + "github.com/ava-labs/avalanchego/snow/engine/common" "github.com/ava-labs/avalanchego/utils/units" - "github.com/ava-labs/strevm/txgossip" "github.com/prometheus/client_golang/prometheus" + + "github.com/ava-labs/strevm/txgossip" ) const ( @@ -26,7 +27,7 @@ const ( // newNetwork creates the P2P network with a registered validator set. func newNetwork( snowCtx *snow.Context, - sender snowcommon.AppSender, + sender common.AppSender, reg *prometheus.Registry, ) ( *p2p.Network, @@ -109,7 +110,7 @@ func newGossipers( // spamming. Push messages are not filtered. type ( appRequester interface { - AppRequest(context.Context, ids.NodeID, time.Time, []byte) ([]byte, *snowcommon.AppError) + AppRequest(context.Context, ids.NodeID, time.Time, []byte) ([]byte, *common.AppError) } appGossiper interface { AppGossip(context.Context, ids.NodeID, []byte) diff --git a/sae/vm_test.go b/sae/vm_test.go index caf9807cc..837348780 100644 --- a/sae/vm_test.go +++ b/sae/vm_test.go @@ -126,7 +126,8 @@ func newSUT(tb testing.TB, numAccounts uint, opts ...sutOption) (context.Context ctx := logger.CancelOnError(tb.Context()) snowCtx := snowtest.Context(tb, chainID) - validators := snowCtx.ValidatorState.(*validatorstest.State) + validators, ok := snowCtx.ValidatorState.(*validatorstest.State) + require.Truef(tb, ok, "unexpected type %T for snowCtx.ValidatorState", snowCtx.ValidatorState) validators.GetCurrentHeightF = func(context.Context) (uint64, error) { return 0, nil } From 1f0f3f6122371e388f4dd3e972f10c4e4c1e5e47 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Wed, 31 Dec 2025 17:33:01 -0500 Subject: [PATCH 20/35] Update avalanchego version --- go.mod | 4 ++-- go.sum | 4 ++-- sae/vm_test.go | 7 ++----- 3 files changed, 6 insertions(+), 9 deletions(-) diff --git a/go.mod b/go.mod index 9e23c4464..bd2951e48 100644 --- a/go.mod +++ b/go.mod @@ -1,11 +1,11 @@ module github.com/ava-labs/strevm -go 1.24.9 +go 1.24.11 tool github.com/go-task/task/v3/cmd/task require ( - github.com/ava-labs/avalanchego v1.14.1-0.20251203215505-70148edc6eca + github.com/ava-labs/avalanchego v1.14.1-antithesis-docker-image-fix.0.20251231210902-f917492d193e github.com/ava-labs/libevm v1.13.15-0.20251215204913-9021836d7dbc github.com/google/go-cmp v0.7.0 github.com/holiman/uint256 v1.2.4 diff --git a/go.sum b/go.sum index 540e703ff..012a8a7ee 100644 --- a/go.sum +++ b/go.sum @@ -38,8 +38,8 @@ github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuW github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= -github.com/ava-labs/avalanchego v1.14.1-0.20251203215505-70148edc6eca h1:5qskWWezAIxXOOhnbMoBlYm8JwQ4Ufpzw8Lhn/p7QII= -github.com/ava-labs/avalanchego v1.14.1-0.20251203215505-70148edc6eca/go.mod h1:VperC9rPkJEewH5W7NGsmuY9eRLHV2QUndn821yxLLg= +github.com/ava-labs/avalanchego v1.14.1-antithesis-docker-image-fix.0.20251231210902-f917492d193e h1:FYnwmfLomI0GstwUX/77YRPdlfcvfAtxWply25tAU6A= +github.com/ava-labs/avalanchego v1.14.1-antithesis-docker-image-fix.0.20251231210902-f917492d193e/go.mod h1:Ykd/hkv76uq3hxpCXppR89U1rncV52Ixyz/q3bTLmto= github.com/ava-labs/libevm v1.13.15-0.20251215204913-9021836d7dbc h1:96JeuuaNheXek6Vcw8ldm/r4CCZsTX+5yd1iDHYJop8= github.com/ava-labs/libevm v1.13.15-0.20251215204913-9021836d7dbc/go.mod h1:DqSotSn4Dx/UJV+d3svfW8raR+cH7+Ohl9BpsQ5HlGU= github.com/aymerick/raymond v2.0.3-0.20180322193309-b565731e1464+incompatible/go.mod h1:osfaiScAUVup+UC9Nfq76eWqDhXlp+4UYaA8uhTBO6g= diff --git a/sae/vm_test.go b/sae/vm_test.go index 837348780..cbd44f3ba 100644 --- a/sae/vm_test.go +++ b/sae/vm_test.go @@ -126,11 +126,6 @@ func newSUT(tb testing.TB, numAccounts uint, opts ...sutOption) (context.Context ctx := logger.CancelOnError(tb.Context()) snowCtx := snowtest.Context(tb, chainID) - validators, ok := snowCtx.ValidatorState.(*validatorstest.State) - require.Truef(tb, ok, "unexpected type %T for snowCtx.ValidatorState", snowCtx.ValidatorState) - validators.GetCurrentHeightF = func(context.Context) (uint64, error) { - return 0, nil - } snowCtx.Log = logger sender := &enginetest.Sender{} @@ -147,6 +142,8 @@ func newSUT(tb testing.TB, numAccounts uint, opts ...sutOption) (context.Context require.NoError(tb, err, "ethclient.Dial(http.NewServer(%T.CreateHandlers()))", snow) tb.Cleanup(client.Close) + validators, ok := snowCtx.ValidatorState.(*validatorstest.State) + require.Truef(tb, ok, "unexpected type %T for snowCtx.ValidatorState", snowCtx.ValidatorState) return ctx, &SUT{ ChainVM: snow, Client: client, From e3ddf04de0ef9d46b266680dca90c7f8a8b072d7 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Sat, 3 Jan 2026 23:48:09 -0500 Subject: [PATCH 21/35] wtf --- adaptor/adaptor.go | 2 +- blocks/block.go | 2 +- blocks/block_test.go | 2 +- blocks/blockstest/blocks.go | 2 +- blocks/blockstest/blocks_test.go | 2 +- blocks/blockstest/chain.go | 2 +- blocks/cmpopt.go | 2 +- blocks/execution.go | 2 +- blocks/execution_test.go | 2 +- blocks/export.go | 2 +- blocks/invariants.go | 2 +- blocks/settlement.go | 2 +- blocks/settlement_test.go | 2 +- blocks/snow.go | 2 +- cmputils/cmputils.go | 2 +- cmputils/types.go | 2 +- gastime/acp176.go | 2 +- gastime/acp176_test.go | 2 +- gastime/cmpopt.go | 2 +- gastime/gastime.go | 2 +- gastime/gastime_test.go | 2 +- gastime/marshal.go | 2 +- hook/hook.go | 2 +- hook/hook_test.go | 2 +- hook/hookstest/stub.go | 2 +- intmath/intmath.go | 2 +- intmath/intmath_test.go | 2 +- params/params.go | 2 +- proxytime/cmpopt.go | 2 +- proxytime/cmpopt_test.go | 2 +- proxytime/proxytime.go | 2 +- proxytime/proxytime_test.go | 2 +- sae/always.go | 2 +- sae/blocks.go | 2 +- sae/consensus.go | 2 +- sae/health.go | 2 +- sae/sae.go | 2 +- sae/temporary.go | 2 +- sae/vm_test.go | 2 +- saetest/escrow/escrow.go | 2 +- saetest/logging.go | 2 +- saetest/saetest.go | 2 +- saetest/saetest_test.go | 2 +- saetest/wallet.go | 2 +- saexec/context.go | 2 +- saexec/execution.go | 2 +- saexec/saexec.go | 2 +- saexec/saexec_test.go | 2 +- saexec/subscription.go | 2 +- txgossip/blockchain.go | 2 +- txgossip/priority.go | 2 +- txgossip/pushpull.go | 2 +- txgossip/txgossip.go | 2 +- txgossip/txgossip_test.go | 2 +- worstcase/state.go | 2 +- worstcase/state_test.go | 2 +- 56 files changed, 56 insertions(+), 56 deletions(-) diff --git a/adaptor/adaptor.go b/adaptor/adaptor.go index 4fdb40a4e..2b514a6e9 100644 --- a/adaptor/adaptor.go +++ b/adaptor/adaptor.go @@ -1,4 +1,4 @@ -// Copyright (C) ((20\d\d\-2026)|(2026)), Ava Labs, Inc. All rights reserved. +// Copyright (C) 2025-2026, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. // Package adaptor provides a generic alternative to the Snowman [block.ChainVM] diff --git a/blocks/block.go b/blocks/block.go index ab5c743b7..d73a34a38 100644 --- a/blocks/block.go +++ b/blocks/block.go @@ -1,4 +1,4 @@ -// Copyright (C) ((20\d\d\-2026)|(2026)), Ava Labs, Inc. All rights reserved. +// Copyright (C) 2025-2026, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. // Package blocks defines [Streaming Asynchronous Execution] (SAE) blocks. diff --git a/blocks/block_test.go b/blocks/block_test.go index f3e7372a4..54e961f41 100644 --- a/blocks/block_test.go +++ b/blocks/block_test.go @@ -1,4 +1,4 @@ -// Copyright (C) ((20\d\d\-2026)|(2026)), Ava Labs, Inc. All rights reserved. +// Copyright (C) 2025-2026, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package blocks diff --git a/blocks/blockstest/blocks.go b/blocks/blockstest/blocks.go index f352e714a..7534653e8 100644 --- a/blocks/blockstest/blocks.go +++ b/blocks/blockstest/blocks.go @@ -1,4 +1,4 @@ -// Copyright (C) ((20\d\d\-2026)|(2026)), Ava Labs, Inc. All rights reserved. +// Copyright (C) 2025-2026, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. // Package blockstest provides test helpers for constructing [Streaming diff --git a/blocks/blockstest/blocks_test.go b/blocks/blockstest/blocks_test.go index d911264cb..a8df5bf1c 100644 --- a/blocks/blockstest/blocks_test.go +++ b/blocks/blockstest/blocks_test.go @@ -1,4 +1,4 @@ -// Copyright (C) ((20\d\d\-2026)|(2026)), Ava Labs, Inc. All rights reserved. +// Copyright (C) 2025-2026, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package blockstest diff --git a/blocks/blockstest/chain.go b/blocks/blockstest/chain.go index 5bbb2c326..5cba85b49 100644 --- a/blocks/blockstest/chain.go +++ b/blocks/blockstest/chain.go @@ -1,4 +1,4 @@ -// Copyright (C) ((20\d\d\-2026)|(2026)), Ava Labs, Inc. All rights reserved. +// Copyright (C) 2025-2026, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. // Package blockstest provides test helpers for constructing [Streaming diff --git a/blocks/cmpopt.go b/blocks/cmpopt.go index 5e1f75aec..8d7b99d32 100644 --- a/blocks/cmpopt.go +++ b/blocks/cmpopt.go @@ -1,4 +1,4 @@ -// Copyright (C) ((20\d\d\-2026)|(2026)), Ava Labs, Inc. All rights reserved. +// Copyright (C) 2025-2026, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. //go:build !prod && !nocmpopts diff --git a/blocks/execution.go b/blocks/execution.go index e8efb0fa6..f0ed97bd6 100644 --- a/blocks/execution.go +++ b/blocks/execution.go @@ -1,4 +1,4 @@ -// Copyright (C) ((20\d\d\-2026)|(2026)), Ava Labs, Inc. All rights reserved. +// Copyright (C) 2025-2026, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package blocks diff --git a/blocks/execution_test.go b/blocks/execution_test.go index da5b888f4..3afec2494 100644 --- a/blocks/execution_test.go +++ b/blocks/execution_test.go @@ -1,4 +1,4 @@ -// Copyright (C) ((20\d\d\-2026)|(2026)), Ava Labs, Inc. All rights reserved. +// Copyright (C) 2025-2026, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package blocks diff --git a/blocks/export.go b/blocks/export.go index f3d22dca7..2ea70f517 100644 --- a/blocks/export.go +++ b/blocks/export.go @@ -1,4 +1,4 @@ -// Copyright (C) ((20\d\d\-2026)|(2026)), Ava Labs, Inc. All rights reserved. +// Copyright (C) 2025-2026, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package blocks diff --git a/blocks/invariants.go b/blocks/invariants.go index d7f0fde78..b011cf618 100644 --- a/blocks/invariants.go +++ b/blocks/invariants.go @@ -1,4 +1,4 @@ -// Copyright (C) ((20\d\d\-2026)|(2026)), Ava Labs, Inc. All rights reserved. +// Copyright (C) 2025-2026, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package blocks diff --git a/blocks/settlement.go b/blocks/settlement.go index b40253386..240c202fe 100644 --- a/blocks/settlement.go +++ b/blocks/settlement.go @@ -1,4 +1,4 @@ -// Copyright (C) ((20\d\d\-2026)|(2026)), Ava Labs, Inc. All rights reserved. +// Copyright (C) 2025-2026, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package blocks diff --git a/blocks/settlement_test.go b/blocks/settlement_test.go index 0562902ba..59fff8005 100644 --- a/blocks/settlement_test.go +++ b/blocks/settlement_test.go @@ -1,4 +1,4 @@ -// Copyright (C) ((20\d\d\-2026)|(2026)), Ava Labs, Inc. All rights reserved. +// Copyright (C) 2025-2026, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package blocks diff --git a/blocks/snow.go b/blocks/snow.go index 547c71c96..a6b519316 100644 --- a/blocks/snow.go +++ b/blocks/snow.go @@ -1,4 +1,4 @@ -// Copyright (C) ((20\d\d\-2026)|(2026)), Ava Labs, Inc. All rights reserved. +// Copyright (C) 2025-2026, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package blocks diff --git a/cmputils/cmputils.go b/cmputils/cmputils.go index 551b23064..f28d2b633 100644 --- a/cmputils/cmputils.go +++ b/cmputils/cmputils.go @@ -1,4 +1,4 @@ -// Copyright (C) ((20\d\d\-2026)|(2026)), Ava Labs, Inc. All rights reserved. +// Copyright (C) 2025-2026, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. //go:build !prod && !nocmpopts diff --git a/cmputils/types.go b/cmputils/types.go index f87bdca6e..c8ce6d7bc 100644 --- a/cmputils/types.go +++ b/cmputils/types.go @@ -1,4 +1,4 @@ -// Copyright (C) ((20\d\d\-2026)|(2026)), Ava Labs, Inc. All rights reserved. +// Copyright (C) 2025-2026, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package cmputils diff --git a/gastime/acp176.go b/gastime/acp176.go index 0470dc28d..4a0972fea 100644 --- a/gastime/acp176.go +++ b/gastime/acp176.go @@ -1,4 +1,4 @@ -// Copyright (C) ((20\d\d\-2026)|(2026)), Ava Labs, Inc. All rights reserved. +// Copyright (C) 2025-2026, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package gastime diff --git a/gastime/acp176_test.go b/gastime/acp176_test.go index cb8e4601e..1ce73d870 100644 --- a/gastime/acp176_test.go +++ b/gastime/acp176_test.go @@ -1,4 +1,4 @@ -// Copyright (C) ((20\d\d\-2026)|(2026)), Ava Labs, Inc. All rights reserved. +// Copyright (C) 2025-2026, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package gastime diff --git a/gastime/cmpopt.go b/gastime/cmpopt.go index 2dff7a3f7..992930bbb 100644 --- a/gastime/cmpopt.go +++ b/gastime/cmpopt.go @@ -1,4 +1,4 @@ -// Copyright (C) ((20\d\d\-2026)|(2026)), Ava Labs, Inc. All rights reserved. +// Copyright (C) 2025-2026, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. //go:build !prod && !nocmpopts diff --git a/gastime/gastime.go b/gastime/gastime.go index 73eaeb199..1f6f19488 100644 --- a/gastime/gastime.go +++ b/gastime/gastime.go @@ -1,4 +1,4 @@ -// Copyright (C) ((20\d\d\-2026)|(2026)), Ava Labs, Inc. All rights reserved. +// Copyright (C) 2025-2026, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. // Package gastime measures time based on the consumption of gas. diff --git a/gastime/gastime_test.go b/gastime/gastime_test.go index 95997ad63..62d130de2 100644 --- a/gastime/gastime_test.go +++ b/gastime/gastime_test.go @@ -1,4 +1,4 @@ -// Copyright (C) ((20\d\d\-2026)|(2026)), Ava Labs, Inc. All rights reserved. +// Copyright (C) 2025-2026, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package gastime diff --git a/gastime/marshal.go b/gastime/marshal.go index d6663cd7b..c4932c304 100644 --- a/gastime/marshal.go +++ b/gastime/marshal.go @@ -1,4 +1,4 @@ -// Copyright (C) ((20\d\d\-2026)|(2026)), Ava Labs, Inc. All rights reserved. +// Copyright (C) 2025-2026, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package gastime diff --git a/hook/hook.go b/hook/hook.go index 5fdc0c35a..4b412a6a6 100644 --- a/hook/hook.go +++ b/hook/hook.go @@ -1,4 +1,4 @@ -// Copyright (C) ((20\d\d\-2026)|(2026)), Ava Labs, Inc. All rights reserved. +// Copyright (C) 2025-2026, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. // Package hook defines points in an SAE block's lifecycle at which common or diff --git a/hook/hook_test.go b/hook/hook_test.go index 1316d0334..3738e81c2 100644 --- a/hook/hook_test.go +++ b/hook/hook_test.go @@ -1,4 +1,4 @@ -// Copyright (C) ((20\d\d\-2026)|(2026)), Ava Labs, Inc. All rights reserved. +// Copyright (C) 2025-2026, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package hook diff --git a/hook/hookstest/stub.go b/hook/hookstest/stub.go index a093e66a1..8a2d671a7 100644 --- a/hook/hookstest/stub.go +++ b/hook/hookstest/stub.go @@ -1,4 +1,4 @@ -// Copyright (C) ((20\d\d\-2026)|(2026)), Ava Labs, Inc. All rights reserved. +// Copyright (C) 2025-2026, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. // Package hookstest provides a test double for SAE's [hook] package. diff --git a/intmath/intmath.go b/intmath/intmath.go index ad6902120..91bb4c120 100644 --- a/intmath/intmath.go +++ b/intmath/intmath.go @@ -1,4 +1,4 @@ -// Copyright (C) ((20\d\d\-2026)|(2026)), Ava Labs, Inc. All rights reserved. +// Copyright (C) 2025-2026, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. // Package intmath provides special-case integer arithmetic. diff --git a/intmath/intmath_test.go b/intmath/intmath_test.go index 7d93c148b..75127bb14 100644 --- a/intmath/intmath_test.go +++ b/intmath/intmath_test.go @@ -1,4 +1,4 @@ -// Copyright (C) ((20\d\d\-2026)|(2026)), Ava Labs, Inc. All rights reserved. +// Copyright (C) 2025-2026, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package intmath diff --git a/params/params.go b/params/params.go index abddfad0d..d6d13c8cf 100644 --- a/params/params.go +++ b/params/params.go @@ -1,4 +1,4 @@ -// Copyright (C) ((20\d\d\-2026)|(2026)), Ava Labs, Inc. All rights reserved. +// Copyright (C) 2025-2026, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. // Package params declares [Streaming Asynchronous Execution] (SAE) parameters. diff --git a/proxytime/cmpopt.go b/proxytime/cmpopt.go index 51a4f29e2..3e68f5888 100644 --- a/proxytime/cmpopt.go +++ b/proxytime/cmpopt.go @@ -1,4 +1,4 @@ -// Copyright (C) ((20\d\d\-2026)|(2026)), Ava Labs, Inc. All rights reserved. +// Copyright (C) 2025-2026, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. //go:build !prod && !nocmpopts diff --git a/proxytime/cmpopt_test.go b/proxytime/cmpopt_test.go index cf8dfaf16..6f319903d 100644 --- a/proxytime/cmpopt_test.go +++ b/proxytime/cmpopt_test.go @@ -1,4 +1,4 @@ -// Copyright (C) ((20\d\d\-2026)|(2026)), Ava Labs, Inc. All rights reserved. +// Copyright (C) 2025-2026, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package proxytime diff --git a/proxytime/proxytime.go b/proxytime/proxytime.go index 9f374324f..ef8c6d185 100644 --- a/proxytime/proxytime.go +++ b/proxytime/proxytime.go @@ -1,4 +1,4 @@ -// Copyright (C) ((20\d\d\-2026)|(2026)), Ava Labs, Inc. All rights reserved. +// Copyright (C) 2025-2026, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. // Package proxytime measures the passage of time based on a proxy unit and diff --git a/proxytime/proxytime_test.go b/proxytime/proxytime_test.go index 5d9261f63..eea04c166 100644 --- a/proxytime/proxytime_test.go +++ b/proxytime/proxytime_test.go @@ -1,4 +1,4 @@ -// Copyright (C) ((20\d\d\-2026)|(2026)), Ava Labs, Inc. All rights reserved. +// Copyright (C) 2025-2026, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package proxytime diff --git a/sae/always.go b/sae/always.go index 850795f1e..c758c5724 100644 --- a/sae/always.go +++ b/sae/always.go @@ -1,4 +1,4 @@ -// Copyright (C) ((20\d\d\-2026)|(2026)), Ava Labs, Inc. All rights reserved. +// Copyright (C) 2025-2026, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package sae diff --git a/sae/blocks.go b/sae/blocks.go index 996dbf368..13109a0ae 100644 --- a/sae/blocks.go +++ b/sae/blocks.go @@ -1,4 +1,4 @@ -// Copyright (C) ((20\d\d\-2026)|(2026)), Ava Labs, Inc. All rights reserved. +// Copyright (C) 2025-2026, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package sae diff --git a/sae/consensus.go b/sae/consensus.go index 6bd75a868..202df4e2e 100644 --- a/sae/consensus.go +++ b/sae/consensus.go @@ -1,4 +1,4 @@ -// Copyright (C) ((20\d\d\-2026)|(2026)), Ava Labs, Inc. All rights reserved. +// Copyright (C) 2025-2026, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package sae diff --git a/sae/health.go b/sae/health.go index b320eea29..4e48ea4ef 100644 --- a/sae/health.go +++ b/sae/health.go @@ -1,4 +1,4 @@ -// Copyright (C) ((20\d\d\-2026)|(2026)), Ava Labs, Inc. All rights reserved. +// Copyright (C) 2025-2026, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package sae diff --git a/sae/sae.go b/sae/sae.go index 2cf8eb135..e19c649cb 100644 --- a/sae/sae.go +++ b/sae/sae.go @@ -1,4 +1,4 @@ -// Copyright (C) ((20\d\d\-2026)|(2026)), Ava Labs, Inc. All rights reserved. +// Copyright (C) 2025-2026, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. // Package sae implements the [Streaming Asynchronous Execution] (SAE) virtual diff --git a/sae/temporary.go b/sae/temporary.go index 3031b476c..259456072 100644 --- a/sae/temporary.go +++ b/sae/temporary.go @@ -1,4 +1,4 @@ -// Copyright (C) ((20\d\d\-2026)|(2026)), Ava Labs, Inc. All rights reserved. +// Copyright (C) 2025-2026, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package sae diff --git a/sae/vm_test.go b/sae/vm_test.go index 0fdf5d8ac..94c1f5d77 100644 --- a/sae/vm_test.go +++ b/sae/vm_test.go @@ -1,4 +1,4 @@ -// Copyright (C) ((20\d\d\-2026)|(2026)), Ava Labs, Inc. All rights reserved. +// Copyright (C) 2025-2026, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package sae diff --git a/saetest/escrow/escrow.go b/saetest/escrow/escrow.go index abe54cf52..e53ebe595 100644 --- a/saetest/escrow/escrow.go +++ b/saetest/escrow/escrow.go @@ -1,4 +1,4 @@ -// Copyright (C) ((20\d\d\-2026)|(2026)), Ava Labs, Inc. All rights reserved. +// Copyright (C) 2025-2026, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. // The above copyright and licensing exclude the original Escrow.sol contract diff --git a/saetest/logging.go b/saetest/logging.go index 3c2f7dd3a..75d29e149 100644 --- a/saetest/logging.go +++ b/saetest/logging.go @@ -1,4 +1,4 @@ -// Copyright (C) ((20\d\d\-2026)|(2026)), Ava Labs, Inc. All rights reserved. +// Copyright (C) 2025-2026, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package saetest diff --git a/saetest/saetest.go b/saetest/saetest.go index 03cf518e0..0b13f3032 100644 --- a/saetest/saetest.go +++ b/saetest/saetest.go @@ -1,4 +1,4 @@ -// Copyright (C) ((20\d\d\-2026)|(2026)), Ava Labs, Inc. All rights reserved. +// Copyright (C) 2025-2026, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. // Package saetest provides testing helpers for [Streaming Asynchronous diff --git a/saetest/saetest_test.go b/saetest/saetest_test.go index 917a3314f..82b22e632 100644 --- a/saetest/saetest_test.go +++ b/saetest/saetest_test.go @@ -1,4 +1,4 @@ -// Copyright (C) ((20\d\d\-2026)|(2026)), Ava Labs, Inc. All rights reserved. +// Copyright (C) 2025-2026, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package saetest diff --git a/saetest/wallet.go b/saetest/wallet.go index d64dd9ae0..c7a4c8493 100644 --- a/saetest/wallet.go +++ b/saetest/wallet.go @@ -1,4 +1,4 @@ -// Copyright (C) ((20\d\d\-2026)|(2026)), Ava Labs, Inc. All rights reserved. +// Copyright (C) 2025-2026, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package saetest diff --git a/saexec/context.go b/saexec/context.go index c77d62df7..727022e29 100644 --- a/saexec/context.go +++ b/saexec/context.go @@ -1,4 +1,4 @@ -// Copyright (C) ((20\d\d\-2026)|(2026)), Ava Labs, Inc. All rights reserved. +// Copyright (C) 2025-2026, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package saexec diff --git a/saexec/execution.go b/saexec/execution.go index 5f6de23c4..e079fa07d 100644 --- a/saexec/execution.go +++ b/saexec/execution.go @@ -1,4 +1,4 @@ -// Copyright (C) ((20\d\d\-2026)|(2026)), Ava Labs, Inc. All rights reserved. +// Copyright (C) 2025-2026, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package saexec diff --git a/saexec/saexec.go b/saexec/saexec.go index df355480b..d75f45aed 100644 --- a/saexec/saexec.go +++ b/saexec/saexec.go @@ -1,4 +1,4 @@ -// Copyright (C) ((20\d\d\-2026)|(2026)), Ava Labs, Inc. All rights reserved. +// Copyright (C) 2025-2026, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. // Package saexec provides the execution module of [Streaming Asynchronous diff --git a/saexec/saexec_test.go b/saexec/saexec_test.go index fddefb818..1826b8c05 100644 --- a/saexec/saexec_test.go +++ b/saexec/saexec_test.go @@ -1,4 +1,4 @@ -// Copyright (C) ((20\d\d\-2026)|(2026)), Ava Labs, Inc. All rights reserved. +// Copyright (C) 2025-2026, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package saexec diff --git a/saexec/subscription.go b/saexec/subscription.go index fffb13a48..68201e350 100644 --- a/saexec/subscription.go +++ b/saexec/subscription.go @@ -1,4 +1,4 @@ -// Copyright (C) ((20\d\d\-2026)|(2026)), Ava Labs, Inc. All rights reserved. +// Copyright (C) 2025-2026, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package saexec diff --git a/txgossip/blockchain.go b/txgossip/blockchain.go index e157a93de..0996b1ab2 100644 --- a/txgossip/blockchain.go +++ b/txgossip/blockchain.go @@ -1,4 +1,4 @@ -// Copyright (C) ((20\d\d\-2026)|(2026)), Ava Labs, Inc. All rights reserved. +// Copyright (C) 2025-2026, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package txgossip diff --git a/txgossip/priority.go b/txgossip/priority.go index b6189fbca..0d9ffd81d 100644 --- a/txgossip/priority.go +++ b/txgossip/priority.go @@ -1,4 +1,4 @@ -// Copyright (C) ((20\d\d\-2026)|(2026)), Ava Labs, Inc. All rights reserved. +// Copyright (C) 2025-2026, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package txgossip diff --git a/txgossip/pushpull.go b/txgossip/pushpull.go index d14b8bd55..af12315c2 100644 --- a/txgossip/pushpull.go +++ b/txgossip/pushpull.go @@ -1,4 +1,4 @@ -// Copyright (C) ((20\d\d\-2026)|(2026)), Ava Labs, Inc. All rights reserved. +// Copyright (C) 2025-2026, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package txgossip diff --git a/txgossip/txgossip.go b/txgossip/txgossip.go index 27fa79586..32da5a4df 100644 --- a/txgossip/txgossip.go +++ b/txgossip/txgossip.go @@ -1,4 +1,4 @@ -// Copyright (C) ((20\d\d\-2026)|(2026)), Ava Labs, Inc. All rights reserved. +// Copyright (C) 2025-2026, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. // Package txgossip provides a mempool for [Streaming Asynchronous Execution], diff --git a/txgossip/txgossip_test.go b/txgossip/txgossip_test.go index 7b31fd2d2..1e1326533 100644 --- a/txgossip/txgossip_test.go +++ b/txgossip/txgossip_test.go @@ -1,4 +1,4 @@ -// Copyright (C) ((20\d\d\-2026)|(2026)), Ava Labs, Inc. All rights reserved. +// Copyright (C) 2025-2026, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package txgossip diff --git a/worstcase/state.go b/worstcase/state.go index 5bd6a22e8..5262411b8 100644 --- a/worstcase/state.go +++ b/worstcase/state.go @@ -1,4 +1,4 @@ -// Copyright (C) ((20\d\d\-2026)|(2026)), Ava Labs, Inc. All rights reserved. +// Copyright (C) 2025-2026, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. // Package worstcase provides the worst-case balance and nonce tracking needed diff --git a/worstcase/state_test.go b/worstcase/state_test.go index 5e63f4c36..5df1a97a6 100644 --- a/worstcase/state_test.go +++ b/worstcase/state_test.go @@ -1,4 +1,4 @@ -// Copyright (C) ((20\d\d\-2026)|(2026)), Ava Labs, Inc. All rights reserved. +// Copyright (C) 2025-2026, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package worstcase From 4672c91edf8b6bf59bf6a7d8f8faa9505b278bd9 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Sat, 3 Jan 2026 23:48:54 -0500 Subject: [PATCH 22/35] reduce diff --- saexec/saexec.go | 5 ----- 1 file changed, 5 deletions(-) diff --git a/saexec/saexec.go b/saexec/saexec.go index d75f45aed..dc45d909c 100644 --- a/saexec/saexec.go +++ b/saexec/saexec.go @@ -117,11 +117,6 @@ func (e *Executor) ChainConfig() *params.ChainConfig { return e.chainConfig } -// DB returns the db originally passed to [New]. -func (e *Executor) DB() ethdb.Database { - return e.db -} - // StateCache returns caching database underpinning execution. func (e *Executor) StateCache() state.Database { return e.stateCache From 22cfae6e124a6d676dd87468b5d7ddd85c894fa7 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Sat, 3 Jan 2026 23:55:51 -0500 Subject: [PATCH 23/35] nits --- sae/rpc.go | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/sae/rpc.go b/sae/rpc.go index 652b8362a..c04b8892e 100644 --- a/sae/rpc.go +++ b/sae/rpc.go @@ -49,8 +49,7 @@ func (vm *VM) ethRPCServer() (*rpc.Server, error) { vm: vm, accountManager: accountManager, } - // Even if this function errors, we should close API to prevent a goroutine - // from leaking. + filterSystem := filters.NewFilterSystem(b, filters.Config{}) filterAPI := filters.NewFilterAPI(filterSystem, false /*isLightClient*/) vm.toClose = append(vm.toClose, func() error { @@ -67,7 +66,7 @@ func (vm *VM) ethRPCServer() (*rpc.Server, error) { // Standard Ethereum node APIs: // - web3_clientVersion // - web3_sha3 - {"web3", &web3API{}}, + {"web3", newWeb3API()}, // Standard Ethereum node APIs: // - net_listening // - net_peerCount @@ -232,10 +231,18 @@ func (vm *VM) ethRPCServer() (*rpc.Server, error) { } // web3API offers the `web3` RPCs. -type web3API struct{} +type web3API struct { + clientVersion string +} + +func newWeb3API() *web3API { + return &web3API{ + clientVersion: version.Current.String(), + } +} -func (*web3API) ClientVersion() string { - return version.Current.String() +func (w *web3API) ClientVersion() string { + return w.clientVersion } func (*web3API) Sha3(input hexutil.Bytes) hexutil.Bytes { From b723fc269054588894cb79d76a19a943b2347af2 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Sun, 4 Jan 2026 11:29:23 -0500 Subject: [PATCH 24/35] comment --- sae/vm_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/sae/vm_test.go b/sae/vm_test.go index c9cd0fa40..4b59a4881 100644 --- a/sae/vm_test.go +++ b/sae/vm_test.go @@ -89,6 +89,7 @@ type ( sutOption = options.Option[sutConfig] ) +// chainID is made a global to keep it constant across multiple SUTs. var chainID = ids.GenerateTestID() func newSUT(tb testing.TB, numAccounts uint, opts ...sutOption) (context.Context, *SUT) { From 4fb327a9754c0d6b183d3c3237f8e12b006cccbe Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Sun, 4 Jan 2026 11:41:16 -0500 Subject: [PATCH 25/35] nit --- sae/rpc.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sae/rpc.go b/sae/rpc.go index d51613fa9..17c25be89 100644 --- a/sae/rpc.go +++ b/sae/rpc.go @@ -448,7 +448,7 @@ func (a *apiBackend) SubscribeChainHeadEvent(ch chan<- core.ChainHeadEvent) even return a.vm.exec.SubscribeChainHeadEvent(ch) } -func (*apiBackend) SubscribeChainSideEvent(ch chan<- core.ChainSideEvent) event.Subscription { +func (*apiBackend) SubscribeChainSideEvent(chan<- core.ChainSideEvent) event.Subscription { // SAE never reorgs, so there are no side events. return newNoopSubscription() } @@ -525,7 +525,7 @@ func (a *apiBackend) SubscribeLogsEvent(ch chan<- []*types.Log) event.Subscripti return a.vm.exec.SubscribeLogsEvent(ch) } -func (a *apiBackend) SubscribePendingLogsEvent(ch chan<- []*types.Log) event.Subscription { +func (*apiBackend) SubscribePendingLogsEvent(chan<- []*types.Log) event.Subscription { // In SAE, "pending" refers to the execution status. There are no logs known // for transactions pending execution. return newNoopSubscription() From d8227659a4aad77c61356f1645c31ceec0794767 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Thu, 8 Jan 2026 07:50:12 -0500 Subject: [PATCH 26/35] Update avalanchego version --- go.mod | 2 +- go.sum | 4 +- sae/p2p.go | 126 ----------------------------------------------------- sae/vm.go | 23 ++++++++-- 4 files changed, 22 insertions(+), 133 deletions(-) diff --git a/go.mod b/go.mod index f2d047253..5d106f4f1 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.24.11 tool github.com/go-task/task/v3/cmd/task require ( - github.com/ava-labs/avalanchego v1.14.1-antithesis-docker-image-fix.0.20251231210902-f917492d193e + github.com/ava-labs/avalanchego v1.14.2-0.20260108105635-ce78957d15e0 github.com/ava-labs/libevm v1.13.15-0.20260103201951-7e0995585aa7 github.com/google/go-cmp v0.7.0 github.com/holiman/uint256 v1.2.4 diff --git a/go.sum b/go.sum index 0ed8dbc36..2153a48bf 100644 --- a/go.sum +++ b/go.sum @@ -38,8 +38,8 @@ github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuW github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= -github.com/ava-labs/avalanchego v1.14.1-antithesis-docker-image-fix.0.20251231210902-f917492d193e h1:FYnwmfLomI0GstwUX/77YRPdlfcvfAtxWply25tAU6A= -github.com/ava-labs/avalanchego v1.14.1-antithesis-docker-image-fix.0.20251231210902-f917492d193e/go.mod h1:Ykd/hkv76uq3hxpCXppR89U1rncV52Ixyz/q3bTLmto= +github.com/ava-labs/avalanchego v1.14.2-0.20260108105635-ce78957d15e0 h1:Gor8uyJ4uHtHt+13QJjPSdXEO2PSDgEmCL3mRgGYJTs= +github.com/ava-labs/avalanchego v1.14.2-0.20260108105635-ce78957d15e0/go.mod h1:TWuYmX/Wmfdd+9KvFZJHGldQ6SXq0jZ334T0kUOaJsM= github.com/ava-labs/libevm v1.13.15-0.20260103201951-7e0995585aa7 h1:x2Id/gBc0/HlACqzN8DDsuMP++Om+XRop19URXQsxCQ= github.com/ava-labs/libevm v1.13.15-0.20260103201951-7e0995585aa7/go.mod h1:oyJdZfpQTc9fVzAbDry+QRYeiCbw8s/kGaDUsEMpb4I= github.com/aymerick/raymond v2.0.3-0.20180322193309-b565731e1464+incompatible/go.mod h1:osfaiScAUVup+UC9Nfq76eWqDhXlp+4UYaA8uhTBO6g= diff --git a/sae/p2p.go b/sae/p2p.go index 813922428..1912cfb4c 100644 --- a/sae/p2p.go +++ b/sae/p2p.go @@ -4,24 +4,12 @@ package sae import ( - "context" "time" - "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/network/p2p" - "github.com/ava-labs/avalanchego/network/p2p/gossip" "github.com/ava-labs/avalanchego/snow" "github.com/ava-labs/avalanchego/snow/engine/common" - "github.com/ava-labs/avalanchego/utils/units" "github.com/prometheus/client_golang/prometheus" - - "github.com/ava-labs/strevm/txgossip" -) - -const ( - gossipHandlerID = p2p.TxGossipHandlerID - pullRequestPeriod = time.Second // seconds/request - pushGossipPeriod = 100 * time.Millisecond ) // newNetwork creates the P2P network with a registered validator set. @@ -54,117 +42,3 @@ func newNetwork( } return network, validatorPeers, nil } - -// newGossipers creates the tx gossipers for mempool dissemination. -func newGossipers( - snowCtx *snow.Context, - network *p2p.Network, - validatorPeers *p2p.Validators, - reg *prometheus.Registry, - mempool *txgossip.Set, -) ( - p2p.Handler, - *gossip.ValidatorGossiper, - *gossip.PushGossiper[txgossip.Transaction], - error, -) { - const namespace = "gossip" - metrics, err := gossip.NewMetrics(reg, namespace) - if err != nil { - return nil, nil, nil, err - } - - const targetMessageSize = 20 * units.KiB - var marshaller txgossip.Marshaller - handler := gossip.NewHandler( - snowCtx.Log, - marshaller, - mempool, - metrics, - targetMessageSize, - ) - - const ( - throttlingPeriod = time.Hour // seconds/period - requestsPerPeerPerPeriod = float64(throttlingPeriod / pullRequestPeriod) // requests/period - ) - throttledHandler, err := p2p.NewDynamicThrottlerHandler( - snowCtx.Log, - handler, - validatorPeers, - throttlingPeriod, - requestsPerPeerPerPeriod, - reg, - namespace, - ) - if err != nil { - return nil, nil, nil, err - } - validatorOnlyHandler := p2p.NewValidatorHandler( - throttledHandler, - validatorPeers, - snowCtx.Log, - ) - - // Pull requests are filtered by validators and are throttled to prevent - // spamming. Push messages are not filtered. - type ( - appRequester interface { - AppRequest(context.Context, ids.NodeID, time.Time, []byte) ([]byte, *common.AppError) - } - appGossiper interface { - AppGossip(context.Context, ids.NodeID, []byte) - } - ) - gossipHandler := struct { - appRequester - appGossiper - }{ - appRequester: validatorOnlyHandler, - appGossiper: handler, - } - - client := network.NewClient(gossipHandlerID, validatorPeers) - const pollSize = 1 - pullGossiper := gossip.NewPullGossiper[txgossip.Transaction]( - snowCtx.Log, - marshaller, - mempool, - client, - metrics, - pollSize, - ) - pullGossiperWhenValidator := &gossip.ValidatorGossiper{ - Gossiper: pullGossiper, - NodeID: snowCtx.NodeID, - Validators: validatorPeers, - } - - pushGossipParams := gossip.BranchingFactor{ - StakePercentage: .9, - Validators: 100, - } - pushRegossipParams := gossip.BranchingFactor{ - Validators: 10, - } - const ( - discardedCacheSize = 16_384 - regossipPeriod = 30 * time.Second - ) - pushGossiper, err := gossip.NewPushGossiper( - marshaller, - mempool, - validatorPeers, - client, - metrics, - pushGossipParams, - pushRegossipParams, - discardedCacheSize, - targetMessageSize, - regossipPeriod, - ) - if err != nil { - return nil, nil, nil, err - } - return gossipHandler, pullGossiperWhenValidator, pushGossiper, nil -} diff --git a/sae/vm.go b/sae/vm.go index b5e97d363..3c4cff3db 100644 --- a/sae/vm.go +++ b/sae/vm.go @@ -165,11 +165,25 @@ func (vm *VM) Init( if err != nil { return fmt.Errorf("newNetwork(...): %v", err) } - handler, pullGossiper, pushGossiper, err := newGossipers(snowCtx, network, validatorPeers, vm.metrics, vm.mempool) + + systemConfig := gossip.SystemConfig{ + Log: snowCtx.Log, + Registry: vm.metrics, + Namespace: "gossip", + } + systemConfig.SetDefaults() + handler, pullGossiper, pushGossiper, err := gossip.NewSystem( + snowCtx.NodeID, + network, + validatorPeers, + vm.mempool, + txgossip.Marshaller{}, + systemConfig, + ) if err != nil { - return fmt.Errorf("newGossipers(...): %v", err) + return fmt.Errorf("gossip.NewSystem(...): %v", err) } - if err := network.AddHandler(gossipHandlerID, handler); err != nil { + if err := network.AddHandler(systemConfig.HandlerID, handler); err != nil { return fmt.Errorf("network.AddHandler(...): %v", err) } @@ -180,10 +194,11 @@ func (vm *VM) Init( wg.Add(2) go func() { defer wg.Done() - gossip.Every(gossipCtx, snowCtx.Log, pullGossiper, pullRequestPeriod) + gossip.Every(gossipCtx, snowCtx.Log, pullGossiper, systemConfig.RequestPeriod) }() go func() { defer wg.Done() + const pushGossipPeriod = 100 * time.Millisecond gossip.Every(gossipCtx, snowCtx.Log, pushGossiper, pushGossipPeriod) }() From 1842c269e3555353c97e9f966da86fab54a8b907 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Wed, 28 Jan 2026 13:12:14 -0500 Subject: [PATCH 27/35] reduce diff --- sae/rpc.go | 162 ++++++++++++++++++++++++++--------------------------- 1 file changed, 81 insertions(+), 81 deletions(-) diff --git a/sae/rpc.go b/sae/rpc.go index b4fbcfb43..68221dd20 100644 --- a/sae/rpc.go +++ b/sae/rpc.go @@ -44,7 +44,7 @@ func (vm *VM) ethRPCServer() (*rpc.Server, error) { accountManager := accounts.NewManager(&accounts.Config{}) vm.toClose = append(vm.toClose, accountManager.Close) - b := &apiBackend{ + b := ðAPIBackend{ Set: vm.mempool, vm: vm, accountManager: accountManager, @@ -283,118 +283,118 @@ func (s *netAPI) Version() string { } var ( - _ filters.Backend = (*apiBackend)(nil) - _ ethapi.Backend = (*apiBackend)(nil) - _ tracers.Backend = (*apiBackend)(nil) + _ filters.Backend = (*ethAPIBackend)(nil) + _ ethapi.Backend = (*ethAPIBackend)(nil) + _ tracers.Backend = (*ethAPIBackend)(nil) ) -type apiBackend struct { +type ethAPIBackend struct { *txgossip.Set vm *VM accountManager *accounts.Manager } -func (a *apiBackend) SyncProgress() ethereum.SyncProgress { +func (b *ethAPIBackend) SyncProgress() ethereum.SyncProgress { // Avalanchego does not expose APIs until after the node has fully synced. // Just report that syncing is complete. return ethereum.SyncProgress{} } -func (a *apiBackend) SuggestGasTipCap(ctx context.Context) (*big.Int, error) { +func (b *ethAPIBackend) SuggestGasTipCap(ctx context.Context) (*big.Int, error) { panic(errUnimplemented) } -func (a *apiBackend) FeeHistory(ctx context.Context, blockCount uint64, lastBlock rpc.BlockNumber, rewardPercentiles []float64) (*big.Int, [][]*big.Int, []*big.Int, []float64, error) { +func (b *ethAPIBackend) FeeHistory(ctx context.Context, blockCount uint64, lastBlock rpc.BlockNumber, rewardPercentiles []float64) (*big.Int, [][]*big.Int, []*big.Int, []float64, error) { panic(errUnimplemented) } -func (a *apiBackend) ChainDb() ethdb.Database { - return a.vm.db +func (b *ethAPIBackend) ChainDb() ethdb.Database { + return b.vm.db } -func (a *apiBackend) AccountManager() *accounts.Manager { - return a.accountManager +func (b *ethAPIBackend) AccountManager() *accounts.Manager { + return b.accountManager } -func (a *apiBackend) ExtRPCEnabled() bool { +func (b *ethAPIBackend) ExtRPCEnabled() bool { // We never recommend to expose the RPC externally. Additionally, this is // only used as an additional security measure for the personal API, which // we do not support in its entirety. return false } -func (a *apiBackend) RPCGasCap() uint64 { +func (b *ethAPIBackend) RPCGasCap() uint64 { // TODO(StephenButtolph) Expose this as a config. return 25_000_000 } -func (a *apiBackend) RPCEVMTimeout() time.Duration { +func (b *ethAPIBackend) RPCEVMTimeout() time.Duration { // TODO(StephenButtolph) Expose this as a config. return 5 * time.Second } -func (a *apiBackend) CurrentHeader() *types.Header { - return types.CopyHeader(a.vm.exec.LastExecuted().Header()) +func (b *ethAPIBackend) CurrentHeader() *types.Header { + return types.CopyHeader(b.vm.exec.LastExecuted().Header()) } -func (a *apiBackend) CurrentBlock() *types.Header { - return a.CurrentHeader() +func (b *ethAPIBackend) CurrentBlock() *types.Header { + return b.CurrentHeader() } -func (a *apiBackend) GetTd(context.Context, common.Hash) *big.Int { +func (b *ethAPIBackend) GetTd(context.Context, common.Hash) *big.Int { return big.NewInt(0) // TODO(arr4n) } -func (a *apiBackend) RPCTxFeeCap() float64 { +func (b *ethAPIBackend) RPCTxFeeCap() float64 { // TODO(StephenButtolph) Expose this as a config. return 1 // 1 AVAX } -func (a *apiBackend) UnprotectedAllowed() bool { +func (b *ethAPIBackend) UnprotectedAllowed() bool { // TODO(StephenButtolph) Expose this as a config and default to false. return true } -func (a *apiBackend) SetHead(number uint64) { +func (b *ethAPIBackend) SetHead(number uint64) { // SAE does not support reorgs. We ignore any attempts to override the chain // head. - a.vm.log().Warn("ignoring attempt to override the chain head", + b.vm.log().Warn("ignoring attempt to override the chain head", zap.Uint64("number", number), ) } -func (a *apiBackend) HeaderByNumber(ctx context.Context, n rpc.BlockNumber) (*types.Header, error) { - return readByNumber(a, n, rawdb.ReadHeader) +func (b *ethAPIBackend) HeaderByNumber(ctx context.Context, n rpc.BlockNumber) (*types.Header, error) { + return readByNumber(b, n, rawdb.ReadHeader) } -func (a *apiBackend) BlockByNumber(ctx context.Context, n rpc.BlockNumber) (*types.Block, error) { - return readByNumber(a, n, rawdb.ReadBlock) +func (b *ethAPIBackend) BlockByNumber(ctx context.Context, n rpc.BlockNumber) (*types.Block, error) { + return readByNumber(b, n, rawdb.ReadBlock) } -func (a *apiBackend) HeaderByHash(ctx context.Context, hash common.Hash) (*types.Header, error) { - if b, ok := a.vm.blocks.Load(hash); ok { +func (b *ethAPIBackend) HeaderByHash(ctx context.Context, hash common.Hash) (*types.Header, error) { + if b, ok := b.vm.blocks.Load(hash); ok { return b.Header(), nil } - return readByHash(a, hash, rawdb.ReadHeader), nil + return readByHash(b, hash, rawdb.ReadHeader), nil } -func (a *apiBackend) BlockByHash(ctx context.Context, hash common.Hash) (*types.Block, error) { - if b, ok := a.vm.blocks.Load(hash); ok { +func (b *ethAPIBackend) BlockByHash(ctx context.Context, hash common.Hash) (*types.Block, error) { + if b, ok := b.vm.blocks.Load(hash); ok { return b.EthBlock(), nil } - return readByHash(a, hash, rawdb.ReadBlock), nil + return readByHash(b, hash, rawdb.ReadBlock), nil } -func (a *apiBackend) HeaderByNumberOrHash(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (*types.Header, error) { +func (b *ethAPIBackend) HeaderByNumberOrHash(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (*types.Header, error) { panic(errUnimplemented) } -func (a *apiBackend) BlockByNumberOrHash(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (*types.Block, error) { +func (b *ethAPIBackend) BlockByNumberOrHash(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (*types.Block, error) { panic(errUnimplemented) } -func (a *apiBackend) GetTransaction(ctx context.Context, txHash common.Hash) (exists bool, tx *types.Transaction, blockHash common.Hash, blockNumber uint64, index uint64, err error) { - tx, blockHash, blockNumber, index = rawdb.ReadTransaction(a.vm.db, txHash) +func (b *ethAPIBackend) GetTransaction(ctx context.Context, txHash common.Hash) (exists bool, tx *types.Transaction, blockHash common.Hash, blockNumber uint64, index uint64, err error) { + tx, blockHash, blockNumber, index = rawdb.ReadTransaction(b.vm.db, txHash) if tx == nil { return false, nil, common.Hash{}, 0, 0, nil } @@ -403,7 +403,7 @@ func (a *apiBackend) GetTransaction(ctx context.Context, txHash common.Hash) (ex type canonicalReader[T any] func(ethdb.Reader, common.Hash, uint64) *T -func readByNumber[T any](a *apiBackend, n rpc.BlockNumber, read canonicalReader[T]) (*T, error) { +func readByNumber[T any](a *ethAPIBackend, n rpc.BlockNumber, read canonicalReader[T]) (*T, error) { num, err := a.resolveBlockNumber(n) if errors.Is(err, errFutureBlockNotResolved) { return nil, nil @@ -413,7 +413,7 @@ func readByNumber[T any](a *apiBackend, n rpc.BlockNumber, read canonicalReader[ return read(a.vm.db, rawdb.ReadCanonicalHash(a.vm.db, num), num), nil } -func readByHash[T any](a *apiBackend, hash common.Hash, read canonicalReader[T]) *T { +func readByHash[T any](a *ethAPIBackend, hash common.Hash, read canonicalReader[T]) *T { num := rawdb.ReadHeaderNumber(a.vm.db, hash) if num == nil { return nil @@ -423,16 +423,16 @@ func readByHash[T any](a *apiBackend, hash common.Hash, read canonicalReader[T]) var errFutureBlockNotResolved = errors.New("not accepted yet") -func (a *apiBackend) resolveBlockNumber(bn rpc.BlockNumber) (uint64, error) { - head := a.vm.last.accepted.Load().Height() +func (b *ethAPIBackend) resolveBlockNumber(bn rpc.BlockNumber) (uint64, error) { + head := b.vm.last.accepted.Load().Height() switch bn { case rpc.PendingBlockNumber: // i.e. pending execution return head, nil case rpc.LatestBlockNumber: - return a.vm.exec.LastExecuted().Height(), nil + return b.vm.exec.LastExecuted().Height(), nil case rpc.SafeBlockNumber, rpc.FinalizedBlockNumber: - return a.vm.last.settled.Load().Height(), nil + return b.vm.last.settled.Load().Height(), nil } if bn < 0 { @@ -445,53 +445,53 @@ func (a *apiBackend) resolveBlockNumber(bn rpc.BlockNumber) (uint64, error) { } return n, nil } -func (a *apiBackend) Stats() (pending int, queued int) { - return a.Set.Pool.Stats() +func (b *ethAPIBackend) Stats() (pending int, queued int) { + return b.Set.Pool.Stats() } -func (a *apiBackend) TxPoolContent() (map[common.Address][]*types.Transaction, map[common.Address][]*types.Transaction) { - return a.Set.Pool.Content() +func (b *ethAPIBackend) TxPoolContent() (map[common.Address][]*types.Transaction, map[common.Address][]*types.Transaction) { + return b.Set.Pool.Content() } -func (a *apiBackend) TxPoolContentFrom(addr common.Address) ([]*types.Transaction, []*types.Transaction) { - return a.Set.Pool.ContentFrom(addr) +func (b *ethAPIBackend) TxPoolContentFrom(addr common.Address) ([]*types.Transaction, []*types.Transaction) { + return b.Set.Pool.ContentFrom(addr) } -func (a *apiBackend) SubscribeChainEvent(ch chan<- core.ChainEvent) event.Subscription { - return a.vm.exec.SubscribeChainEvent(ch) +func (b *ethAPIBackend) SubscribeChainEvent(ch chan<- core.ChainEvent) event.Subscription { + return b.vm.exec.SubscribeChainEvent(ch) } -func (a *apiBackend) StateAndHeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*state.StateDB, *types.Header, error) { +func (b *ethAPIBackend) StateAndHeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*state.StateDB, *types.Header, error) { panic(errUnimplemented) } -func (a *apiBackend) StateAndHeaderByNumberOrHash(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (*state.StateDB, *types.Header, error) { +func (b *ethAPIBackend) StateAndHeaderByNumberOrHash(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (*state.StateDB, *types.Header, error) { panic(errUnimplemented) } -func (a *apiBackend) PendingBlockAndReceipts() (*types.Block, types.Receipts) { +func (b *ethAPIBackend) PendingBlockAndReceipts() (*types.Block, types.Receipts) { panic(errUnimplemented) } -func (a *apiBackend) GetReceipts(ctx context.Context, hash common.Hash) (types.Receipts, error) { +func (b *ethAPIBackend) GetReceipts(ctx context.Context, hash common.Hash) (types.Receipts, error) { panic(errUnimplemented) } -func (a *apiBackend) GetEVM(ctx context.Context, msg *core.Message, state *state.StateDB, header *types.Header, vmConfig *vm.Config, blockCtx *vm.BlockContext) *vm.EVM { +func (b *ethAPIBackend) GetEVM(ctx context.Context, msg *core.Message, state *state.StateDB, header *types.Header, vmConfig *vm.Config, blockCtx *vm.BlockContext) *vm.EVM { panic(errUnimplemented) } -func (a *apiBackend) SubscribeChainHeadEvent(ch chan<- core.ChainHeadEvent) event.Subscription { - return a.vm.exec.SubscribeChainHeadEvent(ch) +func (b *ethAPIBackend) SubscribeChainHeadEvent(ch chan<- core.ChainHeadEvent) event.Subscription { + return b.vm.exec.SubscribeChainHeadEvent(ch) } -func (*apiBackend) SubscribeChainSideEvent(chan<- core.ChainSideEvent) event.Subscription { +func (*ethAPIBackend) SubscribeChainSideEvent(chan<- core.ChainSideEvent) event.Subscription { // SAE never reorgs, so there are no side events. return newNoopSubscription() } -func (a *apiBackend) GetPoolTransactions() (types.Transactions, error) { - pending := a.Pool.Pending(txpool.PendingFilter{}) +func (b *ethAPIBackend) GetPoolTransactions() (types.Transactions, error) { + pending := b.Pool.Pending(txpool.PendingFilter{}) var pendingCount int for _, batch := range pending { @@ -509,62 +509,62 @@ func (a *apiBackend) GetPoolTransactions() (types.Transactions, error) { return txs, nil } -func (a *apiBackend) GetPoolTransaction(txHash common.Hash) *types.Transaction { - return a.Pool.Get(txHash) +func (b *ethAPIBackend) GetPoolTransaction(txHash common.Hash) *types.Transaction { + return b.Pool.Get(txHash) } -func (a *apiBackend) GetPoolNonce(ctx context.Context, addr common.Address) (uint64, error) { - return a.Pool.Nonce(addr), nil +func (b *ethAPIBackend) GetPoolNonce(ctx context.Context, addr common.Address) (uint64, error) { + return b.Pool.Nonce(addr), nil } -func (a *apiBackend) SubscribeNewTxsEvent(ch chan<- core.NewTxsEvent) event.Subscription { - return a.Set.Pool.SubscribeTransactions(ch, true) +func (b *ethAPIBackend) SubscribeNewTxsEvent(ch chan<- core.NewTxsEvent) event.Subscription { + return b.Set.Pool.SubscribeTransactions(ch, true) } -func (a *apiBackend) ChainConfig() *params.ChainConfig { - return a.vm.exec.ChainConfig() +func (b *ethAPIBackend) ChainConfig() *params.ChainConfig { + return b.vm.exec.ChainConfig() } -func (a *apiBackend) Engine() consensus.Engine { +func (b *ethAPIBackend) Engine() consensus.Engine { panic(errUnimplemented) } -func (a *apiBackend) GetBody(ctx context.Context, hash common.Hash, number rpc.BlockNumber) (*types.Body, error) { +func (b *ethAPIBackend) GetBody(ctx context.Context, hash common.Hash, number rpc.BlockNumber) (*types.Body, error) { panic(errUnimplemented) } -func (a *apiBackend) GetLogs(ctx context.Context, blockHash common.Hash, number uint64) ([][]*types.Log, error) { +func (b *ethAPIBackend) GetLogs(ctx context.Context, blockHash common.Hash, number uint64) ([][]*types.Log, error) { panic(errUnimplemented) } -func (*apiBackend) SubscribeRemovedLogsEvent(chan<- core.RemovedLogsEvent) event.Subscription { +func (*ethAPIBackend) SubscribeRemovedLogsEvent(chan<- core.RemovedLogsEvent) event.Subscription { // SAE never reorgs, so no logs are ever removed. return newNoopSubscription() } -func (a *apiBackend) SubscribeLogsEvent(ch chan<- []*types.Log) event.Subscription { - return a.vm.exec.SubscribeLogsEvent(ch) +func (b *ethAPIBackend) SubscribeLogsEvent(ch chan<- []*types.Log) event.Subscription { + return b.vm.exec.SubscribeLogsEvent(ch) } -func (*apiBackend) SubscribePendingLogsEvent(chan<- []*types.Log) event.Subscription { +func (*ethAPIBackend) SubscribePendingLogsEvent(chan<- []*types.Log) event.Subscription { // In SAE, "pending" refers to the execution status. There are no logs known // for transactions pending execution. return newNoopSubscription() } -func (a *apiBackend) BloomStatus() (uint64, uint64) { +func (b *ethAPIBackend) BloomStatus() (uint64, uint64) { panic(errUnimplemented) } -func (a *apiBackend) ServiceFilter(ctx context.Context, session *bloombits.MatcherSession) { +func (b *ethAPIBackend) ServiceFilter(ctx context.Context, session *bloombits.MatcherSession) { panic(errUnimplemented) } -func (a *apiBackend) StateAtBlock(ctx context.Context, block *types.Block, reexec uint64, base *state.StateDB, readOnly bool, preferDisk bool) (*state.StateDB, tracers.StateReleaseFunc, error) { +func (b *ethAPIBackend) StateAtBlock(ctx context.Context, block *types.Block, reexec uint64, base *state.StateDB, readOnly bool, preferDisk bool) (*state.StateDB, tracers.StateReleaseFunc, error) { panic(errUnimplemented) } -func (a *apiBackend) StateAtTransaction(ctx context.Context, block *types.Block, txIndex int, reexec uint64) (*core.Message, vm.BlockContext, *state.StateDB, tracers.StateReleaseFunc, error) { +func (b *ethAPIBackend) StateAtTransaction(ctx context.Context, block *types.Block, txIndex int, reexec uint64) (*core.Message, vm.BlockContext, *state.StateDB, tracers.StateReleaseFunc, error) { panic(errUnimplemented) } From 26deded1f035dec385c18bbf8486bab1f08666e1 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Wed, 28 Jan 2026 13:12:43 -0500 Subject: [PATCH 28/35] reduce diff --- sae/rpc.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/sae/rpc.go b/sae/rpc.go index 68221dd20..ef41190db 100644 --- a/sae/rpc.go +++ b/sae/rpc.go @@ -403,22 +403,22 @@ func (b *ethAPIBackend) GetTransaction(ctx context.Context, txHash common.Hash) type canonicalReader[T any] func(ethdb.Reader, common.Hash, uint64) *T -func readByNumber[T any](a *ethAPIBackend, n rpc.BlockNumber, read canonicalReader[T]) (*T, error) { - num, err := a.resolveBlockNumber(n) +func readByNumber[T any](b *ethAPIBackend, n rpc.BlockNumber, read canonicalReader[T]) (*T, error) { + num, err := b.resolveBlockNumber(n) if errors.Is(err, errFutureBlockNotResolved) { return nil, nil } else if err != nil { return nil, err } - return read(a.vm.db, rawdb.ReadCanonicalHash(a.vm.db, num), num), nil + return read(b.vm.db, rawdb.ReadCanonicalHash(b.vm.db, num), num), nil } -func readByHash[T any](a *ethAPIBackend, hash common.Hash, read canonicalReader[T]) *T { - num := rawdb.ReadHeaderNumber(a.vm.db, hash) +func readByHash[T any](b *ethAPIBackend, hash common.Hash, read canonicalReader[T]) *T { + num := rawdb.ReadHeaderNumber(b.vm.db, hash) if num == nil { return nil } - return read(a.vm.db, hash, *num) + return read(b.vm.db, hash, *num) } var errFutureBlockNotResolved = errors.New("not accepted yet") From f1c2dbbaee7f595fe40eeb0a266ae6f1cb3e3081 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Wed, 28 Jan 2026 13:14:03 -0500 Subject: [PATCH 29/35] reduce diff --- sae/rpc.go | 52 ++++++++++++++++++++++++++-------------------------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/sae/rpc.go b/sae/rpc.go index ef41190db..d10eefcc0 100644 --- a/sae/rpc.go +++ b/sae/rpc.go @@ -294,6 +294,32 @@ type ethAPIBackend struct { accountManager *accounts.Manager } +func (b *ethAPIBackend) ChainConfig() *params.ChainConfig { + return b.vm.exec.ChainConfig() +} + +func (b *ethAPIBackend) RPCTxFeeCap() float64 { + // TODO(StephenButtolph) Expose this as a config. + return 1 // 1 AVAX +} + +func (b *ethAPIBackend) UnprotectedAllowed() bool { + // TODO(StephenButtolph) Expose this as a config and default to false. + return true +} + +func (b *ethAPIBackend) CurrentHeader() *types.Header { + return types.CopyHeader(b.vm.exec.LastExecuted().Header()) +} + +func (b *ethAPIBackend) CurrentBlock() *types.Header { + return b.CurrentHeader() +} + +func (b *ethAPIBackend) GetTd(context.Context, common.Hash) *big.Int { + return big.NewInt(0) // TODO(arr4n) +} + func (b *ethAPIBackend) SyncProgress() ethereum.SyncProgress { // Avalanchego does not expose APIs until after the node has fully synced. // Just report that syncing is complete. @@ -333,28 +359,6 @@ func (b *ethAPIBackend) RPCEVMTimeout() time.Duration { return 5 * time.Second } -func (b *ethAPIBackend) CurrentHeader() *types.Header { - return types.CopyHeader(b.vm.exec.LastExecuted().Header()) -} - -func (b *ethAPIBackend) CurrentBlock() *types.Header { - return b.CurrentHeader() -} - -func (b *ethAPIBackend) GetTd(context.Context, common.Hash) *big.Int { - return big.NewInt(0) // TODO(arr4n) -} - -func (b *ethAPIBackend) RPCTxFeeCap() float64 { - // TODO(StephenButtolph) Expose this as a config. - return 1 // 1 AVAX -} - -func (b *ethAPIBackend) UnprotectedAllowed() bool { - // TODO(StephenButtolph) Expose this as a config and default to false. - return true -} - func (b *ethAPIBackend) SetHead(number uint64) { // SAE does not support reorgs. We ignore any attempts to override the chain // head. @@ -521,10 +525,6 @@ func (b *ethAPIBackend) SubscribeNewTxsEvent(ch chan<- core.NewTxsEvent) event.S return b.Set.Pool.SubscribeTransactions(ch, true) } -func (b *ethAPIBackend) ChainConfig() *params.ChainConfig { - return b.vm.exec.ChainConfig() -} - func (b *ethAPIBackend) Engine() consensus.Engine { panic(errUnimplemented) } From 08825c59cfc888496c0fea710c25b12d8c4db69c Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Wed, 28 Jan 2026 13:14:39 -0500 Subject: [PATCH 30/35] reduce diff --- sae/rpc.go | 1 + 1 file changed, 1 insertion(+) diff --git a/sae/rpc.go b/sae/rpc.go index d10eefcc0..f8259bfa5 100644 --- a/sae/rpc.go +++ b/sae/rpc.go @@ -449,6 +449,7 @@ func (b *ethAPIBackend) resolveBlockNumber(bn rpc.BlockNumber) (uint64, error) { } return n, nil } + func (b *ethAPIBackend) Stats() (pending int, queued int) { return b.Set.Pool.Stats() } From 515e0c90da9ec3c7c2f465b4eccb8b3647b95cb1 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Wed, 28 Jan 2026 13:15:56 -0500 Subject: [PATCH 31/35] reduce diff --- sae/rpc.go | 56 +++++++++++++++++++++++++++--------------------------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/sae/rpc.go b/sae/rpc.go index f8259bfa5..020d663f2 100644 --- a/sae/rpc.go +++ b/sae/rpc.go @@ -466,6 +466,34 @@ func (b *ethAPIBackend) SubscribeChainEvent(ch chan<- core.ChainEvent) event.Sub return b.vm.exec.SubscribeChainEvent(ch) } +func (b *ethAPIBackend) SubscribeChainHeadEvent(ch chan<- core.ChainHeadEvent) event.Subscription { + return b.vm.exec.SubscribeChainHeadEvent(ch) +} + +func (*ethAPIBackend) SubscribeChainSideEvent(chan<- core.ChainSideEvent) event.Subscription { + // SAE never reorgs, so there are no side events. + return newNoopSubscription() +} + +func (b *ethAPIBackend) SubscribeNewTxsEvent(ch chan<- core.NewTxsEvent) event.Subscription { + return b.Set.Pool.SubscribeTransactions(ch, true) +} + +func (*ethAPIBackend) SubscribeRemovedLogsEvent(chan<- core.RemovedLogsEvent) event.Subscription { + // SAE never reorgs, so no logs are ever removed. + return newNoopSubscription() +} + +func (b *ethAPIBackend) SubscribeLogsEvent(ch chan<- []*types.Log) event.Subscription { + return b.vm.exec.SubscribeLogsEvent(ch) +} + +func (*ethAPIBackend) SubscribePendingLogsEvent(chan<- []*types.Log) event.Subscription { + // In SAE, "pending" refers to the execution status. There are no logs known + // for transactions pending execution. + return newNoopSubscription() +} + func (b *ethAPIBackend) StateAndHeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*state.StateDB, *types.Header, error) { panic(errUnimplemented) } @@ -486,15 +514,6 @@ func (b *ethAPIBackend) GetEVM(ctx context.Context, msg *core.Message, state *st panic(errUnimplemented) } -func (b *ethAPIBackend) SubscribeChainHeadEvent(ch chan<- core.ChainHeadEvent) event.Subscription { - return b.vm.exec.SubscribeChainHeadEvent(ch) -} - -func (*ethAPIBackend) SubscribeChainSideEvent(chan<- core.ChainSideEvent) event.Subscription { - // SAE never reorgs, so there are no side events. - return newNoopSubscription() -} - func (b *ethAPIBackend) GetPoolTransactions() (types.Transactions, error) { pending := b.Pool.Pending(txpool.PendingFilter{}) @@ -522,10 +541,6 @@ func (b *ethAPIBackend) GetPoolNonce(ctx context.Context, addr common.Address) ( return b.Pool.Nonce(addr), nil } -func (b *ethAPIBackend) SubscribeNewTxsEvent(ch chan<- core.NewTxsEvent) event.Subscription { - return b.Set.Pool.SubscribeTransactions(ch, true) -} - func (b *ethAPIBackend) Engine() consensus.Engine { panic(errUnimplemented) } @@ -538,21 +553,6 @@ func (b *ethAPIBackend) GetLogs(ctx context.Context, blockHash common.Hash, numb panic(errUnimplemented) } -func (*ethAPIBackend) SubscribeRemovedLogsEvent(chan<- core.RemovedLogsEvent) event.Subscription { - // SAE never reorgs, so no logs are ever removed. - return newNoopSubscription() -} - -func (b *ethAPIBackend) SubscribeLogsEvent(ch chan<- []*types.Log) event.Subscription { - return b.vm.exec.SubscribeLogsEvent(ch) -} - -func (*ethAPIBackend) SubscribePendingLogsEvent(chan<- []*types.Log) event.Subscription { - // In SAE, "pending" refers to the execution status. There are no logs known - // for transactions pending execution. - return newNoopSubscription() -} - func (b *ethAPIBackend) BloomStatus() (uint64, uint64) { panic(errUnimplemented) } From 00efae5ffc361bedd5acfe43cfa3f295ec27bff5 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Wed, 4 Feb 2026 23:18:52 -0500 Subject: [PATCH 32/35] merged --- sae/rpc.go | 1 - 1 file changed, 1 deletion(-) diff --git a/sae/rpc.go b/sae/rpc.go index 05b2221e2..7d137308e 100644 --- a/sae/rpc.go +++ b/sae/rpc.go @@ -324,7 +324,6 @@ func (b *ethAPIBackend) GetTd(context.Context, common.Hash) *big.Int { func (b *ethAPIBackend) SyncProgress() ethereum.SyncProgress { // Avalanchego does not expose APIs until after the node has fully synced. - // Just report that syncing is complete. return ethereum.SyncProgress{} } From db69f7e003110c4f714a634b8549e2b70c47e676 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Thu, 5 Feb 2026 10:58:28 -0500 Subject: [PATCH 33/35] nit --- sae/rpc.go | 1 + 1 file changed, 1 insertion(+) diff --git a/sae/rpc.go b/sae/rpc.go index 5fd7a2e93..90cef497f 100644 --- a/sae/rpc.go +++ b/sae/rpc.go @@ -290,6 +290,7 @@ func (s *netAPI) Version() string { var _ APIBackend = (*ethAPIBackend)(nil) +// TODO: Rename to apiBackend type ethAPIBackend struct { *txgossip.Set vm *VM From 042feb044e5f81afe3ad7c50d4895e609802a529 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Tue, 10 Feb 2026 11:12:10 -0500 Subject: [PATCH 34/35] reduce diff --- sae/rpc.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sae/rpc.go b/sae/rpc.go index 87592d16f..676b2ed9d 100644 --- a/sae/rpc.go +++ b/sae/rpc.go @@ -213,7 +213,7 @@ func (vm *VM) ethRPCServer() (*rpc.Server, error) { s := rpc.NewServer() if vm.config.RPCConfig.EnableProfiling { - apis = append(apis, + apis = append(apis, api{ // Geth-specific APIs: // - debug_blockProfile // - debug_cpuProfile @@ -235,8 +235,8 @@ func (vm *VM) ethRPCServer() (*rpc.Server, error) { // - debug_writeBlockProfile // - debug_writeMemProfile // - debug_writeMutexProfile - api{"debug", debug.Handler}, - ) + "debug", debug.Handler, + }) } for _, api := range apis { From b43e937d9dc97f6a606d4abdb0300a78b002d273 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Tue, 10 Feb 2026 11:12:51 -0500 Subject: [PATCH 35/35] nit --- sae/vm.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sae/vm.go b/sae/vm.go index 8dbdb35f0..1af322eba 100644 --- a/sae/vm.go +++ b/sae/vm.go @@ -59,7 +59,7 @@ type VM struct { exec *saexec.Executor mempool *txgossip.Set - apiBackend APIBackend + apiBackend *ethAPIBackend newTxs chan struct{} toClose [](func() error)