From 311f60ff4bab3b683d8e3c3cf5c010e8316aca62 Mon Sep 17 00:00:00 2001 From: Anna Shaleva Date: Fri, 14 Jun 2024 22:59:35 +0300 Subject: [PATCH 01/14] t8ntool: properly handle BaseFee set in the env config If user sets BaseFee in the t8ntool environment configuration, then the same value should be present in the Policy storage. Also, ensure that this value is always set by the user or NeoXBurn-enabled networks. Ref https://github.com/bane-labs/go-ethereum/pull/166#discussion_r1629068088, the second option is implemented. Signed-off-by: Anna Shaleva --- cmd/evm/internal/t8ntool/execution.go | 14 +++++--------- cmd/evm/internal/t8ntool/transition.go | 14 ++++++++++++++ 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/cmd/evm/internal/t8ntool/execution.go b/cmd/evm/internal/t8ntool/execution.go index dd7d917ca1..f57a9facb7 100644 --- a/cmd/evm/internal/t8ntool/execution.go +++ b/cmd/evm/internal/t8ntool/execution.go @@ -24,11 +24,11 @@ import ( "github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/consensus/ethash" "github.com/ethereum/go-ethereum/consensus/misc" - "github.com/ethereum/go-ethereum/consensus/misc/eip1559" "github.com/ethereum/go-ethereum/consensus/misc/eip4844" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/state" + "github.com/ethereum/go-ethereum/core/systemcontracts" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/crypto" @@ -157,16 +157,12 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig, GasLimit: pre.Env.GasLimit, GetHash: getHash, } - env := &pre.Env - env.BaseFee = eip1559.CalcBaseFeeDBFT(chainConfig, &types.Header{ - Number: new(big.Int).SetUint64(env.Number - 1), - BaseFee: env.ParentBaseFee, - GasUsed: env.ParentGasUsed, - GasLimit: env.ParentGasLimit, - }, statedb) - // If currentBaseFee is defined, add it to the vmContext. + // If currentBaseFee is defined, add it to the vmContext and to the state DB. if pre.Env.BaseFee != nil { vmContext.BaseFee = new(big.Int).Set(pre.Env.BaseFee) + if chainConfig.IsNeoXBurn(vmContext.BlockNumber, vmContext.Time) { + statedb.SetState(systemcontracts.PolicyProxyHash, systemcontracts.GetBaseFeeStateHash(), common.BigToHash(pre.Env.BaseFee)) + } } // If random is defined, add it to the vmContext. if pre.Env.Random != nil { diff --git a/cmd/evm/internal/t8ntool/transition.go b/cmd/evm/internal/t8ntool/transition.go index b49dee0dc3..ae6421d9e3 100644 --- a/cmd/evm/internal/t8ntool/transition.go +++ b/cmd/evm/internal/t8ntool/transition.go @@ -172,6 +172,9 @@ func Transition(ctx *cli.Context) error { if err := applyLondonChecks(&prestate.Env, chainConfig); err != nil { return err } + if err := applyNeoXBurnChecks(&prestate.Env, chainConfig); err != nil { + return err + } if err := applyShanghaiChecks(&prestate.Env, chainConfig); err != nil { return err } @@ -192,6 +195,17 @@ func Transition(ctx *cli.Context) error { return dispatchOutput(ctx, baseDir, result, collector, body) } +func applyNeoXBurnChecks(env *stEnv, chainConfig *params.ChainConfig) error { + if !chainConfig.IsNeoXBurn(big.NewInt(int64(env.Number)), env.Timestamp) { + return nil + } + // Sanity check, to not `panic` in state_transition + if env.BaseFee == nil { + return NewError(ErrorConfig, errors.New("NeoXBurn config but missing 'currentBaseFee' in env section")) + } + return nil +} + func applyLondonChecks(env *stEnv, chainConfig *params.ChainConfig) error { if !chainConfig.IsLondon(big.NewInt(int64(env.Number))) { return nil From 922b774c0d923ef5593024878a388c6d5068bfc9 Mon Sep 17 00:00:00 2001 From: Anna Shaleva Date: Fri, 14 Jun 2024 23:01:48 +0300 Subject: [PATCH 02/14] consensus: squash VerifyEIP1559Header and VerifyEIP1559HeaderDBFT There's no need in two different methods since it's easy to miss the update of the original Geth method. Make the VerifyEIP1559Header depend on the NeoXBurn hardfork. Signed-off-by: Anna Shaleva --- consensus/dbft/dbft.go | 2 +- consensus/misc/eip1559/eip1559.go | 25 +++++-------------------- 2 files changed, 6 insertions(+), 21 deletions(-) diff --git a/consensus/dbft/dbft.go b/consensus/dbft/dbft.go index 1ce38dd3de..3926e1d37a 100644 --- a/consensus/dbft/dbft.go +++ b/consensus/dbft/dbft.go @@ -936,7 +936,7 @@ func (c *DBFT) verifyCascadingFields(chain consensus.ChainHeaderReader, header * if err := misc.VerifyGaslimit(parent.GasLimit, header.GasLimit); err != nil { return err } - } else if err := eip1559.VerifyEIP1559HeaderDBFT(chain.Config(), parent, header); err != nil { + } else if err := eip1559.VerifyEIP1559Header(chain.Config(), parent, header); err != nil { // Verify the header's EIP-1559 attributes. return err } diff --git a/consensus/misc/eip1559/eip1559.go b/consensus/misc/eip1559/eip1559.go index 013837853b..ffe5887ed7 100644 --- a/consensus/misc/eip1559/eip1559.go +++ b/consensus/misc/eip1559/eip1559.go @@ -46,6 +46,11 @@ func VerifyEIP1559Header(config *params.ChainConfig, parent, header *types.Heade if header.BaseFee == nil { return errors.New("header is missing baseFee") } + // For NeoXBurn block BaseFee verification is performed by consensus nodes and hence + // not included into the state-independent ordinary block verification rules. + if config.IsNeoXBurn(parent.Number, parent.Time) { + return nil + } // Verify the baseFee is correct based on the parent header. expectedBaseFee := CalcBaseFee(config, parent) if header.BaseFee.Cmp(expectedBaseFee) != 0 { @@ -96,26 +101,6 @@ func CalcBaseFee(config *params.ChainConfig, parent *types.Header) *big.Int { } } -// VerifyEIP1559HeaderDBFT verifies some header attributes which were changed in EIP-1559 with dbft consensus, -// - gas limit check -// - basefee check, basefee value check moved to VerifyBlock. -func VerifyEIP1559HeaderDBFT(config *params.ChainConfig, parent, header *types.Header) error { - // Verify that the gas limit remains within allowed bounds - parentGasLimit := parent.GasLimit - if !config.IsLondon(parent.Number) { - parentGasLimit = parent.GasLimit * config.ElasticityMultiplier() - } - if err := misc.VerifyGaslimit(parentGasLimit, header.GasLimit); err != nil { - return err - } - // Verify the header is not malformed - if header.BaseFee == nil { - return errors.New("header is missing baseFee") - } - // Verifing the baseFee is moved to VerifyBlock. - return nil -} - // CalcBaseFeeDBFT calculates the basefee of the header. // if is neoXBurn fork, get basefee from Policy contract. func CalcBaseFeeDBFT(config *params.ChainConfig, parent *types.Header, state *state.StateDB) *big.Int { From ce0c07323728c947b325b8e68f6e72227a479c78 Mon Sep 17 00:00:00 2001 From: Anna Shaleva Date: Fri, 14 Jun 2024 23:04:20 +0300 Subject: [PATCH 03/14] core: adjust the documentation Make the documentation follow the implementation. Signed-off-by: Anna Shaleva --- core/blockchain.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/blockchain.go b/core/blockchain.go index 6ef0109b71..2ff6507adf 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -2473,7 +2473,7 @@ func (bc *BlockChain) getParentState(block *types.Block) (*state.StateDB, *types } // ProcessState processes the state changes according to the Ethereum rules by running -// the transaction messages using the statedb and applying any rewards to both +// the transaction messages using the statedb (if given) and applying any rewards to both // the processor (coinbase) and any included uncles. It doesn't persist any data. func (bc *BlockChain) ProcessState(block *types.Block, statedb *state.StateDB) (*state.StateDB, types.Receipts, []*types.Log, uint64, error) { var err error From e427cda61546c741f0372c413dedd25292ba6f59 Mon Sep 17 00:00:00 2001 From: Anna Shaleva Date: Fri, 14 Jun 2024 23:07:22 +0300 Subject: [PATCH 04/14] gasprice: cache BaseFee and MinGasTipCap inside Oracle service Oracle is capable of working with a full backend only anyway, thus bringing state dependency doesn't make things worse for this service. However, retrieving full state DB for every request is too resource-consuming, we'll end up in a very bad situation if not caching these values. Signed-off-by: Anna Shaleva --- eth/api_backend.go | 8 +---- eth/filters/api.go | 29 +++++++++++++----- eth/gasprice/feehistory.go | 16 +++++----- eth/gasprice/gasprice.go | 55 +++++++++++++++++++++++++---------- eth/gasprice/gasprice_test.go | 2 +- 5 files changed, 71 insertions(+), 39 deletions(-) diff --git a/eth/api_backend.go b/eth/api_backend.go index 95d555ce7c..c371b48107 100644 --- a/eth/api_backend.go +++ b/eth/api_backend.go @@ -31,7 +31,6 @@ import ( "github.com/ethereum/go-ethereum/core/bloombits" "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/state" - "github.com/ethereum/go-ethereum/core/systemcontracts" "github.com/ethereum/go-ethereum/core/txpool" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" @@ -365,15 +364,10 @@ func (b *EthAPIBackend) SyncProgress() ethereum.SyncProgress { } func (b *EthAPIBackend) SuggestGasTipCap(ctx context.Context) (*big.Int, error) { - suggestTipCap, err := b.gpo.SuggestTipCap(ctx) + suggestTipCap, minGasTipCap, err := b.gpo.SuggestTipCap(ctx) if err != nil { return nil, err } - stateDb, _, err := b.StateAndHeaderByNumber(ctx, rpc.LatestBlockNumber) - if err != nil { - return nil, err - } - minGasTipCap := stateDb.GetState(systemcontracts.PolicyProxyHash, systemcontracts.GetMinGasTipCapStateHash()).Big() return cmath.BigMax(suggestTipCap, minGasTipCap), nil } diff --git a/eth/filters/api.go b/eth/filters/api.go index a9b5656034..f6ff660cc0 100644 --- a/eth/filters/api.go +++ b/eth/filters/api.go @@ -164,21 +164,33 @@ func (api *FilterAPI) NewPendingTransactions(ctx context.Context, fullTx *bool) defer pendingTxSub.Unsubscribe() chainConfig := api.sys.backend.ChainConfig() + currHeader := api.sys.backend.CurrentHeader() + state, _, err := api.sys.backend.StateAndHeaderByNumber(context.Background(), rpc.BlockNumber(currHeader.Number.Int64())) + if err != nil { + log.Error("Failed to get state to handle pending transactions", "err", err, "header number", currHeader.Number) + return + } + baseFee := eip1559.CalcBaseFeeDBFT(chainConfig, currHeader, state) // OK for { select { case txs := <-txs: + // Update base fee cache only if new block is available. + if latestHeader := api.sys.backend.CurrentHeader(); latestHeader.Number.Cmp(currHeader.Number) > 0 { + currHeader = latestHeader + state, _, err = api.sys.backend.StateAndHeaderByNumber(context.Background(), rpc.BlockNumber(currHeader.Number.Int64())) + if err != nil { + // We're toast, use the old base fee value. + log.Error("Failed to get state to handle pending transactions", "err", err, "header number", currHeader.Number) + } else { + baseFee = eip1559.CalcBaseFeeDBFT(chainConfig, currHeader, state) + } + } // To keep the original behaviour, send a single tx hash in one notification. // TODO(rjl493456442) Send a batch of tx hashes in one notification - latest := api.sys.backend.CurrentHeader() - state, _, err := api.sys.backend.StateAndHeaderByNumber(context.Background(), rpc.BlockNumber(latest.Number.Int64())) - if err != nil { - log.Error("Failed to get state", "err", err, "header number", latest.Number) - } - baseFee := eip1559.CalcBaseFeeDBFT(chainConfig, latest, state) for _, tx := range txs { if fullTx != nil && *fullTx { - rpcTx := ethapi.NewRPCPendingTransaction(tx, latest, chainConfig, baseFee) + rpcTx := ethapi.NewRPCPendingTransaction(tx, currHeader, chainConfig, baseFee) notifier.Notify(rpcSub.ID, rpcTx) } else { notifier.Notify(rpcSub.ID, tx.Hash()) @@ -438,7 +450,8 @@ func (api *FilterAPI) GetFilterChanges(id rpc.ID) (interface{}, error) { latest := api.sys.backend.CurrentHeader() state, _, err := api.sys.backend.StateAndHeaderByNumber(context.Background(), rpc.BlockNumber(latest.Number.Int64())) if err != nil { - log.Error("Failed to get state", "err", err, "header number", latest.Number) + log.Error("Failed to get state to retrieve filter changes", "err", err, "header number", latest.Number) + return []interface{}{}, err } baseFee := eip1559.CalcBaseFeeDBFT(chainConfig, latest, state) diff --git a/eth/gasprice/feehistory.go b/eth/gasprice/feehistory.go index 6b4ef44301..5c918da363 100644 --- a/eth/gasprice/feehistory.go +++ b/eth/gasprice/feehistory.go @@ -26,7 +26,6 @@ import ( "sync/atomic" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/consensus/misc/eip1559" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/rpc" @@ -77,17 +76,19 @@ type txGasAndReward struct { // processBlock takes a blockFees structure with the blockNumber, the header and optionally // the block field filled in, retrieves the block from the backend if not present yet and // fills in the rest of the fields. -func (oracle *Oracle) processBlock(bf *blockFees, percentiles []float64) { +func (oracle *Oracle) processBlock(ctx context.Context, bf *blockFees, percentiles []float64) { chainconfig := oracle.backend.ChainConfig() if bf.results.baseFee = bf.header.BaseFee; bf.results.baseFee == nil { bf.results.baseFee = new(big.Int) } if chainconfig.IsLondon(big.NewInt(int64(bf.blockNumber + 1))) { - state, _, err := oracle.backend.StateAndHeaderByNumber(context.Background(), rpc.BlockNumber(bf.blockNumber)) + _, baseFee, _, err := oracle.suggestTipCapInternal(ctx, bf.header) if err != nil { - log.Error("Failed to get state", "err", err, "header number", bf.blockNumber) + log.Error(fmt.Sprintf("Failed to calculate BaseFee: %s", err)) + bf.results.nextBaseFee = new(big.Int) + } else { + bf.results.nextBaseFee = baseFee } - bf.results.nextBaseFee = eip1559.CalcBaseFeeDBFT(chainconfig, bf.header, state) } else { bf.results.nextBaseFee = new(big.Int) } @@ -98,7 +99,6 @@ func (oracle *Oracle) processBlock(bf *blockFees, percentiles []float64) { } if bf.block == nil || (bf.receipts == nil && len(bf.block.Transactions()) != 0) { log.Error("Block or receipts are missing while reward percentiles are requested") - return } bf.results.reward = make([]*big.Int, len(percentiles)) @@ -267,7 +267,7 @@ func (oracle *Oracle) FeeHistory(ctx context.Context, blocks uint64, unresolvedL if pendingBlock != nil && blockNumber >= pendingBlock.NumberU64() { fees.block, fees.receipts = pendingBlock, pendingReceipts fees.header = fees.block.Header() - oracle.processBlock(fees, rewardPercentiles) + oracle.processBlock(ctx, fees, rewardPercentiles) results <- fees } else { cacheKey := cacheKey{number: blockNumber, percentiles: string(percentileKey)} @@ -286,7 +286,7 @@ func (oracle *Oracle) FeeHistory(ctx context.Context, blocks uint64, unresolvedL fees.header, fees.err = oracle.backend.HeaderByNumber(ctx, rpc.BlockNumber(blockNumber)) } if fees.header != nil && fees.err == nil { - oracle.processBlock(fees, rewardPercentiles) + oracle.processBlock(ctx, fees, rewardPercentiles) if fees.err == nil { oracle.historyCache.Add(cacheKey, fees.results) } diff --git a/eth/gasprice/gasprice.go b/eth/gasprice/gasprice.go index 9dc4a50f79..f4ba3ab840 100644 --- a/eth/gasprice/gasprice.go +++ b/eth/gasprice/gasprice.go @@ -18,13 +18,16 @@ package gasprice import ( "context" + "fmt" "math/big" "sync" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/lru" + "github.com/ethereum/go-ethereum/consensus/misc/eip1559" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/state" + "github.com/ethereum/go-ethereum/core/systemcontracts" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/log" @@ -62,15 +65,17 @@ type OracleBackend interface { } // Oracle recommends gas prices based on the content of recent -// blocks. Suitable for both light and full clients. +// blocks. Suitable only for full clients. type Oracle struct { - backend OracleBackend - lastHead common.Hash - lastPrice *big.Int - maxPrice *big.Int - ignorePrice *big.Int - cacheLock sync.RWMutex - fetchLock sync.Mutex + backend OracleBackend + lastHead common.Hash + lastPrice *big.Int + lastBaseFee *big.Int // lastBaseFee contains next BaseFee value calculated based on the lastHead block. + lastMinGasTipCap *big.Int // lastMinGasTipCap contains next MinGasTipCap value calculated based on the lastHead block. + maxPrice *big.Int + ignorePrice *big.Int + cacheLock sync.RWMutex + fetchLock sync.Mutex checkBlocks, percentile int maxHeaderHistory, maxBlockHistory uint64 @@ -149,26 +154,34 @@ func NewOracle(backend OracleBackend, params Config) *Oracle { // Note, for legacy transactions and the legacy eth_gasPrice RPC call, it will be // necessary to add the basefee to the returned number to fall back to the legacy // behavior. -func (oracle *Oracle) SuggestTipCap(ctx context.Context) (*big.Int, error) { +func (oracle *Oracle) SuggestTipCap(ctx context.Context) (*big.Int, *big.Int, error) { head, _ := oracle.backend.HeaderByNumber(ctx, rpc.LatestBlockNumber) + + price, _, minGasTipCap, err := oracle.suggestTipCapInternal(ctx, head) + return price, minGasTipCap, err +} + +// suggestTipCapInternal return GAS price, BaseFee and minGasTipCap for the specified block. +// It updates the cache for the specified block height if needed. +func (oracle *Oracle) suggestTipCapInternal(ctx context.Context, head *types.Header) (*big.Int, *big.Int, *big.Int, error) { headHash := head.Hash() // If the latest gasprice is still available, return it. oracle.cacheLock.RLock() - lastHead, lastPrice := oracle.lastHead, oracle.lastPrice + lastHead, lastPrice, lastBaseFee, lastMinGasTipCap := oracle.lastHead, oracle.lastPrice, oracle.lastBaseFee, oracle.lastMinGasTipCap oracle.cacheLock.RUnlock() if headHash == lastHead { - return new(big.Int).Set(lastPrice), nil + return new(big.Int).Set(lastPrice), new(big.Int).Set(lastBaseFee), new(big.Int).Set(lastMinGasTipCap), nil } oracle.fetchLock.Lock() defer oracle.fetchLock.Unlock() // Try checking the cache again, maybe the last fetch fetched what we need oracle.cacheLock.RLock() - lastHead, lastPrice = oracle.lastHead, oracle.lastPrice + lastHead, lastPrice, lastBaseFee, lastMinGasTipCap = oracle.lastHead, oracle.lastPrice, oracle.lastBaseFee, oracle.lastMinGasTipCap oracle.cacheLock.RUnlock() if headHash == lastHead { - return new(big.Int).Set(lastPrice), nil + return new(big.Int).Set(lastPrice), new(big.Int).Set(lastBaseFee), new(big.Int).Set(lastMinGasTipCap), nil } var ( sent, exp int @@ -187,7 +200,7 @@ func (oracle *Oracle) SuggestTipCap(ctx context.Context) (*big.Int, error) { res := <-result if res.err != nil { close(quit) - return new(big.Int).Set(lastPrice), res.err + return new(big.Int).Set(lastPrice), new(big.Int).Set(lastBaseFee), new(big.Int).Set(lastMinGasTipCap), res.err } exp-- // Nothing returned. There are two special cases here: @@ -216,12 +229,24 @@ func (oracle *Oracle) SuggestTipCap(ctx context.Context) (*big.Int, error) { if price.Cmp(oracle.maxPrice) > 0 { price = new(big.Int).Set(oracle.maxPrice) } + if cfg := oracle.backend.ChainConfig(); cfg.IsLondon(head.Number) { + state, _, err := oracle.backend.StateAndHeaderByNumber(ctx, rpc.BlockNumber(head.Number.Uint64())) + if err != nil { + return nil, nil, nil, fmt.Errorf("failed to get state at %d to calculate base fee: %w", head.Number.Uint64(), err) + } + lastBaseFee = eip1559.CalcBaseFeeDBFT(cfg, head, state) // OK + if cfg.IsNeoXBurn(head.Number, head.Time) { + lastMinGasTipCap = state.GetState(systemcontracts.PolicyProxyHash, systemcontracts.GetMinGasTipCapStateHash()).Big() + } + } oracle.cacheLock.Lock() oracle.lastHead = headHash oracle.lastPrice = price + oracle.lastBaseFee = lastBaseFee + oracle.lastMinGasTipCap = lastMinGasTipCap oracle.cacheLock.Unlock() - return new(big.Int).Set(price), nil + return new(big.Int).Set(price), new(big.Int).Set(lastBaseFee), new(big.Int).Set(lastMinGasTipCap), nil } type results struct { diff --git a/eth/gasprice/gasprice_test.go b/eth/gasprice/gasprice_test.go index 72a350b745..1358819fd3 100644 --- a/eth/gasprice/gasprice_test.go +++ b/eth/gasprice/gasprice_test.go @@ -209,7 +209,7 @@ func TestSuggestTipCap(t *testing.T) { oracle := NewOracle(backend, config) // The gas price sampled is: 32G, 31G, 30G, 29G, 28G, 27G - got, err := oracle.SuggestTipCap(context.Background()) + got, _, err := oracle.SuggestTipCap(context.Background()) backend.teardown() if err != nil { t.Fatalf("Failed to retrieve recommended gas price: %v", err) From e8ef782c848839bb194d6ccf0810b4181b4019f3 Mon Sep 17 00:00:00 2001 From: Anna Shaleva Date: Tue, 18 Jun 2024 14:49:42 +0300 Subject: [PATCH 05/14] ethapi: add BaseFee cache Cache BaseFee values for TxPoolAPI and TransactionAPI. Signed-off-by: Anna Shaleva --- internal/ethapi/api.go | 92 ++++++++++++++++++++++++++++++++---------- 1 file changed, 70 insertions(+), 22 deletions(-) diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index 0452c9c5a6..9dc93cf513 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -23,6 +23,7 @@ import ( "fmt" "math/big" "strings" + "sync" "time" "github.com/davecgh/go-spew/spew" @@ -31,6 +32,7 @@ import ( "github.com/ethereum/go-ethereum/accounts/scwallet" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/common/lru" "github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/consensus" "github.com/ethereum/go-ethereum/consensus/misc/eip1559" @@ -55,6 +57,10 @@ import ( // allowed to produce in order to speed up calculations. const estimateGasErrorRatio = 0.015 +// baseFeeCacheCap is a capacity of nextBaseFee cache containing the cached +// BasedFee values calculated for the recently requested heights. +const baseFeeCacheCap = 5 + var errBlobTxNotSupported = errors.New("signing blob transactions not supported") // EthereumAPI provides an API to access Ethereum related information. @@ -160,13 +166,36 @@ func (s *EthereumAPI) Syncing() (interface{}, error) { } // TxPoolAPI offers and API for the transaction pool. It only operates on data that is non-confidential. +// It only operates with a full node backend. type TxPoolAPI struct { b Backend + + // nextBaseFeeCache is an LRU cache of block BaseFee. + nextBaseFeeCache *baseFeeCache +} + +// baseFeeCache is an LRU cache of block BaseFee with capacity of [baseFeeCacheCap]. +type baseFeeCache struct { + b Backend + + fetchLock sync.Mutex + cache *lru.Cache[int64, *big.Int] +} + +// newBaseFeeCache allocates an instance of baseFeeCache with capacity [baseFeeCacheCap]. +func newBaseFeeCache(b Backend) *baseFeeCache { + return &baseFeeCache{ + b: b, + cache: lru.NewCache[int64, *big.Int](baseFeeCacheCap), + } } // NewTxPoolAPI creates a new tx pool service that gives information about the transaction pool. func NewTxPoolAPI(b Backend) *TxPoolAPI { - return &TxPoolAPI{b} + return &TxPoolAPI{ + b: b, + nextBaseFeeCache: newBaseFeeCache(b), + } } // Content returns the transactions contained within the transaction pool. @@ -177,11 +206,7 @@ func (s *TxPoolAPI) Content() map[string]map[string]map[string]*RPCTransaction { } pending, queue := s.b.TxPoolContent() curHeader := s.b.CurrentHeader() - state, _, err := s.b.StateAndHeaderByNumber(context.Background(), rpc.BlockNumber(curHeader.Number.Int64())) - if err != nil { - log.Error("Failed to get state", "err", err, "header number", curHeader.Number) - } - baseFee := eip1559.CalcBaseFeeDBFT(s.b.ChainConfig(), curHeader, state) + baseFee := s.nextBaseFeeCache.getNextBaseFee(curHeader.Number.Int64()) // Flatten the pending transactions for account, txs := range pending { dump := make(map[string]*RPCTransaction) @@ -206,11 +231,7 @@ func (s *TxPoolAPI) ContentFrom(addr common.Address) map[string]map[string]*RPCT content := make(map[string]map[string]*RPCTransaction, 2) pending, queue := s.b.TxPoolContentFrom(addr) curHeader := s.b.CurrentHeader() - state, _, err := s.b.StateAndHeaderByNumber(context.Background(), rpc.BlockNumber(curHeader.Number.Int64())) - if err != nil { - log.Error("Failed to get state", "err", err, "header number", curHeader.Number) - } - baseFee := eip1559.CalcBaseFeeDBFT(s.b.ChainConfig(), curHeader, state) + baseFee := s.nextBaseFeeCache.getNextBaseFee(curHeader.Number.Int64()) // Build the pending transactions dump := make(map[string]*RPCTransaction, len(pending)) for _, tx := range pending { @@ -228,6 +249,33 @@ func (s *TxPoolAPI) ContentFrom(addr common.Address) map[string]map[string]*RPCT return content } +// getNextBaseFee returns next block BaseFee value calculated based on the specified +// height. Cache is used if available. +func (c *baseFeeCache) getNextBaseFee(head int64) *big.Int { + fee, ok := c.cache.Get(head) + if ok { + return fee + } + + c.fetchLock.Lock() + defer c.fetchLock.Unlock() + + // Try one more time if cache was updated by another thread. + fee, ok = c.cache.Get(head) + if ok { + return fee + } + + state, header, err := c.b.StateAndHeaderByNumber(context.Background(), rpc.BlockNumber(head)) + if err != nil { + log.Error("Failed to get state", "err", err, "header number", head) + return new(big.Int) + } + baseFee := eip1559.CalcBaseFeeDBFT(c.b.ChainConfig(), header, state) + c.cache.Add(head, baseFee) + return baseFee +} + // Status returns the number of pending and queued transaction in the pool. func (s *TxPoolAPI) Status() map[string]hexutil.Uint { pending, queue := s.b.Stats() @@ -1575,6 +1623,9 @@ type TransactionAPI struct { b Backend nonceLock *AddrLocker signer types.Signer + + // nextBaseFeeCache is an LRU cache of block BaseFee. + nextBaseFeeCache *baseFeeCache } // NewTransactionAPI creates a new RPC service with methods for interacting with transactions. @@ -1582,7 +1633,12 @@ func NewTransactionAPI(b Backend, nonceLock *AddrLocker) *TransactionAPI { // The signer used by the API should always be the 'latest' known one because we expect // signers to be backwards-compatible with old transactions. signer := types.LatestSigner(b.ChainConfig()) - return &TransactionAPI{b, nonceLock, signer} + return &TransactionAPI{ + b: b, + nonceLock: nonceLock, + signer: signer, + nextBaseFeeCache: newBaseFeeCache(b), + } } // GetBlockTransactionCountByNumber returns the number of transactions in the block with the given block number. @@ -1661,11 +1717,7 @@ func (s *TransactionAPI) GetTransactionByHash(ctx context.Context, hash common.H if !found { // No finalized transaction, try to retrieve it from the pool if tx := s.b.GetPoolTransaction(hash); tx != nil { - state, _, err := s.b.StateAndHeaderByNumber(context.Background(), rpc.BlockNumber(s.b.CurrentHeader().Number.Int64())) - if err != nil { - log.Error("Failed to get state", "err", err, "header number", s.b.CurrentHeader().Number) - } - baseFee := eip1559.CalcBaseFeeDBFT(s.b.ChainConfig(), s.b.CurrentHeader(), state) + baseFee := s.nextBaseFeeCache.getNextBaseFee(s.b.CurrentHeader().Number.Int64()) return NewRPCPendingTransaction(tx, s.b.CurrentHeader(), s.b.ChainConfig(), baseFee), nil } if err == nil { @@ -1952,11 +2004,7 @@ func (s *TransactionAPI) PendingTransactions() ([]*RPCTransaction, error) { } } curHeader := s.b.CurrentHeader() - state, _, err := s.b.StateAndHeaderByNumber(context.Background(), rpc.BlockNumber(curHeader.Number.Int64())) - if err != nil { - log.Error("Failed to get state", "err", err, "header number", curHeader.Number) - } - baseFee := eip1559.CalcBaseFeeDBFT(s.b.ChainConfig(), curHeader, state) + baseFee := s.nextBaseFeeCache.getNextBaseFee(s.b.CurrentHeader().Number.Int64()) transactions := make([]*RPCTransaction, 0, len(pending)) for _, tx := range pending { from, _ := types.Sender(s.signer, tx) From 1d2c47d687bd51130349e7f17ceed807859e1dc1 Mon Sep 17 00:00:00 2001 From: Anna Shaleva Date: Wed, 26 Jun 2024 11:57:11 +0300 Subject: [PATCH 06/14] privnet: enable NeoXBurn hardfork from genesis Signed-off-by: Anna Shaleva --- privnet/four/genesis_privnet.json | 1 + privnet/four/genesis_template.json | 1 + privnet/single/genesis_privnet.json | 1 + privnet/single/genesis_template.json | 1 + 4 files changed, 4 insertions(+) diff --git a/privnet/four/genesis_privnet.json b/privnet/four/genesis_privnet.json index ecc6de7c40..5bcaf007d5 100644 --- a/privnet/four/genesis_privnet.json +++ b/privnet/four/genesis_privnet.json @@ -15,6 +15,7 @@ "arrowGlacierBlock": 0, "grayGlacierBlock": 0, "shanghaiTime": 0, + "neoXBurnTime": 0, "dbft": { "period": 5, "standbyValidators": [ diff --git a/privnet/four/genesis_template.json b/privnet/four/genesis_template.json index 8e3f0e365b..93332bd665 100644 --- a/privnet/four/genesis_template.json +++ b/privnet/four/genesis_template.json @@ -15,6 +15,7 @@ "arrowGlacierBlock": 0, "grayGlacierBlock": 0, "shanghaiTime": 0, + "neoXBurnTime": 0, "dbft": { "period": 5, "standbyValidators": [ diff --git a/privnet/single/genesis_privnet.json b/privnet/single/genesis_privnet.json index cc861ebe3f..aeffd2510b 100644 --- a/privnet/single/genesis_privnet.json +++ b/privnet/single/genesis_privnet.json @@ -15,6 +15,7 @@ "arrowGlacierBlock": 0, "grayGlacierBlock": 0, "shanghaiTime": 0, + "neoXBurnTime": 0, "dbft": { "period": 5, "standbyValidators": [ diff --git a/privnet/single/genesis_template.json b/privnet/single/genesis_template.json index 4a1b9f7f5f..ac0558967e 100644 --- a/privnet/single/genesis_template.json +++ b/privnet/single/genesis_template.json @@ -15,6 +15,7 @@ "arrowGlacierBlock": 0, "grayGlacierBlock": 0, "shanghaiTime": 0, + "neoXBurnTime": 0, "dbft": { "period": 5, "standbyValidators": [ From f005f6b7c31e65dcc0aefb12a715b123f938d2ec Mon Sep 17 00:00:00 2001 From: Anna Shaleva Date: Wed, 26 Jun 2024 12:06:09 +0300 Subject: [PATCH 07/14] eth: remove useless comment Signed-off-by: Anna Shaleva --- eth/filters/api.go | 2 +- eth/gasprice/gasprice.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/eth/filters/api.go b/eth/filters/api.go index f6ff660cc0..fa619859e4 100644 --- a/eth/filters/api.go +++ b/eth/filters/api.go @@ -170,7 +170,7 @@ func (api *FilterAPI) NewPendingTransactions(ctx context.Context, fullTx *bool) log.Error("Failed to get state to handle pending transactions", "err", err, "header number", currHeader.Number) return } - baseFee := eip1559.CalcBaseFeeDBFT(chainConfig, currHeader, state) // OK + baseFee := eip1559.CalcBaseFeeDBFT(chainConfig, currHeader, state) for { select { diff --git a/eth/gasprice/gasprice.go b/eth/gasprice/gasprice.go index f4ba3ab840..64bc2be0af 100644 --- a/eth/gasprice/gasprice.go +++ b/eth/gasprice/gasprice.go @@ -234,7 +234,7 @@ func (oracle *Oracle) suggestTipCapInternal(ctx context.Context, head *types.Hea if err != nil { return nil, nil, nil, fmt.Errorf("failed to get state at %d to calculate base fee: %w", head.Number.Uint64(), err) } - lastBaseFee = eip1559.CalcBaseFeeDBFT(cfg, head, state) // OK + lastBaseFee = eip1559.CalcBaseFeeDBFT(cfg, head, state) if cfg.IsNeoXBurn(head.Number, head.Time) { lastMinGasTipCap = state.GetState(systemcontracts.PolicyProxyHash, systemcontracts.GetMinGasTipCapStateHash()).Big() } From 2016088c4f7e87d73d5319e8f38b5ca54526aa54 Mon Sep 17 00:00:00 2001 From: Anna Shaleva Date: Wed, 26 Jun 2024 11:59:23 +0300 Subject: [PATCH 08/14] gasprice: return in case of nil block This code was accidentally removed in e427cda61546c741f0372c413dedd25292ba6f59. Signed-off-by: Anna Shaleva --- eth/gasprice/feehistory.go | 1 + 1 file changed, 1 insertion(+) diff --git a/eth/gasprice/feehistory.go b/eth/gasprice/feehistory.go index 5c918da363..5d2ee211dd 100644 --- a/eth/gasprice/feehistory.go +++ b/eth/gasprice/feehistory.go @@ -99,6 +99,7 @@ func (oracle *Oracle) processBlock(ctx context.Context, bf *blockFees, percentil } if bf.block == nil || (bf.receipts == nil && len(bf.block.Transactions()) != 0) { log.Error("Block or receipts are missing while reward percentiles are requested") + return } bf.results.reward = make([]*big.Int, len(percentiles)) From 8e44e1d9d8c45a44344c348cde496a02897cbe7e Mon Sep 17 00:00:00 2001 From: Anna Shaleva Date: Wed, 26 Jun 2024 12:06:38 +0300 Subject: [PATCH 09/14] filters: properly process nil current block on pending txs retrieval It's possible that current block is nil, we have to live with it somehow. Fix the following failing test: ``` --- FAIL: TestPendingTxFilterFullTx (1.00s) panic: runtime error: invalid memory address or nil pointer dereference [recovered] panic: runtime error: invalid memory address or nil pointer dereference [signal SIGSEGV: segmentation violation code=0x1 addr=0x1c0 pc=0xdc1715] goroutine 52 [running]: testing.tRunner.func1.2({0xf66340, 0x19f14a0}) /usr/local/go/src/testing/testing.go:1631 +0x24a testing.tRunner.func1() /usr/local/go/src/testing/testing.go:1634 +0x377 panic({0xf66340?, 0x19f14a0?}) /usr/local/go/src/runtime/panic.go:770 +0x132 github.com/ethereum/go-ethereum/eth/filters.(*FilterAPI).GetFilterChanges(0xc0020020c0, {0xc000c320f0, 0x22}) /home/anna/Documents/GitProjects/bane-labs/go-ethereum/eth/filters/api.go:468 +0x235 github.com/ethereum/go-ethereum/eth/filters.TestPendingTxFilterFullTx(0xc0006064e0) /home/anna/Documents/GitProjects/bane-labs/go-ethereum/eth/filters/filter_system_test.go:338 +0x826 testing.tRunner(0xc0006064e0, 0x11fc210) /usr/local/go/src/testing/testing.go:1689 +0xfb created by testing.(*T).Run in goroutine 1 /usr/local/go/src/testing/testing.go:1742 +0x390 FAIL github.com/ethereum/go-ethereum/eth/filters 1.201s ? github.com/ethereum/go-ethereum/eth/gasestimator [no test files] panic: runtime error: invalid memory address or nil pointer dereference [signal SIGSEGV: segmentation violation code=0x1 addr=0x10 pc=0xc061b5] goroutine 25 [running]: math/big.(*Int).Set(...) /usr/local/go/src/math/big/int.go:98 github.com/ethereum/go-ethereum/eth/gasprice.(*Oracle).suggestTipCapInternal(0xc0002a4500, {0x1084ad0, 0x16de8a0}, 0xc000324288) /home/anna/Documents/GitProjects/bane-labs/go-ethereum/eth/gasprice/gasprice.go:249 +0x1fd5 github.com/ethereum/go-ethereum/eth/gasprice.(*Oracle).processBlock(0xc0002a4500, {0x1084ad0, 0x16de8a0}, 0xc0001d6c40, {0x0, 0x0, 0x0?}) /home/anna/Documents/GitProjects/bane-labs/go-ethereum/eth/gasprice/feehistory.go:85 +0x1af github.com/ethereum/go-ethereum/eth/gasprice.(*Oracle).FeeHistory.func1() /home/anna/Documents/GitProjects/bane-labs/go-ethereum/eth/gasprice/feehistory.go:290 +0x605 created by github.com/ethereum/go-ethereum/eth/gasprice.(*Oracle).FeeHistory in goroutine 15 /home/anna/Documents/GitProjects/bane-labs/go-ethereum/eth/gasprice/feehistory.go:259 +0x6c5 FAIL github.com/ethereum/go-ethereum/eth/gasprice 0.030s ``` Signed-off-by: Anna Shaleva --- eth/filters/api.go | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/eth/filters/api.go b/eth/filters/api.go index fa619859e4..82b60d7fba 100644 --- a/eth/filters/api.go +++ b/eth/filters/api.go @@ -448,12 +448,6 @@ func (api *FilterAPI) GetFilterChanges(id rpc.ID) (interface{}, error) { chainConfig := api.sys.backend.ChainConfig() latest := api.sys.backend.CurrentHeader() - state, _, err := api.sys.backend.StateAndHeaderByNumber(context.Background(), rpc.BlockNumber(latest.Number.Int64())) - if err != nil { - log.Error("Failed to get state to retrieve filter changes", "err", err, "header number", latest.Number) - return []interface{}{}, err - } - baseFee := eip1559.CalcBaseFeeDBFT(chainConfig, latest, state) if f, found := api.filters[id]; found { if !f.deadline.Stop() { @@ -471,6 +465,15 @@ func (api *FilterAPI) GetFilterChanges(id rpc.ID) (interface{}, error) { case PendingTransactionsSubscription: if f.fullTx { txs := make([]*ethapi.RPCTransaction, 0, len(f.txs)) + var baseFee *big.Int + if latest != nil { + state, _, err := api.sys.backend.StateAndHeaderByNumber(context.Background(), rpc.BlockNumber(latest.Number.Int64())) + if err != nil { + log.Error("Failed to get state to retrieve filter changes", "err", err, "header number", latest.Number) + return txs, err + } + baseFee = eip1559.CalcBaseFeeDBFT(chainConfig, latest, state) + } for _, tx := range f.txs { txs = append(txs, ethapi.NewRPCPendingTransaction(tx, latest, chainConfig, baseFee)) } From 5a90cd95d1953d4a1d2cb2f9ddd988d3606333f1 Mon Sep 17 00:00:00 2001 From: Anna Shaleva Date: Wed, 26 Jun 2024 12:26:32 +0300 Subject: [PATCH 10/14] t8ntool: get back the baseFee setter for London MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Get back the code removed in fb84a68095e31b2998316a366faffd88b3445147. If it's London, then BaseFee should still be calculated based on the parent's fee. If it's NeoXBurn, then just ensure that BaseFee is set in the config (this check is already present in the applyNeoXBurnChecks. Fix the following test: ``` --- FAIL: TestT8n (0.39s) t8n_test.go:299: args: t8n --output.body "" --output.result stdout --output.alloc stdout --input.alloc ./testdata/1/alloc.json --input.txs ./testdata/1/txs.json --input.env ./testdata/1/env.json --state.fork Frontier+1346 test_cmd.go:261: (stderr:1) ERROR(3): failed constructing chain configuration: syntax error, invalid eip number 1346 t8n_test.go:299: args: t8n --output.body "" --output.result stdout --output.alloc stdout --input.alloc ./testdata/1/alloc.json --input.txs ./testdata/1/txs.json --input.env ./testdata/1/env.json --state.fork Byzantium test_cmd.go:261: (stderr:6) INFO [06-25|16:52:13.126] rejected tx index=1 hash=0557ba..18d673 from=0x8A8eAFb1cf62BfBeb1741769DAE1a9dd47996192 error="nonce too low: address 0x8A8eAFb1cf62BfBeb1741769DAE1a9dd47996192, tx: 0 state: 1" test_cmd.go:261: (stderr:6) INFO [06-25|16:52:13.126] Trie dumping started root=84208a..ae4e13 test_cmd.go:261: (stderr:6) INFO [06-25|16:52:13.126] Trie dumping complete accounts=3 elapsed="60.108µs" t8n_test.go:299: args: t8n --output.body "" --output.result stdout --output.alloc stdout --input.alloc ./testdata/3/alloc.json --input.txs ./testdata/3/txs.json --input.env ./testdata/3/env.json --state.fork Berlin test_cmd.go:261: (stderr:9) INFO [06-25|16:52:13.152] Trie dumping started root=b7341d..857ea1 test_cmd.go:261: (stderr:9) INFO [06-25|16:52:13.153] Trie dumping complete accounts=3 elapsed="90.49µs" t8n_test.go:299: args: t8n --output.body "" --output.result stdout --output.alloc stdout --input.alloc ./testdata/4/alloc.json --input.txs ./testdata/4/txs.json --input.env ./testdata/4/env.json --state.fork Berlin test_cmd.go:261: (stderr:12) ERROR(4): getHash(3) invoked, blockhash for that block not provided t8n_test.go:299: args: t8n --output.body "" --output.result stdout --output.alloc stdout --input.alloc ./testdata/5/alloc.json --input.txs ./testdata/5/txs.json --input.env ./testdata/5/env.json --state.fork Byzantium --state.reward 0x80 test_cmd.go:261: (stderr:15) INFO [06-25|16:52:13.205] Trie dumping started root=a7312a..c1f393 test_cmd.go:261: (stderr:15) INFO [06-25|16:52:13.205] Trie dumping complete accounts=3 elapsed="70.846µs" t8n_test.go:299: args: t8n --output.body stdout --output.result "" --output.alloc "" --input.alloc ./testdata/13/alloc.json --input.txs ./testdata/13/txs.json --input.env ./testdata/13/env.json --state.fork London test_cmd.go:261: (stderr:16) INFO [06-25|16:52:13.234] Trie dumping started root=e4b924..6aef61 test_cmd.go:261: (stderr:16) INFO [06-25|16:52:13.234] Trie dumping complete accounts=3 elapsed="104.878µs" t8n_test.go:299: args: t8n --output.body "" --output.result stdout --output.alloc "" --input.alloc ./testdata/13/alloc.json --input.txs ./testdata/13/signed_txs.rlp --input.env ./testdata/13/env.json --state.fork London test_cmd.go:261: (stderr:18) INFO [06-25|16:52:13.256] Trie dumping started root=e4b924..6aef61 test_cmd.go:261: (stderr:18) INFO [06-25|16:52:13.256] Trie dumping complete accounts=3 elapsed="61.682µs" t8n_test.go:299: args: t8n --output.body "" --output.result stdout --output.alloc "" --input.alloc ./testdata/14/alloc.json --input.txs ./testdata/14/txs.json --input.env ./testdata/14/env.json --state.fork London test_cmd.go:261: (stderr:19) INFO [06-25|16:52:13.277] Trie dumping started root=6f0588..7f4bdc test_cmd.go:261: (stderr:19) INFO [06-25|16:52:13.277] Trie dumping complete accounts=2 elapsed="65.13µs" t8n_test.go:299: args: t8n --output.body "" --output.result stdout --output.alloc "" --input.alloc ./testdata/14/alloc.json --input.txs ./testdata/14/txs.json --input.env ./testdata/14/env.uncles.json --state.fork London test_cmd.go:261: (stderr:20) INFO [06-25|16:52:13.298] Trie dumping started root=6f0588..7f4bdc test_cmd.go:261: (stderr:20) INFO [06-25|16:52:13.298] Trie dumping complete accounts=2 elapsed="54.479µs" t8n_test.go:299: args: t8n --output.body "" --output.result stdout --output.alloc "" --input.alloc ./testdata/14/alloc.json --input.txs ./testdata/14/txs.json --input.env ./testdata/14/env.uncles.json --state.fork Berlin test_cmd.go:261: (stderr:21) INFO [06-25|16:52:13.318] Trie dumping started root=6f0588..7f4bdc test_cmd.go:261: (stderr:21) INFO [06-25|16:52:13.318] Trie dumping complete accounts=2 elapsed="53.961µs" t8n_test.go:299: args: t8n --output.body "" --output.result stdout --output.alloc "" --input.alloc ./testdata/19/alloc.json --input.txs ./testdata/19/txs.json --input.env ./testdata/19/env.json --state.fork London test_cmd.go:261: (stderr:22) INFO [06-25|16:52:13.338] Trie dumping started root=6f0588..7f4bdc test_cmd.go:261: (stderr:22) INFO [06-25|16:52:13.338] Trie dumping complete accounts=2 elapsed="62.368µs" t8n_test.go:299: args: t8n --output.body "" --output.result stdout --output.alloc "" --input.alloc ./testdata/19/alloc.json --input.txs ./testdata/19/txs.json --input.env ./testdata/19/env.json --state.fork ArrowGlacier test_cmd.go:261: (stderr:23) INFO [06-25|16:52:13.359] Trie dumping started root=6f0588..7f4bdc test_cmd.go:261: (stderr:23) INFO [06-25|16:52:13.359] Trie dumping complete accounts=2 elapsed="62.569µs" t8n_test.go:299: args: t8n --output.body "" --output.result stdout --output.alloc "" --input.alloc ./testdata/19/alloc.json --input.txs ./testdata/19/txs.json --input.env ./testdata/19/env.json --state.fork GrayGlacier test_cmd.go:261: (stderr:24) INFO [06-25|16:52:13.379] Trie dumping started root=6f0588..7f4bdc test_cmd.go:261: (stderr:24) INFO [06-25|16:52:13.379] Trie dumping complete accounts=2 elapsed="66.893µs" t8n_test.go:299: args: t8n --output.body "" --output.result stdout --output.alloc "" --input.alloc ./testdata/23/alloc.json --input.txs ./testdata/23/txs.json --input.env ./testdata/23/env.json --state.fork Berlin test_cmd.go:261: (stderr:25) INFO [06-25|16:52:13.401] Trie dumping started root=653343..95154d test_cmd.go:261: (stderr:25) INFO [06-25|16:52:13.401] Trie dumping complete accounts=3 elapsed="56.934µs" t8n_test.go:299: args: t8n --output.body "" --output.result stdout --output.alloc stdout --input.alloc ./testdata/24/alloc.json --input.txs ./testdata/24/txs.json --input.env ./testdata/24/env.json --state.fork Merge test_cmd.go:261: (stderr:26) INFO [06-25|16:52:13.421] Trie dumping started root=9e4224..043c15 test_cmd.go:261: (stderr:26) INFO [06-25|16:52:13.421] Trie dumping complete accounts=3 elapsed="65.042µs" t8n_test.go:299: args: t8n --output.body "" --output.result "" --output.alloc "" --input.alloc ./testdata/24/alloc.json --input.txs ./testdata/24/txs.json --input.env ./testdata/24/env-missingrandom.json --state.fork Merge test_cmd.go:261: (stderr:27) ERROR(3): post-merge requires currentRandom to be defined in env t8n_test.go:299: args: t8n --output.body "" --output.result stdout --output.alloc stdout --input.alloc ./testdata/25/alloc.json --input.txs ./testdata/25/txs.json --input.env ./testdata/25/env.json --state.fork Merge test_cmd.go:261: (stderr:28) panic: runtime error: invalid memory address or nil pointer dereference test_cmd.go:261: (stderr:28) [signal SIGSEGV: segmentation violation code=0x1 addr=0x0 pc=0x5f5dbb] test_cmd.go:261: (stderr:28) goroutine 1 [running]: test_cmd.go:261: (stderr:28) math/big.(*Int).Cmp(0xc00057da40?, 0x0?) test_cmd.go:261: (stderr:28) /usr/local/go/src/math/big/int.go:381 +0x1b test_cmd.go:261: (stderr:28) github.com/ethereum/go-ethereum/core.(*StateTransition).preCheck(0xc00028ebb0) test_cmd.go:261: (stderr:28) /home/anna/Documents/GitProjects/bane-labs/go-ethereum/core/state_transition.go:323 +0x6f2 test_cmd.go:261: (stderr:28) github.com/ethereum/go-ethereum/core.(*StateTransition).TransitionDb(0xc00028ebb0) test_cmd.go:261: (stderr:28) /home/anna/Documents/GitProjects/bane-labs/go-ethereum/core/state_transition.go:396 +0x51 test_cmd.go:261: (stderr:28) github.com/ethereum/go-ethereum/core.ApplyMessage(0x152ada8?, 0x152adb0?, 0xc000579158?) test_cmd.go:261: (stderr:28) /home/anna/Documents/GitProjects/bane-labs/go-ethereum/core/state_transition.go:184 +0x57 test_cmd.go:261: (stderr:28) github.com/ethereum/go-ethereum/cmd/evm/internal/t8ntool.(*Prestate).Apply(0xc0003ea2d0, {{0x0, 0x0}, 0x0, 0x0, {0x0, 0x0, 0x0}}, 0xc0004ad6c0, {0x1694ef8, ...}, ...) test_cmd.go:261: (stderr:28) /home/anna/Documents/GitProjects/bane-labs/go-ethereum/cmd/evm/internal/t8ntool/execution.go:243 +0x17cf test_cmd.go:261: (stderr:28) github.com/ethereum/go-ethereum/cmd/evm/internal/t8ntool.Transition(0xc000267300) test_cmd.go:261: (stderr:28) /home/anna/Documents/GitProjects/bane-labs/go-ethereum/cmd/evm/internal/t8ntool/transition.go:188 +0xb65 test_cmd.go:261: (stderr:28) github.com/ethereum/go-ethereum/internal/flags.MigrateGlobalFlags.func2.1(0xc000267300) test_cmd.go:261: (stderr:28) /home/anna/Documents/GitProjects/bane-labs/go-ethereum/internal/flags/helpers.go:100 +0x34 test_cmd.go:261: (stderr:28) github.com/urfave/cli/v2.(*Command).Run(0x1f59480, 0xc000267300, {0xc0003ea1e0, 0xf, 0xf}) test_cmd.go:261: (stderr:28) /home/anna/go/pkg/mod/github.com/urfave/cli/v2@v2.25.7/command.go:274 +0x93f test_cmd.go:261: (stderr:28) github.com/urfave/cli/v2.(*Command).Run(0xc00044eb00, 0xc000266040, {0xc000040100, 0x10, 0x10}) test_cmd.go:261: (stderr:28) /home/anna/go/pkg/mod/github.com/urfave/cli/v2@v2.25.7/command.go:267 +0xb7d test_cmd.go:261: (stderr:28) github.com/urfave/cli/v2.(*App).RunContext( test_cmd.go:261: (stderr:28) 0xc00016c5a0, {0x169aad0, 0x2166400}, {0xc000040100, 0x10, 0x10}) test_cmd.go:261: (stderr:28) /home/anna/go/pkg/mod/github.com/urfave/cli/v2@v2.25.7/app.go:332 +0x566 test_cmd.go:261: (stderr:28) github.com/urfave/cli/v2.(*App).Run(...) test_cmd.go:261: (stderr:28) /home/anna/go/pkg/mod/github.com/urfave/cli/v2@v2.25.7/app.go:309 test_cmd.go:261: (stderr:28) github.com/ethereum/go-ethereum/cmd/evm.TestMain.func1() test_cmd.go:261: (stderr:28) /home/anna/Documents/GitProjects/bane-labs/go-ethereum/cmd/evm/t8n_test.go:35 +0x45 test_cmd.go:261: (stderr:28) github.com/ethereum/go-ethereum/internal/reexec.Init(...) test_cmd.go:261: (stderr:28) /home/anna/Documents/GitProjects/bane-labs/go-ethereum/internal/reexec/reexec.go:31 test_cmd.go:261: (stderr:28) github.com/ethereum/go-ethereum/cmd/evm.TestMain(0xc00043e280) test_cmd.go:261: (stderr:28) /home/anna/Documents/GitProjects/bane-labs/go-ethereum/cmd/evm/t8n_test.go:42 +0x67 test_cmd.go:261: (stderr:28) main.main() test_cmd.go:261: (stderr:28) _testmain.go:53 +0x195 t8n_test.go:312: test 16, file ./testdata/25/exp.json: json parsing failed: unexpected end of JSON input FAIL FAIL github.com/ethereum/go-ethereum/cmd/evm 0.413s ``` Signed-off-by: Anna Shaleva --- cmd/evm/internal/t8ntool/transition.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/cmd/evm/internal/t8ntool/transition.go b/cmd/evm/internal/t8ntool/transition.go index ae6421d9e3..fa17e8856a 100644 --- a/cmd/evm/internal/t8ntool/transition.go +++ b/cmd/evm/internal/t8ntool/transition.go @@ -26,8 +26,10 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/consensus/misc/eip1559" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/state" + "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/eth/tracers" "github.com/ethereum/go-ethereum/eth/tracers/logger" @@ -218,6 +220,12 @@ func applyLondonChecks(env *stEnv, chainConfig *params.ChainConfig) error { if env.ParentBaseFee == nil || env.Number == 0 { return NewError(ErrorConfig, errors.New("EIP-1559 config but missing 'currentBaseFee' in env section")) } + env.BaseFee = eip1559.CalcBaseFee(chainConfig, &types.Header{ + Number: new(big.Int).SetUint64(env.Number - 1), + BaseFee: env.ParentBaseFee, + GasUsed: env.ParentGasUsed, + GasLimit: env.ParentGasLimit, + }) return nil } From 5f8252ec85e42c997a84ff6786f2c02c65907641 Mon Sep 17 00:00:00 2001 From: Anna Shaleva Date: Wed, 26 Jun 2024 12:48:49 +0300 Subject: [PATCH 11/14] gasprice: do not keep Oracle's lastBaseFee and lastMinGasTip empty Initialize these values if London or NeoXBurn are not active yet. Otherwise gas price related RPC handlers won't work properly with London or NeoXBurn disabled. Fix the following error in tests for `eth_gasPrice` handler ``` --- FAIL: TestEthClient (0.24s) --- FAIL: TestEthClient/StatusFunctions (0.00s) ethclient_test.go:503: unexpected error: method handler crashed FAIL FAIL github.com/ethereum/go-ethereum/ethclient 0.258s ``` Signed-off-by: Anna Shaleva --- eth/gasprice/gasprice.go | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/eth/gasprice/gasprice.go b/eth/gasprice/gasprice.go index 64bc2be0af..2bbb1000d6 100644 --- a/eth/gasprice/gasprice.go +++ b/eth/gasprice/gasprice.go @@ -162,7 +162,8 @@ func (oracle *Oracle) SuggestTipCap(ctx context.Context) (*big.Int, *big.Int, er } // suggestTipCapInternal return GAS price, BaseFee and minGasTipCap for the specified block. -// It updates the cache for the specified block height if needed. +// It updates the cache for the specified block height if needed. Zero BaseFee is returned if +// London is not active yet. Zero minGasTipCap is returned if NeoXBurn is not active yet. func (oracle *Oracle) suggestTipCapInternal(ctx context.Context, head *types.Header) (*big.Int, *big.Int, *big.Int, error) { headHash := head.Hash() @@ -237,7 +238,12 @@ func (oracle *Oracle) suggestTipCapInternal(ctx context.Context, head *types.Hea lastBaseFee = eip1559.CalcBaseFeeDBFT(cfg, head, state) if cfg.IsNeoXBurn(head.Number, head.Time) { lastMinGasTipCap = state.GetState(systemcontracts.PolicyProxyHash, systemcontracts.GetMinGasTipCapStateHash()).Big() + } else { + lastMinGasTipCap = new(big.Int) } + } else { + lastBaseFee = new(big.Int) + lastMinGasTipCap = new(big.Int) } oracle.cacheLock.Lock() oracle.lastHead = headHash From 47d271238a77ddc36865ca697f3e2aff8e9d87e1 Mon Sep 17 00:00:00 2001 From: Anna Shaleva Date: Tue, 2 Jul 2024 14:14:10 +0300 Subject: [PATCH 12/14] gasprice: move policy-based minGasPrice retrieval our of NeoXBurn Bug introduced by e427cda. Signed-off-by: Anna Shaleva --- eth/gasprice/gasprice.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eth/gasprice/gasprice.go b/eth/gasprice/gasprice.go index 2bbb1000d6..20b0853103 100644 --- a/eth/gasprice/gasprice.go +++ b/eth/gasprice/gasprice.go @@ -236,7 +236,7 @@ func (oracle *Oracle) suggestTipCapInternal(ctx context.Context, head *types.Hea return nil, nil, nil, fmt.Errorf("failed to get state at %d to calculate base fee: %w", head.Number.Uint64(), err) } lastBaseFee = eip1559.CalcBaseFeeDBFT(cfg, head, state) - if cfg.IsNeoXBurn(head.Number, head.Time) { + if cfg.DBFT != nil { lastMinGasTipCap = state.GetState(systemcontracts.PolicyProxyHash, systemcontracts.GetMinGasTipCapStateHash()).Big() } else { lastMinGasTipCap = new(big.Int) From 97a454b3edf05a9bbece9a727cef1e281bf8fa5a Mon Sep 17 00:00:00 2001 From: Anna Shaleva Date: Fri, 5 Jul 2024 18:43:32 +0300 Subject: [PATCH 13/14] t8ntool: extend the list of valid forks with NeoXBurn Fix https://github.com/bane-labs/go-ethereum/pull/230#issuecomment-2208760497, t8ntool needs state.fork to be specified to create proper config, the default value for this CLI flag is GrayGlacier. Signed-off-by: Anna Shaleva --- tests/init.go | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/tests/init.go b/tests/init.go index 99b7e4d333..54f98f4164 100644 --- a/tests/init.go +++ b/tests/init.go @@ -281,6 +281,25 @@ var Forks = map[string]*params.ChainConfig{ TerminalTotalDifficulty: big.NewInt(0), ShanghaiTime: u64(0), }, + "NeoXBurn": { + ChainID: big.NewInt(1), + HomesteadBlock: big.NewInt(0), + EIP150Block: big.NewInt(0), + EIP155Block: big.NewInt(0), + EIP158Block: big.NewInt(0), + ByzantiumBlock: big.NewInt(0), + ConstantinopleBlock: big.NewInt(0), + PetersburgBlock: big.NewInt(0), + IstanbulBlock: big.NewInt(0), + MuirGlacierBlock: big.NewInt(0), + BerlinBlock: big.NewInt(0), + LondonBlock: big.NewInt(0), + ArrowGlacierBlock: big.NewInt(0), + MergeNetsplitBlock: big.NewInt(0), + TerminalTotalDifficulty: big.NewInt(0), + ShanghaiTime: u64(0), + NeoXBurnTime: u64(0), + }, "MergeToShanghaiAtTime15k": { ChainID: big.NewInt(1), HomesteadBlock: big.NewInt(0), From 458efcb912c573ece3d17f810cea179b5f91d676 Mon Sep 17 00:00:00 2001 From: Anna Shaleva Date: Mon, 8 Jul 2024 15:50:30 +0300 Subject: [PATCH 14/14] eth: return Policy's minGasTipCap from suggestTipCap If minGasTipCap is available in Policy, then return this value from SuggestGasTipCap. Ref. https://github.com/bane-labs/go-ethereum/pull/230#issuecomment-2212888965. Signed-off-by: Anna Shaleva --- eth/api_backend.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/eth/api_backend.go b/eth/api_backend.go index c371b48107..c26fa34707 100644 --- a/eth/api_backend.go +++ b/eth/api_backend.go @@ -25,7 +25,6 @@ import ( "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/common" - cmath "github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/consensus" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/bloombits" @@ -368,7 +367,10 @@ func (b *EthAPIBackend) SuggestGasTipCap(ctx context.Context) (*big.Int, error) if err != nil { return nil, err } - return cmath.BigMax(suggestTipCap, minGasTipCap), nil + if minGasTipCap != nil && minGasTipCap.Sign() > 0 { + return minGasTipCap, nil + } + return suggestTipCap, nil } func (b *EthAPIBackend) FeeHistory(ctx context.Context, blockCount uint64, lastBlock rpc.BlockNumber, rewardPercentiles []float64) (firstBlock *big.Int, reward [][]*big.Int, baseFee []*big.Int, gasUsedRatio []float64, err error) {