Merge branch 'ethereum:master' into master

This commit is contained in:
CertiK 2025-10-17 17:39:27 +08:00 committed by GitHub
commit 5ffb60402f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
54 changed files with 2081 additions and 453 deletions

View file

@ -485,13 +485,13 @@ var bindTests = []struct {
contract Defaulter { contract Defaulter {
address public caller; address public caller;
function() { fallback() external payable {
caller = msg.sender; caller = msg.sender;
} }
} }
`, `,
[]string{`6060604052606a8060106000396000f360606040523615601d5760e060020a6000350463fc9c8d3981146040575b605e6000805473ffffffffffffffffffffffffffffffffffffffff191633179055565b606060005473ffffffffffffffffffffffffffffffffffffffff1681565b005b6060908152602090f3`}, []string{`608060405234801561000f575f80fd5b5061013d8061001d5f395ff3fe608060405260043610610021575f3560e01c8063fc9c8d391461006257610022565b5b335f806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055005b34801561006d575f80fd5b5061007661008c565b60405161008391906100ee565b60405180910390f35b5f8054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f6100d8826100af565b9050919050565b6100e8816100ce565b82525050565b5f6020820190506101015f8301846100df565b9291505056fea26469706673582212201e9273ecfb1f534644c77f09a25c21baaba81cf1c444ebc071e12a225a23c72964736f6c63430008140033`},
[]string{`[{"constant":true,"inputs":[],"name":"caller","outputs":[{"name":"","type":"address"}],"type":"function"}]`}, []string{`[{"stateMutability":"payable","type":"fallback"},{"inputs":[],"name":"caller","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"}]`},
` `
"math/big" "math/big"

View file

@ -277,8 +277,10 @@ func (c *BoundContract) RawCreationTransact(opts *TransactOpts, calldata []byte)
// Transfer initiates a plain transaction to move funds to the contract, calling // Transfer initiates a plain transaction to move funds to the contract, calling
// its default method if one is available. // its default method if one is available.
func (c *BoundContract) Transfer(opts *TransactOpts) (*types.Transaction, error) { func (c *BoundContract) Transfer(opts *TransactOpts) (*types.Transaction, error) {
// todo(rjl493456442) check the payable fallback or receive is defined // Check if payable fallback or receive is defined
// or not, reject invalid transaction at the first place if !c.abi.HasReceive() && !(c.abi.HasFallback() && c.abi.Fallback.IsPayable()) {
return nil, fmt.Errorf("contract does not have a payable fallback or receive function")
}
return c.transact(opts, &c.address, nil) return c.transact(opts, &c.address, nil)
} }

View file

@ -188,6 +188,8 @@ var (
utils.AllowUnprotectedTxs, utils.AllowUnprotectedTxs,
utils.BatchRequestLimit, utils.BatchRequestLimit,
utils.BatchResponseMaxSize, utils.BatchResponseMaxSize,
utils.RPCTxSyncDefaultTimeoutFlag,
utils.RPCTxSyncMaxTimeoutFlag,
} }
metricsFlags = []cli.Flag{ metricsFlags = []cli.Flag{

View file

@ -201,7 +201,7 @@ func expandVerkle(ctx *cli.Context) error {
} }
for i, key := range keylist { for i, key := range keylist {
log.Info("Reading key", "index", i, "key", keylist[0]) log.Info("Reading key", "index", i, "key", key)
root.Get(key, chaindb.Get) root.Get(key, chaindb.Get)
} }

View file

@ -615,6 +615,18 @@ var (
Value: ethconfig.Defaults.LogQueryLimit, Value: ethconfig.Defaults.LogQueryLimit,
Category: flags.APICategory, Category: flags.APICategory,
} }
RPCTxSyncDefaultTimeoutFlag = &cli.DurationFlag{
Name: "rpc.txsync.defaulttimeout",
Usage: "Default timeout for eth_sendRawTransactionSync (e.g. 2s, 500ms)",
Value: ethconfig.Defaults.TxSyncDefaultTimeout,
Category: flags.APICategory,
}
RPCTxSyncMaxTimeoutFlag = &cli.DurationFlag{
Name: "rpc.txsync.maxtimeout",
Usage: "Maximum allowed timeout for eth_sendRawTransactionSync (e.g. 5m)",
Value: ethconfig.Defaults.TxSyncMaxTimeout,
Category: flags.APICategory,
}
// Authenticated RPC HTTP settings // Authenticated RPC HTTP settings
AuthListenFlag = &cli.StringFlag{ AuthListenFlag = &cli.StringFlag{
Name: "authrpc.addr", Name: "authrpc.addr",
@ -1717,6 +1729,12 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) {
if ctx.IsSet(RPCGlobalLogQueryLimit.Name) { if ctx.IsSet(RPCGlobalLogQueryLimit.Name) {
cfg.LogQueryLimit = ctx.Int(RPCGlobalLogQueryLimit.Name) cfg.LogQueryLimit = ctx.Int(RPCGlobalLogQueryLimit.Name)
} }
if ctx.IsSet(RPCTxSyncDefaultTimeoutFlag.Name) {
cfg.TxSyncDefaultTimeout = ctx.Duration(RPCTxSyncDefaultTimeoutFlag.Name)
}
if ctx.IsSet(RPCTxSyncMaxTimeoutFlag.Name) {
cfg.TxSyncMaxTimeout = ctx.Duration(RPCTxSyncMaxTimeoutFlag.Name)
}
if !ctx.Bool(SnapshotFlag.Name) || cfg.SnapshotCache == 0 { if !ctx.Bool(SnapshotFlag.Name) || cfg.SnapshotCache == 0 {
// If snap-sync is requested, this flag is also required // If snap-sync is requested, this flag is also required
if cfg.SyncMode == ethconfig.SnapSync { if cfg.SyncMode == ethconfig.SnapSync {

View file

@ -182,13 +182,14 @@ func (s *filterTestSuite) loadQueries() error {
// filterQuery is a single query for testing. // filterQuery is a single query for testing.
type filterQuery struct { type filterQuery struct {
FromBlock int64 `json:"fromBlock"` FromBlock int64 `json:"fromBlock"`
ToBlock int64 `json:"toBlock"` ToBlock int64 `json:"toBlock"`
Address []common.Address `json:"address"` lastBlockHash common.Hash
Topics [][]common.Hash `json:"topics"` Address []common.Address `json:"address"`
ResultHash *common.Hash `json:"resultHash,omitempty"` Topics [][]common.Hash `json:"topics"`
results []types.Log ResultHash *common.Hash `json:"resultHash,omitempty"`
Err error `json:"error,omitempty"` results []types.Log
Err error `json:"error,omitempty"`
} }
func (fq *filterQuery) isWildcard() bool { func (fq *filterQuery) isWildcard() bool {

View file

@ -0,0 +1,337 @@
// Copyright 2025 The go-ethereum Authors
// This file is part of go-ethereum.
//
// go-ethereum is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// go-ethereum is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
package main
import (
"context"
"fmt"
"math/big"
"reflect"
"slices"
"time"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/lru"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/rpc"
"github.com/urfave/cli/v2"
)
const maxFilterRangeForTestFuzz = 300
var (
filterFuzzCommand = &cli.Command{
Name: "filterfuzz",
Usage: "Generates queries and compares results against matches derived from receipts",
ArgsUsage: "<RPC endpoint URL>",
Action: filterFuzzCmd,
Flags: []cli.Flag{},
}
)
// filterFuzzCmd is the main function of the filter fuzzer.
func filterFuzzCmd(ctx *cli.Context) error {
f := newFilterTestGen(ctx, maxFilterRangeForTestFuzz)
var lastHead *types.Header
headerCache := lru.NewCache[common.Hash, *types.Header](200)
commonAncestor := func(oldPtr, newPtr *types.Header) *types.Header {
if oldPtr == nil || newPtr == nil {
return nil
}
if newPtr.Number.Uint64() > oldPtr.Number.Uint64()+100 || oldPtr.Number.Uint64() > newPtr.Number.Uint64()+100 {
return nil
}
for oldPtr.Hash() != newPtr.Hash() {
if newPtr.Number.Uint64() >= oldPtr.Number.Uint64() {
if parent, _ := headerCache.Get(newPtr.ParentHash); parent != nil {
newPtr = parent
} else {
newPtr, _ = getHeaderByHash(f.client, newPtr.ParentHash)
if newPtr == nil {
return nil
}
headerCache.Add(newPtr.Hash(), newPtr)
}
}
if oldPtr.Number.Uint64() > newPtr.Number.Uint64() {
oldPtr, _ = headerCache.Get(oldPtr.ParentHash)
if oldPtr == nil {
return nil
}
}
}
return newPtr
}
fetchHead := func() (*types.Header, bool) {
currentHead, err := getLatestHeader(f.client)
if err != nil {
fmt.Println("Could not fetch head block", err)
return nil, false
}
headerCache.Add(currentHead.Hash(), currentHead)
if lastHead != nil && currentHead.Hash() == lastHead.Hash() {
return currentHead, false
}
f.blockLimit = currentHead.Number.Int64()
ca := commonAncestor(lastHead, currentHead)
fmt.Print("*** New head ", f.blockLimit)
if ca == nil {
fmt.Println(" <no common ancestor>")
} else {
if reorged := lastHead.Number.Uint64() - ca.Number.Uint64(); reorged > 0 {
fmt.Print(" reorged ", reorged)
}
if missed := currentHead.Number.Uint64() - ca.Number.Uint64() - 1; missed > 0 {
fmt.Print(" missed ", missed)
}
fmt.Println()
}
lastHead = currentHead
return currentHead, true
}
tryExtendQuery := func(query *filterQuery) *filterQuery {
for {
extQuery := f.extendRange(query)
if extQuery == nil {
return query
}
extQuery.checkLastBlockHash(f.client)
extQuery.run(f.client, nil)
if extQuery.Err == nil && len(extQuery.results) == 0 {
// query is useless now due to major reorg; abandon and continue
fmt.Println("Zero length results")
return nil
}
if extQuery.Err != nil {
extQuery.printError()
return nil
}
if len(extQuery.results) > maxFilterResultSize {
return query
}
query = extQuery
}
}
var (
mmQuery *filterQuery
mmRetry, mmNextRetry int
)
mainLoop:
for {
select {
case <-ctx.Done():
return nil
default:
}
var query *filterQuery
if mmQuery != nil {
if mmRetry == 0 {
query = mmQuery
mmRetry = mmNextRetry
mmNextRetry *= 2
query.checkLastBlockHash(f.client)
query.run(f.client, nil)
if query.Err != nil {
query.printError()
continue
}
fmt.Println("Retrying query from:", query.FromBlock, "to:", query.ToBlock, "results:", len(query.results))
} else {
mmRetry--
}
}
if query == nil {
currentHead, isNewHead := fetchHead()
if currentHead == nil {
select {
case <-ctx.Done():
return nil
case <-time.After(time.Second):
}
continue mainLoop
}
if isNewHead {
query = f.newHeadSeedQuery(currentHead.Number.Int64())
} else {
query = f.newQuery()
}
query.checkLastBlockHash(f.client)
query.run(f.client, nil)
if query.Err != nil {
query.printError()
continue
}
fmt.Println("New query from:", query.FromBlock, "to:", query.ToBlock, "results:", len(query.results))
if len(query.results) == 0 || len(query.results) > maxFilterResultSize {
continue mainLoop
}
if query = tryExtendQuery(query); query == nil {
continue mainLoop
}
}
if !query.checkLastBlockHash(f.client) {
fmt.Println("Reorg during search")
continue mainLoop
}
// now we have a new query; check results
results, err := query.getResultsFromReceipts(f.client)
if err != nil {
fmt.Println("Could not fetch results from receipts", err)
continue mainLoop
}
if !query.checkLastBlockHash(f.client) {
fmt.Println("Reorg during search")
continue mainLoop
}
if !reflect.DeepEqual(query.results, results) {
fmt.Println("Results mismatch from:", query.FromBlock, "to:", query.ToBlock, "addresses:", query.Address, "topics:", query.Topics)
resShared, resGetLogs, resReceipts := compareResults(query.results, results)
fmt.Println(" shared:", len(resShared))
fmt.Println(" only from getLogs:", len(resGetLogs), resGetLogs)
fmt.Println(" only from receipts:", len(resReceipts), resReceipts)
if mmQuery != query {
mmQuery = query
mmRetry = 0
mmNextRetry = 1
}
continue mainLoop
}
fmt.Println("Successful query from:", query.FromBlock, "to:", query.ToBlock, "results:", len(query.results))
f.storeQuery(query)
}
}
func compareResults(a, b []types.Log) (shared, onlya, onlyb []types.Log) {
for len(a) > 0 && len(b) > 0 {
if reflect.DeepEqual(a[0], b[0]) {
shared = append(shared, a[0])
a = a[1:]
b = b[1:]
} else {
for i := 1; ; i++ {
if i >= len(a) { // b[0] not found in a
onlyb = append(onlyb, b[0])
b = b[1:]
break
}
if i >= len(b) { // a[0] not found in b
onlya = append(onlya, a[0])
a = a[1:]
break
}
if reflect.DeepEqual(b[0], a[i]) { // a[:i] not found in b
onlya = append(onlya, a[:i]...)
a = a[i:]
break
}
if reflect.DeepEqual(a[0], b[i]) { // b[:i] not found in a
onlyb = append(onlyb, b[:i]...)
b = b[i:]
break
}
}
}
}
onlya = append(onlya, a...)
onlyb = append(onlyb, b...)
return
}
func getLatestHeader(client *client) (*types.Header, error) {
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
defer cancel()
return client.Eth.HeaderByNumber(ctx, big.NewInt(int64(rpc.LatestBlockNumber)))
}
func getHeaderByHash(client *client, hash common.Hash) (*types.Header, error) {
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
defer cancel()
return client.Eth.HeaderByHash(ctx, hash)
}
// newHeadSeedQuery creates a query that gets all logs from the latest head.
func (s *filterTestGen) newHeadSeedQuery(head int64) *filterQuery {
return &filterQuery{
FromBlock: head,
ToBlock: head,
}
}
func (fq *filterQuery) checkLastBlockHash(client *client) bool {
ctx, cancel := context.WithTimeout(context.Background(), time.Second*30)
defer cancel()
header, err := client.Eth.HeaderByNumber(ctx, big.NewInt(fq.ToBlock))
if err != nil {
fmt.Println("Cound not fetch last block hash of query number:", fq.ToBlock, "error:", err)
fq.lastBlockHash = common.Hash{}
return false
}
hash := header.Hash()
if fq.lastBlockHash == hash {
return true
}
fq.lastBlockHash = hash
return false
}
func (fq *filterQuery) filterLog(log *types.Log) bool {
if len(fq.Address) > 0 && !slices.Contains(fq.Address, log.Address) {
return false
}
// If the to filtered topics is greater than the amount of topics in logs, skip.
if len(fq.Topics) > len(log.Topics) {
return false
}
for i, sub := range fq.Topics {
if len(sub) == 0 {
continue // empty rule set == wildcard
}
if !slices.Contains(sub, log.Topics[i]) {
return false
}
}
return true
}
func (fq *filterQuery) getResultsFromReceipts(client *client) ([]types.Log, error) {
ctx, cancel := context.WithTimeout(context.Background(), time.Second*30)
defer cancel()
var results []types.Log
for blockNumber := fq.FromBlock; blockNumber <= fq.ToBlock; blockNumber++ {
receipts, err := client.Eth.BlockReceipts(ctx, rpc.BlockNumberOrHashWithNumber(rpc.BlockNumber(blockNumber)))
if err != nil {
return nil, err
}
for _, receipt := range receipts {
for _, log := range receipt.Logs {
if fq.filterLog(log) {
results = append(results, *log)
}
}
}
}
return results, nil
}

View file

@ -32,6 +32,17 @@ import (
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
) )
const (
// Parameter of the random filter query generator.
maxFilterRangeForTestGen = 100000000000
maxFilterResultSize = 1000
filterBuckets = 10
maxFilterBucketSize = 100
filterSeedChance = 10
filterMergeChance = 45
filterExtendChance = 50
)
var ( var (
filterGenerateCommand = &cli.Command{ filterGenerateCommand = &cli.Command{
Name: "filtergen", Name: "filtergen",
@ -58,7 +69,7 @@ var (
// filterGenCmd is the main function of the filter tests generator. // filterGenCmd is the main function of the filter tests generator.
func filterGenCmd(ctx *cli.Context) error { func filterGenCmd(ctx *cli.Context) error {
f := newFilterTestGen(ctx) f := newFilterTestGen(ctx, maxFilterRangeForTestGen)
lastWrite := time.Now() lastWrite := time.Now()
for { for {
select { select {
@ -67,7 +78,7 @@ func filterGenCmd(ctx *cli.Context) error {
default: default:
} }
f.updateFinalizedBlock() f.setLimitToFinalizedBlock()
query := f.newQuery() query := f.newQuery()
query.run(f.client, nil) query.run(f.client, nil)
if query.Err != nil { if query.Err != nil {
@ -75,7 +86,7 @@ func filterGenCmd(ctx *cli.Context) error {
exit("filter query failed") exit("filter query failed")
} }
if len(query.results) > 0 && len(query.results) <= maxFilterResultSize { if len(query.results) > 0 && len(query.results) <= maxFilterResultSize {
for { for rand.Intn(100) < filterExtendChance {
extQuery := f.extendRange(query) extQuery := f.extendRange(query)
if extQuery == nil { if extQuery == nil {
break break
@ -108,39 +119,32 @@ func filterGenCmd(ctx *cli.Context) error {
// filterTestGen is the filter query test generator. // filterTestGen is the filter query test generator.
type filterTestGen struct { type filterTestGen struct {
client *client client *client
queryFile string queryFile string
maxFilterRange int64
finalizedBlock int64 blockLimit int64
queries [filterBuckets][]*filterQuery queries [filterBuckets][]*filterQuery
} }
func newFilterTestGen(ctx *cli.Context) *filterTestGen { func newFilterTestGen(ctx *cli.Context, maxFilterRange int64) *filterTestGen {
return &filterTestGen{ return &filterTestGen{
client: makeClient(ctx), client: makeClient(ctx),
queryFile: ctx.String(filterQueryFileFlag.Name), queryFile: ctx.String(filterQueryFileFlag.Name),
maxFilterRange: maxFilterRange,
} }
} }
func (s *filterTestGen) updateFinalizedBlock() { func (s *filterTestGen) setLimitToFinalizedBlock() {
s.finalizedBlock = mustGetFinalizedBlock(s.client) s.blockLimit = mustGetFinalizedBlock(s.client)
} }
const (
// Parameter of the random filter query generator.
maxFilterRange = 10000000
maxFilterResultSize = 300
filterBuckets = 10
maxFilterBucketSize = 100
filterSeedChance = 10
filterMergeChance = 45
)
// storeQuery adds a filter query to the output file. // storeQuery adds a filter query to the output file.
func (s *filterTestGen) storeQuery(query *filterQuery) { func (s *filterTestGen) storeQuery(query *filterQuery) {
query.ResultHash = new(common.Hash) query.ResultHash = new(common.Hash)
*query.ResultHash = query.calculateHash() *query.ResultHash = query.calculateHash()
logRatio := math.Log(float64(len(query.results))*float64(s.finalizedBlock)/float64(query.ToBlock+1-query.FromBlock)) / math.Log(float64(s.finalizedBlock)*maxFilterResultSize) maxFilterRange := min(s.maxFilterRange, s.blockLimit)
logRatio := math.Log(float64(len(query.results))*float64(maxFilterRange)/float64(query.ToBlock+1-query.FromBlock)) / math.Log(float64(maxFilterRange)*maxFilterResultSize)
bucket := int(math.Floor(logRatio * filterBuckets)) bucket := int(math.Floor(logRatio * filterBuckets))
if bucket >= filterBuckets { if bucket >= filterBuckets {
bucket = filterBuckets - 1 bucket = filterBuckets - 1
@ -160,13 +164,13 @@ func (s *filterTestGen) storeQuery(query *filterQuery) {
func (s *filterTestGen) extendRange(q *filterQuery) *filterQuery { func (s *filterTestGen) extendRange(q *filterQuery) *filterQuery {
rangeLen := q.ToBlock + 1 - q.FromBlock rangeLen := q.ToBlock + 1 - q.FromBlock
extLen := rand.Int63n(rangeLen) + 1 extLen := rand.Int63n(rangeLen) + 1
if rangeLen+extLen > s.finalizedBlock { if rangeLen+extLen > min(s.maxFilterRange, s.blockLimit) {
return nil return nil
} }
extBefore := min(rand.Int63n(extLen+1), q.FromBlock) extBefore := min(rand.Int63n(extLen+1), q.FromBlock)
extAfter := extLen - extBefore extAfter := extLen - extBefore
if q.ToBlock+extAfter > s.finalizedBlock { if q.ToBlock+extAfter > s.blockLimit {
d := q.ToBlock + extAfter - s.finalizedBlock d := q.ToBlock + extAfter - s.blockLimit
extAfter -= d extAfter -= d
if extBefore+d <= q.FromBlock { if extBefore+d <= q.FromBlock {
extBefore += d extBefore += d
@ -203,7 +207,7 @@ func (s *filterTestGen) newQuery() *filterQuery {
// newSeedQuery creates a query that gets all logs in a random non-finalized block. // newSeedQuery creates a query that gets all logs in a random non-finalized block.
func (s *filterTestGen) newSeedQuery() *filterQuery { func (s *filterTestGen) newSeedQuery() *filterQuery {
block := rand.Int63n(s.finalizedBlock + 1) block := rand.Int63n(s.blockLimit + 1)
return &filterQuery{ return &filterQuery{
FromBlock: block, FromBlock: block,
ToBlock: block, ToBlock: block,
@ -358,6 +362,7 @@ func (s *filterTestGen) writeQueries() {
func mustGetFinalizedBlock(client *client) int64 { func mustGetFinalizedBlock(client *client) int64 {
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
defer cancel() defer cancel()
header, err := client.Eth.HeaderByNumber(ctx, big.NewInt(int64(rpc.FinalizedBlockNumber))) header, err := client.Eth.HeaderByNumber(ctx, big.NewInt(int64(rpc.FinalizedBlockNumber)))
if err != nil { if err != nil {
exit(fmt.Errorf("could not fetch finalized header (error: %v)", err)) exit(fmt.Errorf("could not fetch finalized header (error: %v)", err))

View file

@ -49,6 +49,7 @@ func init() {
filterGenerateCommand, filterGenerateCommand,
traceGenerateCommand, traceGenerateCommand,
filterPerfCommand, filterPerfCommand,
filterFuzzCommand,
} }
} }

View file

@ -188,24 +188,16 @@ func ReadStateAccountIndex(db ethdb.AncientReaderOp, id uint64) []byte {
// data in the concatenated storage data table. Compute the position of state // data in the concatenated storage data table. Compute the position of state
// history in freezer by minus one since the id of first state history starts // history in freezer by minus one since the id of first state history starts
// from one (zero for initial state). // from one (zero for initial state).
func ReadStateStorageIndex(db ethdb.AncientReaderOp, id uint64) []byte { func ReadStateStorageIndex(db ethdb.AncientReaderOp, id uint64, offset, length int) ([]byte, error) {
blob, err := db.Ancient(stateHistoryStorageIndex, id-1) return db.AncientBytes(stateHistoryStorageIndex, id-1, uint64(offset), uint64(length))
if err != nil {
return nil
}
return blob
} }
// ReadStateAccountHistory retrieves the concatenated account data blob for the // ReadStateAccountHistory retrieves the concatenated account data blob for the
// specified state history. Offsets and lengths are resolved via the account // specified state history. Offsets and lengths are resolved via the account
// index. Compute the position of state history in freezer by minus one since // index. Compute the position of state history in freezer by minus one since
// the id of first state history starts from one (zero for initial state). // the id of first state history starts from one (zero for initial state).
func ReadStateAccountHistory(db ethdb.AncientReaderOp, id uint64) []byte { func ReadStateAccountHistory(db ethdb.AncientReaderOp, id uint64, offset, length int) ([]byte, error) {
blob, err := db.Ancient(stateHistoryAccountData, id-1) return db.AncientBytes(stateHistoryAccountData, id-1, uint64(offset), uint64(length))
if err != nil {
return nil
}
return blob
} }
// ReadStateStorageHistory retrieves the concatenated storage slot data blob for // ReadStateStorageHistory retrieves the concatenated storage slot data blob for
@ -213,12 +205,8 @@ func ReadStateAccountHistory(db ethdb.AncientReaderOp, id uint64) []byte {
// storage indexes. Compute the position of state history in freezer by minus // storage indexes. Compute the position of state history in freezer by minus
// one since the id of first state history starts from one (zero for initial // one since the id of first state history starts from one (zero for initial
// state). // state).
func ReadStateStorageHistory(db ethdb.AncientReaderOp, id uint64) []byte { func ReadStateStorageHistory(db ethdb.AncientReaderOp, id uint64, offset, length int) ([]byte, error) {
blob, err := db.Ancient(stateHistoryStorageData, id-1) return db.AncientBytes(stateHistoryStorageData, id-1, uint64(offset), uint64(length))
if err != nil {
return nil
}
return blob
} }
// ReadStateHistory retrieves the state history from database with provided id. // ReadStateHistory retrieves the state history from database with provided id.

View file

@ -403,6 +403,10 @@ func (f *chainFreezer) AncientRange(kind string, start, count, maxBytes uint64)
return f.ancients.AncientRange(kind, start, count, maxBytes) return f.ancients.AncientRange(kind, start, count, maxBytes)
} }
func (f *chainFreezer) AncientBytes(kind string, id, offset, length uint64) ([]byte, error) {
return f.ancients.AncientBytes(kind, id, offset, length)
}
func (f *chainFreezer) ModifyAncients(fn func(ethdb.AncientWriteOp) error) (int64, error) { func (f *chainFreezer) ModifyAncients(fn func(ethdb.AncientWriteOp) error) (int64, error) {
return f.ancients.ModifyAncients(fn) return f.ancients.ModifyAncients(fn)
} }

View file

@ -100,6 +100,12 @@ func (db *nofreezedb) AncientRange(kind string, start, max, maxByteSize uint64)
return nil, errNotSupported return nil, errNotSupported
} }
// AncientBytes retrieves the value segment of the element specified by the id
// and value offsets.
func (db *nofreezedb) AncientBytes(kind string, id, offset, length uint64) ([]byte, error) {
return nil, errNotSupported
}
// Ancients returns an error as we don't have a backing chain freezer. // Ancients returns an error as we don't have a backing chain freezer.
func (db *nofreezedb) Ancients() (uint64, error) { func (db *nofreezedb) Ancients() (uint64, error) {
return 0, errNotSupported return 0, errNotSupported

View file

@ -202,6 +202,15 @@ func (f *Freezer) AncientRange(kind string, start, count, maxBytes uint64) ([][]
return nil, errUnknownTable return nil, errUnknownTable
} }
// AncientBytes retrieves the value segment of the element specified by the id
// and value offsets.
func (f *Freezer) AncientBytes(kind string, id, offset, length uint64) ([]byte, error) {
if table := f.tables[kind]; table != nil {
return table.RetrieveBytes(id, offset, length)
}
return nil, errUnknownTable
}
// Ancients returns the length of the frozen items. // Ancients returns the length of the frozen items.
func (f *Freezer) Ancients() (uint64, error) { func (f *Freezer) Ancients() (uint64, error) {
return f.frozen.Load(), nil return f.frozen.Load(), nil

View file

@ -412,3 +412,28 @@ func (f *MemoryFreezer) Reset() error {
func (f *MemoryFreezer) AncientDatadir() (string, error) { func (f *MemoryFreezer) AncientDatadir() (string, error) {
return "", nil return "", nil
} }
// AncientBytes retrieves the value segment of the element specified by the id
// and value offsets.
func (f *MemoryFreezer) AncientBytes(kind string, id, offset, length uint64) ([]byte, error) {
f.lock.RLock()
defer f.lock.RUnlock()
table := f.tables[kind]
if table == nil {
return nil, errUnknownTable
}
entries, err := table.retrieve(id, 1, 0)
if err != nil {
return nil, err
}
if len(entries) == 0 {
return nil, errOutOfBounds
}
data := entries[0]
if offset > uint64(len(data)) || offset+length > uint64(len(data)) {
return nil, fmt.Errorf("requested range out of bounds: item size %d, offset %d, length %d", len(data), offset, length)
}
return data[offset : offset+length], nil
}

View file

@ -126,6 +126,15 @@ func (f *resettableFreezer) AncientRange(kind string, start, count, maxBytes uin
return f.freezer.AncientRange(kind, start, count, maxBytes) return f.freezer.AncientRange(kind, start, count, maxBytes)
} }
// AncientBytes retrieves the value segment of the element specified by the id
// and value offsets.
func (f *resettableFreezer) AncientBytes(kind string, id, offset, length uint64) ([]byte, error) {
f.lock.RLock()
defer f.lock.RUnlock()
return f.freezer.AncientBytes(kind, id, offset, length)
}
// Ancients returns the length of the frozen items. // Ancients returns the length of the frozen items.
func (f *resettableFreezer) Ancients() (uint64, error) { func (f *resettableFreezer) Ancients() (uint64, error) {
f.lock.RLock() f.lock.RLock()

View file

@ -1107,6 +1107,71 @@ func (t *freezerTable) retrieveItems(start, count, maxBytes uint64) ([]byte, []i
return output, sizes, nil return output, sizes, nil
} }
// RetrieveBytes retrieves the value segment of the element specified by the id
// and value offsets.
func (t *freezerTable) RetrieveBytes(item, offset, length uint64) ([]byte, error) {
t.lock.RLock()
defer t.lock.RUnlock()
if t.index == nil || t.head == nil || t.metadata.file == nil {
return nil, errClosed
}
items, hidden := t.items.Load(), t.itemHidden.Load()
if items <= item || hidden > item {
return nil, errOutOfBounds
}
// Retrieves the index entries for the specified ID and its immediate successor
indices, err := t.getIndices(item, 1)
if err != nil {
return nil, err
}
index0, index1 := indices[0], indices[1]
itemStart, itemLimit, fileId := index0.bounds(index1)
itemSize := itemLimit - itemStart
dataFile, exist := t.files[fileId]
if !exist {
return nil, fmt.Errorf("missing data file %d", fileId)
}
// Perform the partial read if no-compression was enabled upon
if t.config.noSnappy {
if offset > uint64(itemSize) || offset+length > uint64(itemSize) {
return nil, fmt.Errorf("requested range out of bounds: item size %d, offset %d, length %d", itemSize, offset, length)
}
itemStart += uint32(offset)
buf := make([]byte, length)
_, err = dataFile.ReadAt(buf, int64(itemStart))
if err != nil {
return nil, err
}
t.readMeter.Mark(int64(length))
return buf, nil
} else {
// If compressed, read the full item, decompress, then slice.
// Unfortunately, in this case, there is no performance gain
// by performing the partial read at all.
buf := make([]byte, itemSize)
_, err = dataFile.ReadAt(buf, int64(itemStart))
if err != nil {
return nil, err
}
t.readMeter.Mark(int64(itemSize))
data, err := snappy.Decode(nil, buf)
if err != nil {
return nil, err
}
if offset > uint64(len(data)) || offset+length > uint64(len(data)) {
return nil, fmt.Errorf("requested range out of bounds: item size %d, offset %d, length %d", len(data), offset, length)
}
return data[offset : offset+length], nil
}
}
// size returns the total data size in the freezer table. // size returns the total data size in the freezer table.
func (t *freezerTable) size() (uint64, error) { func (t *freezerTable) size() (uint64, error) {
t.lock.RLock() t.lock.RLock()

View file

@ -1571,3 +1571,65 @@ func TestTailTruncationCrash(t *testing.T) {
t.Fatalf("Unexpected index flush offset, want: %d, got: %d", 26*indexEntrySize, f.metadata.flushOffset) t.Fatalf("Unexpected index flush offset, want: %d, got: %d", 26*indexEntrySize, f.metadata.flushOffset)
} }
} }
func TestFreezerAncientBytes(t *testing.T) {
t.Parallel()
types := []struct {
name string
config freezerTableConfig
}{
{"uncompressed", freezerTableConfig{noSnappy: true}},
{"compressed", freezerTableConfig{noSnappy: false}},
}
for _, typ := range types {
t.Run(typ.name, func(t *testing.T) {
f, err := newTable(os.TempDir(), fmt.Sprintf("ancientbytes-%s-%d", typ.name, rand.Uint64()), metrics.NewMeter(), metrics.NewMeter(), metrics.NewGauge(), 1000, typ.config, false)
if err != nil {
t.Fatal(err)
}
defer f.Close()
for i := 0; i < 10; i++ {
data := getChunk(100, i)
batch := f.newBatch()
require.NoError(t, batch.AppendRaw(uint64(i), data))
require.NoError(t, batch.commit())
}
for i := 0; i < 10; i++ {
full, err := f.Retrieve(uint64(i))
require.NoError(t, err)
// Full read
got, err := f.RetrieveBytes(uint64(i), 0, uint64(len(full)))
require.NoError(t, err)
if !bytes.Equal(got, full) {
t.Fatalf("full read mismatch for entry %d", i)
}
// Empty read
got, err = f.RetrieveBytes(uint64(i), 0, 0)
require.NoError(t, err)
if !bytes.Equal(got, full[:0]) {
t.Fatalf("empty read mismatch for entry %d", i)
}
// Middle slice
got, err = f.RetrieveBytes(uint64(i), 10, 50)
require.NoError(t, err)
if !bytes.Equal(got, full[10:60]) {
t.Fatalf("middle slice mismatch for entry %d", i)
}
// Single byte
got, err = f.RetrieveBytes(uint64(i), 99, 1)
require.NoError(t, err)
if !bytes.Equal(got, full[99:100]) {
t.Fatalf("single byte mismatch for entry %d", i)
}
// Out of bounds
_, err = f.RetrieveBytes(uint64(i), 100, 1)
if err == nil {
t.Fatalf("expected error for out-of-bounds read for entry %d", i)
}
}
})
}
}

View file

@ -62,6 +62,12 @@ func (t *table) AncientRange(kind string, start, count, maxBytes uint64) ([][]by
return t.db.AncientRange(kind, start, count, maxBytes) return t.db.AncientRange(kind, start, count, maxBytes)
} }
// AncientBytes is a noop passthrough that just forwards the request to the underlying
// database.
func (t *table) AncientBytes(kind string, id, offset, length uint64) ([]byte, error) {
return t.db.AncientBytes(kind, id, offset, length)
}
// Ancients is a noop passthrough that just forwards the request to the underlying // Ancients is a noop passthrough that just forwards the request to the underlying
// database. // database.
func (t *table) Ancients() (uint64, error) { func (t *table) Ancients() (uint64, error) {

View file

@ -302,6 +302,8 @@ func mustCopyTrie(t Trie) Trie {
return t.Copy() return t.Copy()
case *trie.VerkleTrie: case *trie.VerkleTrie:
return t.Copy() return t.Copy()
case *trie.TransitionTrie:
return t.Copy()
default: default:
panic(fmt.Errorf("unknown trie type %T", t)) panic(fmt.Errorf("unknown trie type %T", t))
} }

View file

@ -28,6 +28,7 @@ import (
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/trie"
"github.com/ethereum/go-ethereum/trie/trienode" "github.com/ethereum/go-ethereum/trie/trienode"
"github.com/holiman/uint256" "github.com/holiman/uint256"
) )
@ -494,8 +495,20 @@ func (s *stateObject) deepCopy(db *StateDB) *stateObject {
selfDestructed: s.selfDestructed, selfDestructed: s.selfDestructed,
newContract: s.newContract, newContract: s.newContract,
} }
if s.trie != nil {
switch s.trie.(type) {
case *trie.VerkleTrie:
// Verkle uses only one tree, and the copy has already been
// made in mustCopyTrie.
obj.trie = db.trie
case *trie.TransitionTrie:
// Same thing for the transition tree, since the MPT is
// read-only.
obj.trie = db.trie
case *trie.StateTrie:
obj.trie = mustCopyTrie(s.trie) obj.trie = mustCopyTrie(s.trie)
case nil:
// do nothing
} }
return obj return obj
} }

View file

@ -23,7 +23,6 @@ import (
"math" "math"
"math/big" "math/big"
"slices" "slices"
"sort"
"sync" "sync"
"sync/atomic" "sync/atomic"
"time" "time"
@ -238,11 +237,10 @@ type LegacyPool struct {
pendingNonces *noncer // Pending state tracking virtual nonces pendingNonces *noncer // Pending state tracking virtual nonces
reserver txpool.Reserver // Address reserver to ensure exclusivity across subpools reserver txpool.Reserver // Address reserver to ensure exclusivity across subpools
pending map[common.Address]*list // All currently processable transactions pending map[common.Address]*list // All currently processable transactions
queue map[common.Address]*list // Queued but non-processable transactions queue *queue
beats map[common.Address]time.Time // Last heartbeat from each known account all *lookup // All transactions to allow lookups
all *lookup // All transactions to allow lookups priced *pricedList // All transactions sorted by price
priced *pricedList // All transactions sorted by price
reqResetCh chan *txpoolResetRequest reqResetCh chan *txpoolResetRequest
reqPromoteCh chan *accountSet reqPromoteCh chan *accountSet
@ -266,14 +264,14 @@ func New(config Config, chain BlockChain) *LegacyPool {
config = (&config).sanitize() config = (&config).sanitize()
// Create the transaction pool with its initial settings // Create the transaction pool with its initial settings
signer := types.LatestSigner(chain.Config())
pool := &LegacyPool{ pool := &LegacyPool{
config: config, config: config,
chain: chain, chain: chain,
chainconfig: chain.Config(), chainconfig: chain.Config(),
signer: types.LatestSigner(chain.Config()), signer: signer,
pending: make(map[common.Address]*list), pending: make(map[common.Address]*list),
queue: make(map[common.Address]*list), queue: newQueue(config, signer),
beats: make(map[common.Address]time.Time),
all: newLookup(), all: newLookup(),
reqResetCh: make(chan *txpoolResetRequest), reqResetCh: make(chan *txpoolResetRequest),
reqPromoteCh: make(chan *accountSet), reqPromoteCh: make(chan *accountSet),
@ -369,15 +367,8 @@ func (pool *LegacyPool) loop() {
// Handle inactive account transaction eviction // Handle inactive account transaction eviction
case <-evict.C: case <-evict.C:
pool.mu.Lock() pool.mu.Lock()
for addr := range pool.queue { for _, hash := range pool.queue.evictList() {
// Any old enough should be removed pool.removeTx(hash, true, true)
if time.Since(pool.beats[addr]) > pool.config.Lifetime {
list := pool.queue[addr].Flatten()
for _, tx := range list {
pool.removeTx(tx.Hash(), true, true)
}
queuedEvictionMeter.Mark(int64(len(list)))
}
} }
pool.mu.Unlock() pool.mu.Unlock()
} }
@ -459,11 +450,7 @@ func (pool *LegacyPool) stats() (int, int) {
for _, list := range pool.pending { for _, list := range pool.pending {
pending += list.Len() pending += list.Len()
} }
queued := 0 return pending, pool.queue.stats()
for _, list := range pool.queue {
queued += list.Len()
}
return pending, queued
} }
// Content retrieves the data content of the transaction pool, returning all the // Content retrieves the data content of the transaction pool, returning all the
@ -476,10 +463,7 @@ func (pool *LegacyPool) Content() (map[common.Address][]*types.Transaction, map[
for addr, list := range pool.pending { for addr, list := range pool.pending {
pending[addr] = list.Flatten() pending[addr] = list.Flatten()
} }
queued := make(map[common.Address][]*types.Transaction, len(pool.queue)) queued := pool.queue.content()
for addr, list := range pool.queue {
queued[addr] = list.Flatten()
}
return pending, queued return pending, queued
} }
@ -493,10 +477,7 @@ func (pool *LegacyPool) ContentFrom(addr common.Address) ([]*types.Transaction,
if list, ok := pool.pending[addr]; ok { if list, ok := pool.pending[addr]; ok {
pending = list.Flatten() pending = list.Flatten()
} }
var queued []*types.Transaction queued := pool.queue.contentFrom(addr)
if list, ok := pool.queue[addr]; ok {
queued = list.Flatten()
}
return pending, queued return pending, queued
} }
@ -644,7 +625,7 @@ func (pool *LegacyPool) validateAuth(tx *types.Transaction) error {
if pending := pool.pending[auth]; pending != nil { if pending := pool.pending[auth]; pending != nil {
count += pending.Len() count += pending.Len()
} }
if queue := pool.queue[auth]; queue != nil { if queue, ok := pool.queue.get(auth); ok {
count += queue.Len() count += queue.Len()
} }
if count > 1 { if count > 1 {
@ -691,7 +672,7 @@ func (pool *LegacyPool) add(tx *types.Transaction) (replaced bool, err error) {
// only by this subpool until all transactions are evicted // only by this subpool until all transactions are evicted
var ( var (
_, hasPending = pool.pending[from] _, hasPending = pool.pending[from]
_, hasQueued = pool.queue[from] _, hasQueued = pool.queue.get(from)
) )
if !hasPending && !hasQueued { if !hasPending && !hasQueued {
if err := pool.reserver.Hold(from); err != nil { if err := pool.reserver.Hold(from); err != nil {
@ -790,7 +771,7 @@ func (pool *LegacyPool) add(tx *types.Transaction) (replaced bool, err error) {
log.Trace("Pooled new executable transaction", "hash", hash, "from", from, "to", tx.To()) log.Trace("Pooled new executable transaction", "hash", hash, "from", from, "to", tx.To())
// Successful promotion, bump the heartbeat // Successful promotion, bump the heartbeat
pool.beats[from] = time.Now() pool.queue.bump(from)
return old != nil, nil return old != nil, nil
} }
// New transaction isn't replacing a pending one, push into queue // New transaction isn't replacing a pending one, push into queue
@ -815,7 +796,7 @@ func (pool *LegacyPool) isGapped(from common.Address, tx *types.Transaction) boo
} }
// The transaction has a nonce gap with pending list, it's only considered // The transaction has a nonce gap with pending list, it's only considered
// as executable if transactions in queue can fill up the nonce gap. // as executable if transactions in queue can fill up the nonce gap.
queue, ok := pool.queue[from] queue, ok := pool.queue.get(from)
if !ok { if !ok {
return true return true
} }
@ -831,25 +812,12 @@ func (pool *LegacyPool) isGapped(from common.Address, tx *types.Transaction) boo
// //
// Note, this method assumes the pool lock is held! // Note, this method assumes the pool lock is held!
func (pool *LegacyPool) enqueueTx(hash common.Hash, tx *types.Transaction, addAll bool) (bool, error) { func (pool *LegacyPool) enqueueTx(hash common.Hash, tx *types.Transaction, addAll bool) (bool, error) {
// Try to insert the transaction into the future queue replaced, err := pool.queue.add(tx)
from, _ := types.Sender(pool.signer, tx) // already validated if err != nil {
if pool.queue[from] == nil { return false, err
pool.queue[from] = newList(false)
} }
inserted, old := pool.queue[from].Add(tx, pool.config.PriceBump) if replaced != nil {
if !inserted { pool.removeTx(*replaced, true, true)
// An older transaction was better, discard this
queuedDiscardMeter.Mark(1)
return false, txpool.ErrReplaceUnderpriced
}
// Discard any previous transaction and mark this
if old != nil {
pool.all.Remove(old.Hash())
pool.priced.Removed(1)
queuedReplaceMeter.Mark(1)
} else {
// Nothing was replaced, bump the queued counter
queuedGauge.Inc(1)
} }
// If the transaction isn't in lookup set but it's expected to be there, // If the transaction isn't in lookup set but it's expected to be there,
// show the error log. // show the error log.
@ -860,11 +828,7 @@ func (pool *LegacyPool) enqueueTx(hash common.Hash, tx *types.Transaction, addAl
pool.all.Add(tx) pool.all.Add(tx)
pool.priced.Put(tx) pool.priced.Put(tx)
} }
// If we never record the heartbeat, do it right now. return replaced != nil, nil
if _, exist := pool.beats[from]; !exist {
pool.beats[from] = time.Now()
}
return old != nil, nil
} }
// promoteTx adds a transaction to the pending (processable) list of transactions // promoteTx adds a transaction to the pending (processable) list of transactions
@ -899,7 +863,7 @@ func (pool *LegacyPool) promoteTx(addr common.Address, hash common.Hash, tx *typ
pool.pendingNonces.set(addr, tx.Nonce()+1) pool.pendingNonces.set(addr, tx.Nonce()+1)
// Successful promotion, bump the heartbeat // Successful promotion, bump the heartbeat
pool.beats[addr] = time.Now() pool.queue.bump(addr)
return true return true
} }
@ -1019,7 +983,7 @@ func (pool *LegacyPool) Status(hash common.Hash) txpool.TxStatus {
if txList := pool.pending[from]; txList != nil && txList.txs.items[tx.Nonce()] != nil { if txList := pool.pending[from]; txList != nil && txList.txs.items[tx.Nonce()] != nil {
return txpool.TxStatusPending return txpool.TxStatusPending
} else if txList := pool.queue[from]; txList != nil && txList.txs.items[tx.Nonce()] != nil { } else if txList, ok := pool.queue.get(from); ok && txList.txs.items[tx.Nonce()] != nil {
return txpool.TxStatusQueued return txpool.TxStatusQueued
} }
return txpool.TxStatusUnknown return txpool.TxStatusUnknown
@ -1096,7 +1060,7 @@ func (pool *LegacyPool) removeTx(hash common.Hash, outofbound bool, unreserve bo
defer func() { defer func() {
var ( var (
_, hasPending = pool.pending[addr] _, hasPending = pool.pending[addr]
_, hasQueued = pool.queue[addr] _, hasQueued = pool.queue.get(addr)
) )
if !hasPending && !hasQueued { if !hasPending && !hasQueued {
pool.reserver.Release(addr) pool.reserver.Release(addr)
@ -1128,16 +1092,7 @@ func (pool *LegacyPool) removeTx(hash common.Hash, outofbound bool, unreserve bo
} }
} }
// Transaction is in the future queue // Transaction is in the future queue
if future := pool.queue[addr]; future != nil { pool.queue.remove(addr, tx)
if removed, _ := future.Remove(tx); removed {
// Reduce the queued counter
queuedGauge.Dec(1)
}
if future.Empty() {
delete(pool.queue, addr)
delete(pool.beats, addr)
}
}
return 0 return 0
} }
@ -1285,10 +1240,7 @@ func (pool *LegacyPool) runReorg(done chan struct{}, reset *txpoolResetRequest,
} }
} }
// Reset needs promote for all addresses // Reset needs promote for all addresses
promoteAddrs = make([]common.Address, 0, len(pool.queue)) promoteAddrs = pool.queue.addresses()
for addr := range pool.queue {
promoteAddrs = append(promoteAddrs, addr)
}
} }
// Check for pending transactions for every account that sent new ones // Check for pending transactions for every account that sent new ones
promoted := pool.promoteExecutables(promoteAddrs) promoted := pool.promoteExecutables(promoteAddrs)
@ -1442,60 +1394,29 @@ func (pool *LegacyPool) reset(oldHead, newHead *types.Header) {
// future queue to the set of pending transactions. During this process, all // future queue to the set of pending transactions. During this process, all
// invalidated transactions (low nonce, low balance) are deleted. // invalidated transactions (low nonce, low balance) are deleted.
func (pool *LegacyPool) promoteExecutables(accounts []common.Address) []*types.Transaction { func (pool *LegacyPool) promoteExecutables(accounts []common.Address) []*types.Transaction {
// Track the promoted transactions to broadcast them at once
var promoted []*types.Transaction
// Iterate over all accounts and promote any executable transactions
gasLimit := pool.currentHead.Load().GasLimit gasLimit := pool.currentHead.Load().GasLimit
for _, addr := range accounts { promotable, dropped, removedAddresses := pool.queue.promoteExecutables(accounts, gasLimit, pool.currentState, pool.pendingNonces)
list := pool.queue[addr]
if list == nil {
continue // Just in case someone calls with a non existing account
}
// Drop all transactions that are deemed too old (low nonce)
forwards := list.Forward(pool.currentState.GetNonce(addr))
for _, tx := range forwards {
pool.all.Remove(tx.Hash())
}
log.Trace("Removed old queued transactions", "count", len(forwards))
// Drop all transactions that are too costly (low balance or out of gas)
drops, _ := list.Filter(pool.currentState.GetBalance(addr), gasLimit)
for _, tx := range drops {
pool.all.Remove(tx.Hash())
}
log.Trace("Removed unpayable queued transactions", "count", len(drops))
queuedNofundsMeter.Mark(int64(len(drops)))
// Gather all executable transactions and promote them // promote all promotable transactions
readies := list.Ready(pool.pendingNonces.get(addr)) promoted := make([]*types.Transaction, 0, len(promotable))
for _, tx := range readies { for _, tx := range promotable {
hash := tx.Hash() from, _ := pool.signer.Sender(tx)
if pool.promoteTx(addr, hash, tx) { if pool.promoteTx(from, tx.Hash(), tx) {
promoted = append(promoted, tx) promoted = append(promoted, tx)
}
} }
log.Trace("Promoted queued transactions", "count", len(promoted)) }
queuedGauge.Dec(int64(len(readies)))
// Drop all transactions over the allowed limit // remove all removable transactions
var caps = list.Cap(int(pool.config.AccountQueue)) for _, hash := range dropped {
for _, tx := range caps { pool.all.Remove(hash)
hash := tx.Hash() }
pool.all.Remove(hash) pool.priced.Removed(len(dropped))
log.Trace("Removed cap-exceeding queued transaction", "hash", hash)
}
queuedRateLimitMeter.Mark(int64(len(caps)))
// Mark all the items dropped as removed
pool.priced.Removed(len(forwards) + len(drops) + len(caps))
queuedGauge.Dec(int64(len(forwards) + len(drops) + len(caps)))
// Delete the entire queue entry if it became empty. // release all accounts that have no more transactions in the pool
if list.Empty() { for _, addr := range removedAddresses {
delete(pool.queue, addr) _, hasPending := pool.pending[addr]
delete(pool.beats, addr) if !hasPending {
if _, ok := pool.pending[addr]; !ok { pool.reserver.Release(addr)
pool.reserver.Release(addr)
}
} }
} }
return promoted return promoted
@ -1585,43 +1506,18 @@ func (pool *LegacyPool) truncatePending() {
// truncateQueue drops the oldest transactions in the queue if the pool is above the global queue limit. // truncateQueue drops the oldest transactions in the queue if the pool is above the global queue limit.
func (pool *LegacyPool) truncateQueue() { func (pool *LegacyPool) truncateQueue() {
queued := uint64(0) removed, removedAddresses := pool.queue.truncate()
for _, list := range pool.queue {
queued += uint64(list.Len()) // Remove all removable transactions from the lookup and global price list
} for _, hash := range removed {
if queued <= pool.config.GlobalQueue { pool.all.Remove(hash)
return
} }
pool.priced.Removed(len(removed))
// Sort all accounts with queued transactions by heartbeat for _, addr := range removedAddresses {
addresses := make(addressesByHeartbeat, 0, len(pool.queue)) _, hasPending := pool.pending[addr]
for addr := range pool.queue { if !hasPending {
addresses = append(addresses, addressByHeartbeat{addr, pool.beats[addr]}) pool.reserver.Release(addr)
}
sort.Sort(sort.Reverse(addresses))
// Drop transactions until the total is below the limit
for drop := queued - pool.config.GlobalQueue; drop > 0 && len(addresses) > 0; {
addr := addresses[len(addresses)-1]
list := pool.queue[addr.address]
addresses = addresses[:len(addresses)-1]
// Drop all transactions if they are less than the overflow
if size := uint64(list.Len()); size <= drop {
for _, tx := range list.Flatten() {
pool.removeTx(tx.Hash(), true, true)
}
drop -= size
queuedRateLimitMeter.Mark(int64(size))
continue
}
// Otherwise drop only last few transactions
txs := list.Flatten()
for i := len(txs) - 1; i >= 0 && drop > 0; i-- {
pool.removeTx(txs[i].Hash(), true, true)
drop--
queuedRateLimitMeter.Mark(1)
} }
} }
} }
@ -1679,25 +1575,13 @@ func (pool *LegacyPool) demoteUnexecutables() {
// Delete the entire pending entry if it became empty. // Delete the entire pending entry if it became empty.
if list.Empty() { if list.Empty() {
delete(pool.pending, addr) delete(pool.pending, addr)
if _, ok := pool.queue[addr]; !ok { if _, ok := pool.queue.get(addr); !ok {
pool.reserver.Release(addr) pool.reserver.Release(addr)
} }
} }
} }
} }
// addressByHeartbeat is an account address tagged with its last activity timestamp.
type addressByHeartbeat struct {
address common.Address
heartbeat time.Time
}
type addressesByHeartbeat []addressByHeartbeat
func (a addressesByHeartbeat) Len() int { return len(a) }
func (a addressesByHeartbeat) Less(i, j int) bool { return a[i].heartbeat.Before(a[j].heartbeat) }
func (a addressesByHeartbeat) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
// accountSet is simply a set of addresses to check for existence, and a signer // accountSet is simply a set of addresses to check for existence, and a signer
// capable of deriving addresses from transactions. // capable of deriving addresses from transactions.
type accountSet struct { type accountSet struct {
@ -1938,17 +1822,17 @@ func (pool *LegacyPool) Clear() {
// acquire the subpool lock until the transaction addition is completed. // acquire the subpool lock until the transaction addition is completed.
for addr := range pool.pending { for addr := range pool.pending {
if _, ok := pool.queue[addr]; !ok { if _, ok := pool.queue.get(addr); !ok {
pool.reserver.Release(addr) pool.reserver.Release(addr)
} }
} }
for addr := range pool.queue { for _, addr := range pool.queue.addresses() {
pool.reserver.Release(addr) pool.reserver.Release(addr)
} }
pool.all.Clear() pool.all.Clear()
pool.priced.Reheap() pool.priced.Reheap()
pool.pending = make(map[common.Address]*list) pool.pending = make(map[common.Address]*list)
pool.queue = make(map[common.Address]*list) pool.queue = newQueue(pool.config, pool.signer)
pool.pendingNonces = newNoncer(pool.currentState) pool.pendingNonces = newNoncer(pool.currentState)
} }

View file

@ -466,8 +466,8 @@ func TestQueue(t *testing.T) {
if _, ok := pool.pending[from].txs.items[tx.Nonce()]; ok { if _, ok := pool.pending[from].txs.items[tx.Nonce()]; ok {
t.Error("expected transaction to be in tx pool") t.Error("expected transaction to be in tx pool")
} }
if len(pool.queue) > 0 { if len(pool.queue.queued) > 0 {
t.Error("expected transaction queue to be empty. is", len(pool.queue)) t.Error("expected transaction queue to be empty. is", len(pool.queue.queued))
} }
} }
@ -492,8 +492,8 @@ func TestQueue2(t *testing.T) {
if len(pool.pending) != 1 { if len(pool.pending) != 1 {
t.Error("expected pending length to be 1, got", len(pool.pending)) t.Error("expected pending length to be 1, got", len(pool.pending))
} }
if pool.queue[from].Len() != 2 { if list, _ := pool.queue.get(from); list.Len() != 2 {
t.Error("expected len(queue) == 2, got", pool.queue[from].Len()) t.Error("expected len(queue) == 2, got", list.Len())
} }
} }
@ -639,8 +639,8 @@ func TestMissingNonce(t *testing.T) {
if len(pool.pending) != 0 { if len(pool.pending) != 0 {
t.Error("expected 0 pending transactions, got", len(pool.pending)) t.Error("expected 0 pending transactions, got", len(pool.pending))
} }
if pool.queue[addr].Len() != 1 { if list, _ := pool.queue.get(addr); list.Len() != 1 {
t.Error("expected 1 queued transaction, got", pool.queue[addr].Len()) t.Error("expected 1 queued transaction, got", list.Len())
} }
if pool.all.Count() != 1 { if pool.all.Count() != 1 {
t.Error("expected 1 total transactions, got", pool.all.Count()) t.Error("expected 1 total transactions, got", pool.all.Count())
@ -712,8 +712,8 @@ func TestDropping(t *testing.T) {
if pool.pending[account].Len() != 3 { if pool.pending[account].Len() != 3 {
t.Errorf("pending transaction mismatch: have %d, want %d", pool.pending[account].Len(), 3) t.Errorf("pending transaction mismatch: have %d, want %d", pool.pending[account].Len(), 3)
} }
if pool.queue[account].Len() != 3 { if list, _ := pool.queue.get(account); list.Len() != 3 {
t.Errorf("queued transaction mismatch: have %d, want %d", pool.queue[account].Len(), 3) t.Errorf("queued transaction mismatch: have %d, want %d", list.Len(), 3)
} }
if pool.all.Count() != 6 { if pool.all.Count() != 6 {
t.Errorf("total transaction mismatch: have %d, want %d", pool.all.Count(), 6) t.Errorf("total transaction mismatch: have %d, want %d", pool.all.Count(), 6)
@ -722,8 +722,8 @@ func TestDropping(t *testing.T) {
if pool.pending[account].Len() != 3 { if pool.pending[account].Len() != 3 {
t.Errorf("pending transaction mismatch: have %d, want %d", pool.pending[account].Len(), 3) t.Errorf("pending transaction mismatch: have %d, want %d", pool.pending[account].Len(), 3)
} }
if pool.queue[account].Len() != 3 { if list, _ := pool.queue.get(account); list.Len() != 3 {
t.Errorf("queued transaction mismatch: have %d, want %d", pool.queue[account].Len(), 3) t.Errorf("queued transaction mismatch: have %d, want %d", list.Len(), 3)
} }
if pool.all.Count() != 6 { if pool.all.Count() != 6 {
t.Errorf("total transaction mismatch: have %d, want %d", pool.all.Count(), 6) t.Errorf("total transaction mismatch: have %d, want %d", pool.all.Count(), 6)
@ -741,13 +741,14 @@ func TestDropping(t *testing.T) {
if _, ok := pool.pending[account].txs.items[tx2.Nonce()]; ok { if _, ok := pool.pending[account].txs.items[tx2.Nonce()]; ok {
t.Errorf("out-of-fund pending transaction present: %v", tx1) t.Errorf("out-of-fund pending transaction present: %v", tx1)
} }
if _, ok := pool.queue[account].txs.items[tx10.Nonce()]; !ok { list, _ := pool.queue.get(account)
if _, ok := list.txs.items[tx10.Nonce()]; !ok {
t.Errorf("funded queued transaction missing: %v", tx10) t.Errorf("funded queued transaction missing: %v", tx10)
} }
if _, ok := pool.queue[account].txs.items[tx11.Nonce()]; !ok { if _, ok := list.txs.items[tx11.Nonce()]; !ok {
t.Errorf("funded queued transaction missing: %v", tx10) t.Errorf("funded queued transaction missing: %v", tx10)
} }
if _, ok := pool.queue[account].txs.items[tx12.Nonce()]; ok { if _, ok := list.txs.items[tx12.Nonce()]; ok {
t.Errorf("out-of-fund queued transaction present: %v", tx11) t.Errorf("out-of-fund queued transaction present: %v", tx11)
} }
if pool.all.Count() != 4 { if pool.all.Count() != 4 {
@ -763,10 +764,11 @@ func TestDropping(t *testing.T) {
if _, ok := pool.pending[account].txs.items[tx1.Nonce()]; ok { if _, ok := pool.pending[account].txs.items[tx1.Nonce()]; ok {
t.Errorf("over-gased pending transaction present: %v", tx1) t.Errorf("over-gased pending transaction present: %v", tx1)
} }
if _, ok := pool.queue[account].txs.items[tx10.Nonce()]; !ok { list, _ = pool.queue.get(account)
if _, ok := list.txs.items[tx10.Nonce()]; !ok {
t.Errorf("funded queued transaction missing: %v", tx10) t.Errorf("funded queued transaction missing: %v", tx10)
} }
if _, ok := pool.queue[account].txs.items[tx11.Nonce()]; ok { if _, ok := list.txs.items[tx11.Nonce()]; ok {
t.Errorf("over-gased queued transaction present: %v", tx11) t.Errorf("over-gased queued transaction present: %v", tx11)
} }
if pool.all.Count() != 2 { if pool.all.Count() != 2 {
@ -820,8 +822,8 @@ func TestPostponing(t *testing.T) {
if pending := pool.pending[accs[0]].Len() + pool.pending[accs[1]].Len(); pending != len(txs) { if pending := pool.pending[accs[0]].Len() + pool.pending[accs[1]].Len(); pending != len(txs) {
t.Errorf("pending transaction mismatch: have %d, want %d", pending, len(txs)) t.Errorf("pending transaction mismatch: have %d, want %d", pending, len(txs))
} }
if len(pool.queue) != 0 { if len(pool.queue.addresses()) != 0 {
t.Errorf("queued accounts mismatch: have %d, want %d", len(pool.queue), 0) t.Errorf("queued accounts mismatch: have %d, want %d", len(pool.queue.addresses()), 0)
} }
if pool.all.Count() != len(txs) { if pool.all.Count() != len(txs) {
t.Errorf("total transaction mismatch: have %d, want %d", pool.all.Count(), len(txs)) t.Errorf("total transaction mismatch: have %d, want %d", pool.all.Count(), len(txs))
@ -830,8 +832,8 @@ func TestPostponing(t *testing.T) {
if pending := pool.pending[accs[0]].Len() + pool.pending[accs[1]].Len(); pending != len(txs) { if pending := pool.pending[accs[0]].Len() + pool.pending[accs[1]].Len(); pending != len(txs) {
t.Errorf("pending transaction mismatch: have %d, want %d", pending, len(txs)) t.Errorf("pending transaction mismatch: have %d, want %d", pending, len(txs))
} }
if len(pool.queue) != 0 { if len(pool.queue.addresses()) != 0 {
t.Errorf("queued accounts mismatch: have %d, want %d", len(pool.queue), 0) t.Errorf("queued accounts mismatch: have %d, want %d", len(pool.queue.addresses()), 0)
} }
if pool.all.Count() != len(txs) { if pool.all.Count() != len(txs) {
t.Errorf("total transaction mismatch: have %d, want %d", pool.all.Count(), len(txs)) t.Errorf("total transaction mismatch: have %d, want %d", pool.all.Count(), len(txs))
@ -847,7 +849,8 @@ func TestPostponing(t *testing.T) {
if _, ok := pool.pending[accs[0]].txs.items[txs[0].Nonce()]; !ok { if _, ok := pool.pending[accs[0]].txs.items[txs[0].Nonce()]; !ok {
t.Errorf("tx %d: valid and funded transaction missing from pending pool: %v", 0, txs[0]) t.Errorf("tx %d: valid and funded transaction missing from pending pool: %v", 0, txs[0])
} }
if _, ok := pool.queue[accs[0]].txs.items[txs[0].Nonce()]; ok { list, _ := pool.queue.get(accs[0])
if _, ok := list.txs.items[txs[0].Nonce()]; ok {
t.Errorf("tx %d: valid and funded transaction present in future queue: %v", 0, txs[0]) t.Errorf("tx %d: valid and funded transaction present in future queue: %v", 0, txs[0])
} }
for i, tx := range txs[1:100] { for i, tx := range txs[1:100] {
@ -855,14 +858,14 @@ func TestPostponing(t *testing.T) {
if _, ok := pool.pending[accs[0]].txs.items[tx.Nonce()]; ok { if _, ok := pool.pending[accs[0]].txs.items[tx.Nonce()]; ok {
t.Errorf("tx %d: valid but future transaction present in pending pool: %v", i+1, tx) t.Errorf("tx %d: valid but future transaction present in pending pool: %v", i+1, tx)
} }
if _, ok := pool.queue[accs[0]].txs.items[tx.Nonce()]; !ok { if _, ok := list.txs.items[tx.Nonce()]; !ok {
t.Errorf("tx %d: valid but future transaction missing from future queue: %v", i+1, tx) t.Errorf("tx %d: valid but future transaction missing from future queue: %v", i+1, tx)
} }
} else { } else {
if _, ok := pool.pending[accs[0]].txs.items[tx.Nonce()]; ok { if _, ok := pool.pending[accs[0]].txs.items[tx.Nonce()]; ok {
t.Errorf("tx %d: out-of-fund transaction present in pending pool: %v", i+1, tx) t.Errorf("tx %d: out-of-fund transaction present in pending pool: %v", i+1, tx)
} }
if _, ok := pool.queue[accs[0]].txs.items[tx.Nonce()]; ok { if _, ok := list.txs.items[tx.Nonce()]; ok {
t.Errorf("tx %d: out-of-fund transaction present in future queue: %v", i+1, tx) t.Errorf("tx %d: out-of-fund transaction present in future queue: %v", i+1, tx)
} }
} }
@ -872,13 +875,14 @@ func TestPostponing(t *testing.T) {
if pool.pending[accs[1]] != nil { if pool.pending[accs[1]] != nil {
t.Errorf("invalidated account still has pending transactions") t.Errorf("invalidated account still has pending transactions")
} }
list, _ = pool.queue.get(accs[1])
for i, tx := range txs[100:] { for i, tx := range txs[100:] {
if i%2 == 1 { if i%2 == 1 {
if _, ok := pool.queue[accs[1]].txs.items[tx.Nonce()]; !ok { if _, ok := list.txs.items[tx.Nonce()]; !ok {
t.Errorf("tx %d: valid but future transaction missing from future queue: %v", 100+i, tx) t.Errorf("tx %d: valid but future transaction missing from future queue: %v", 100+i, tx)
} }
} else { } else {
if _, ok := pool.queue[accs[1]].txs.items[tx.Nonce()]; ok { if _, ok := list.txs.items[tx.Nonce()]; ok {
t.Errorf("tx %d: out-of-fund transaction present in future queue: %v", 100+i, tx) t.Errorf("tx %d: out-of-fund transaction present in future queue: %v", 100+i, tx)
} }
} }
@ -963,13 +967,14 @@ func TestQueueAccountLimiting(t *testing.T) {
if len(pool.pending) != 0 { if len(pool.pending) != 0 {
t.Errorf("tx %d: pending pool size mismatch: have %d, want %d", i, len(pool.pending), 0) t.Errorf("tx %d: pending pool size mismatch: have %d, want %d", i, len(pool.pending), 0)
} }
list, _ := pool.queue.get(account)
if i <= testTxPoolConfig.AccountQueue { if i <= testTxPoolConfig.AccountQueue {
if pool.queue[account].Len() != int(i) { if list.Len() != int(i) {
t.Errorf("tx %d: queue size mismatch: have %d, want %d", i, pool.queue[account].Len(), i) t.Errorf("tx %d: queue size mismatch: have %d, want %d", i, list.Len(), i)
} }
} else { } else {
if pool.queue[account].Len() != int(testTxPoolConfig.AccountQueue) { if list.Len() != int(testTxPoolConfig.AccountQueue) {
t.Errorf("tx %d: queue limit mismatch: have %d, want %d", i, pool.queue[account].Len(), testTxPoolConfig.AccountQueue) t.Errorf("tx %d: queue limit mismatch: have %d, want %d", i, list.Len(), testTxPoolConfig.AccountQueue)
} }
} }
} }
@ -1020,7 +1025,7 @@ func TestQueueGlobalLimiting(t *testing.T) {
pool.addRemotesSync(txs) pool.addRemotesSync(txs)
queued := 0 queued := 0
for addr, list := range pool.queue { for addr, list := range pool.queue.queued {
if list.Len() > int(config.AccountQueue) { if list.Len() > int(config.AccountQueue) {
t.Errorf("addr %x: queued accounts overflown allowance: %d > %d", addr, list.Len(), config.AccountQueue) t.Errorf("addr %x: queued accounts overflown allowance: %d > %d", addr, list.Len(), config.AccountQueue)
} }
@ -1179,8 +1184,8 @@ func TestPendingLimiting(t *testing.T) {
if pool.pending[account].Len() != int(i)+1 { if pool.pending[account].Len() != int(i)+1 {
t.Errorf("tx %d: pending pool size mismatch: have %d, want %d", i, pool.pending[account].Len(), i+1) t.Errorf("tx %d: pending pool size mismatch: have %d, want %d", i, pool.pending[account].Len(), i+1)
} }
if len(pool.queue) != 0 { if len(pool.queue.addresses()) != 0 {
t.Errorf("tx %d: queue size mismatch: have %d, want %d", i, pool.queue[account].Len(), 0) t.Errorf("tx %d: queue size mismatch: have %d, want %d", i, len(pool.queue.addresses()), 0)
} }
} }
if pool.all.Count() != int(testTxPoolConfig.AccountQueue+5) { if pool.all.Count() != int(testTxPoolConfig.AccountQueue+5) {

View file

@ -0,0 +1,275 @@
// Copyright 2025 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package legacypool
import (
"sort"
"time"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/txpool"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/log"
)
// queue manages nonce-gapped transactions that have been validated but are
// not yet processable.
type queue struct {
config Config
signer types.Signer
queued map[common.Address]*list // Queued but non-processable transactions
beats map[common.Address]time.Time // Last heartbeat from each known account
}
func newQueue(config Config, signer types.Signer) *queue {
return &queue{
signer: signer,
config: config,
queued: make(map[common.Address]*list),
beats: make(map[common.Address]time.Time),
}
}
// evictList returns the hashes of transactions that are old enough to be evicted.
func (q *queue) evictList() []common.Hash {
var removed []common.Hash
for addr, list := range q.queued {
if time.Since(q.beats[addr]) > q.config.Lifetime {
for _, tx := range list.Flatten() {
removed = append(removed, tx.Hash())
}
}
}
queuedEvictionMeter.Mark(int64(len(removed)))
return removed
}
func (q *queue) stats() int {
queued := 0
for _, list := range q.queued {
queued += list.Len()
}
return queued
}
func (q *queue) content() map[common.Address][]*types.Transaction {
queued := make(map[common.Address][]*types.Transaction, len(q.queued))
for addr, list := range q.queued {
queued[addr] = list.Flatten()
}
return queued
}
func (q *queue) contentFrom(addr common.Address) []*types.Transaction {
var queued []*types.Transaction
if list, ok := q.get(addr); ok {
queued = list.Flatten()
}
return queued
}
func (q *queue) get(addr common.Address) (*list, bool) {
l, ok := q.queued[addr]
return l, ok
}
func (q *queue) bump(addr common.Address) {
q.beats[addr] = time.Now()
}
func (q *queue) addresses() []common.Address {
addrs := make([]common.Address, 0, len(q.queued))
for addr := range q.queued {
addrs = append(addrs, addr)
}
return addrs
}
func (q *queue) remove(addr common.Address, tx *types.Transaction) {
if future := q.queued[addr]; future != nil {
if txOld := future.txs.Get(tx.Nonce()); txOld != nil && txOld.Hash() != tx.Hash() {
// Edge case, a different transaction
// with the same nonce is in the queued, just ignore
return
}
if removed, _ := future.Remove(tx); removed {
// Reduce the queued counter
queuedGauge.Dec(1)
}
if future.Empty() {
delete(q.queued, addr)
delete(q.beats, addr)
}
}
}
func (q *queue) add(tx *types.Transaction) (*common.Hash, error) {
// Try to insert the transaction into the future queue
from, _ := types.Sender(q.signer, tx) // already validated
if q.queued[from] == nil {
q.queued[from] = newList(false)
}
inserted, old := q.queued[from].Add(tx, q.config.PriceBump)
if !inserted {
// An older transaction was better, discard this
queuedDiscardMeter.Mark(1)
return nil, txpool.ErrReplaceUnderpriced
}
// If we never record the heartbeat, do it right now.
if _, exist := q.beats[from]; !exist {
q.beats[from] = time.Now()
}
if old == nil {
// Nothing was replaced, bump the queued counter
queuedGauge.Inc(1)
return nil, nil
}
h := old.Hash()
// Transaction was replaced, bump the replacement counter
queuedReplaceMeter.Mark(1)
return &h, nil
}
// promoteExecutables iterates over all accounts with queued transactions, selecting
// for promotion any that are now executable. It also drops any transactions that are
// deemed too old (nonce too low) or too costly (insufficient funds or over gas limit).
//
// Returns three lists:
// - all transactions that were removed from the queue and selected for promotion;
// - all other transactions that were removed from the queue and dropped;
// - the list of addresses removed.
func (q *queue) promoteExecutables(accounts []common.Address, gasLimit uint64, currentState *state.StateDB, nonces *noncer) ([]*types.Transaction, []common.Hash, []common.Address) {
// Track the promotable transactions to broadcast them at once
var (
promotable []*types.Transaction
dropped []common.Hash
removedAddresses []common.Address
)
// Iterate over all accounts and promote any executable transactions
for _, addr := range accounts {
list := q.queued[addr]
if list == nil {
continue // Just in case someone calls with a non existing account
}
// Drop all transactions that are deemed too old (low nonce)
forwards := list.Forward(currentState.GetNonce(addr))
for _, tx := range forwards {
dropped = append(dropped, tx.Hash())
}
log.Trace("Removing old queued transactions", "count", len(forwards))
// Drop all transactions that are too costly (low balance or out of gas)
drops, _ := list.Filter(currentState.GetBalance(addr), gasLimit)
for _, tx := range drops {
dropped = append(dropped, tx.Hash())
}
log.Trace("Removing unpayable queued transactions", "count", len(drops))
queuedNofundsMeter.Mark(int64(len(drops)))
// Gather all executable transactions and promote them
readies := list.Ready(nonces.get(addr))
promotable = append(promotable, readies...)
log.Trace("Promoting queued transactions", "count", len(promotable))
queuedGauge.Dec(int64(len(readies)))
// Drop all transactions over the allowed limit
var caps = list.Cap(int(q.config.AccountQueue))
for _, tx := range caps {
hash := tx.Hash()
dropped = append(dropped, hash)
log.Trace("Removing cap-exceeding queued transaction", "hash", hash)
}
queuedRateLimitMeter.Mark(int64(len(caps)))
// Delete the entire queue entry if it became empty.
if list.Empty() {
delete(q.queued, addr)
delete(q.beats, addr)
removedAddresses = append(removedAddresses, addr)
}
}
queuedGauge.Dec(int64(len(dropped)))
return promotable, dropped, removedAddresses
}
// truncate drops the oldest transactions from the queue until the total
// number is below the configured limit. Returns the hashes of all dropped
// transactions and the addresses of accounts that became empty due to
// the truncation.
func (q *queue) truncate() ([]common.Hash, []common.Address) {
queued := uint64(0)
for _, list := range q.queued {
queued += uint64(list.Len())
}
if queued <= q.config.GlobalQueue {
return nil, nil
}
// Sort all accounts with queued transactions by heartbeat
addresses := make(addressesByHeartbeat, 0, len(q.queued))
for addr := range q.queued {
addresses = append(addresses, addressByHeartbeat{addr, q.beats[addr]})
}
sort.Sort(sort.Reverse(addresses))
// Drop transactions until the total is below the limit
var (
removed = make([]common.Hash, 0)
removedAddresses = make([]common.Address, 0)
)
for drop := queued - q.config.GlobalQueue; drop > 0 && len(addresses) > 0; {
addr := addresses[len(addresses)-1]
list := q.queued[addr.address]
addresses = addresses[:len(addresses)-1]
// Drop all transactions if they are less than the overflow
if size := uint64(list.Len()); size <= drop {
for _, tx := range list.Flatten() {
q.remove(addr.address, tx)
removed = append(removed, tx.Hash())
}
drop -= size
queuedRateLimitMeter.Mark(int64(size))
removedAddresses = append(removedAddresses, addr.address)
continue
}
// Otherwise drop only last few transactions
txs := list.Flatten()
for i := len(txs) - 1; i >= 0 && drop > 0; i-- {
q.remove(addr.address, txs[i])
removed = append(removed, txs[i].Hash())
drop--
queuedRateLimitMeter.Mark(1)
}
}
// No need to clear empty accounts, remove already does that
return removed, removedAddresses
}
// addressByHeartbeat is an account address tagged with its last activity timestamp.
type addressByHeartbeat struct {
address common.Address
heartbeat time.Time
}
type addressesByHeartbeat []addressByHeartbeat
func (a addressesByHeartbeat) Len() int { return len(a) }
func (a addressesByHeartbeat) Less(i, j int) bool { return a[i].heartbeat.Before(a[j].heartbeat) }
func (a addressesByHeartbeat) Swap(i, j int) { a[i], a[j] = a[j], a[i] }

View file

@ -21,6 +21,7 @@ import (
"fmt" "fmt"
"math/big" "math/big"
"github.com/ethereum/go-ethereum/common/bitutil"
"github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
) )
@ -125,9 +126,7 @@ func MergeBloom(receipts Receipts) Bloom {
for _, receipt := range receipts { for _, receipt := range receipts {
if len(receipt.Logs) != 0 { if len(receipt.Logs) != 0 {
bl := receipt.Bloom.Bytes() bl := receipt.Bloom.Bytes()
for i := range bin { bitutil.ORBytes(bin[:], bin[:], bl)
bin[i] |= bl[i]
}
} }
} }
return bin return bin

View file

@ -486,3 +486,11 @@ func (b *EthAPIBackend) StateAtBlock(ctx context.Context, block *types.Block, re
func (b *EthAPIBackend) StateAtTransaction(ctx context.Context, block *types.Block, txIndex int, reexec uint64) (*types.Transaction, vm.BlockContext, *state.StateDB, tracers.StateReleaseFunc, error) { func (b *EthAPIBackend) StateAtTransaction(ctx context.Context, block *types.Block, txIndex int, reexec uint64) (*types.Transaction, vm.BlockContext, *state.StateDB, tracers.StateReleaseFunc, error) {
return b.eth.stateAtTransaction(ctx, block, txIndex, reexec) return b.eth.stateAtTransaction(ctx, block, txIndex, reexec)
} }
func (b *EthAPIBackend) RPCTxSyncDefaultTimeout() time.Duration {
return b.eth.config.TxSyncDefaultTimeout
}
func (b *EthAPIBackend) RPCTxSyncMaxTimeout() time.Duration {
return b.eth.config.TxSyncMaxTimeout
}

View file

@ -47,7 +47,6 @@ import (
// Register adds the engine API to the full node. // Register adds the engine API to the full node.
func Register(stack *node.Node, backend *eth.Ethereum) error { func Register(stack *node.Node, backend *eth.Ethereum) error {
log.Warn("Engine API enabled", "protocol", "eth")
stack.RegisterAPIs([]rpc.API{ stack.RegisterAPIs([]rpc.API{
{ {
Namespace: "engine", Namespace: "engine",

View file

@ -49,27 +49,29 @@ var FullNodeGPO = gasprice.Config{
// Defaults contains default settings for use on the Ethereum main net. // Defaults contains default settings for use on the Ethereum main net.
var Defaults = Config{ var Defaults = Config{
HistoryMode: history.KeepAll, HistoryMode: history.KeepAll,
SyncMode: SnapSync, SyncMode: SnapSync,
NetworkId: 0, // enable auto configuration of networkID == chainID NetworkId: 0, // enable auto configuration of networkID == chainID
TxLookupLimit: 2350000, TxLookupLimit: 2350000,
TransactionHistory: 2350000, TransactionHistory: 2350000,
LogHistory: 2350000, LogHistory: 2350000,
StateHistory: params.FullImmutabilityThreshold, StateHistory: params.FullImmutabilityThreshold,
DatabaseCache: 512, DatabaseCache: 512,
TrieCleanCache: 154, TrieCleanCache: 154,
TrieDirtyCache: 256, TrieDirtyCache: 256,
TrieTimeout: 60 * time.Minute, TrieTimeout: 60 * time.Minute,
SnapshotCache: 102, SnapshotCache: 102,
FilterLogCacheSize: 32, FilterLogCacheSize: 32,
LogQueryLimit: 1000, LogQueryLimit: 1000,
Miner: miner.DefaultConfig, Miner: miner.DefaultConfig,
TxPool: legacypool.DefaultConfig, TxPool: legacypool.DefaultConfig,
BlobPool: blobpool.DefaultConfig, BlobPool: blobpool.DefaultConfig,
RPCGasCap: 50000000, RPCGasCap: 50000000,
RPCEVMTimeout: 5 * time.Second, RPCEVMTimeout: 5 * time.Second,
GPO: FullNodeGPO, GPO: FullNodeGPO,
RPCTxFeeCap: 1, // 1 ether RPCTxFeeCap: 1, // 1 ether
TxSyncDefaultTimeout: 20 * time.Second,
TxSyncMaxTimeout: 1 * time.Minute,
} }
//go:generate go run github.com/fjl/gencodec -type Config -formats toml -out gen_config.go //go:generate go run github.com/fjl/gencodec -type Config -formats toml -out gen_config.go
@ -183,6 +185,10 @@ type Config struct {
// OverrideVerkle (TODO: remove after the fork) // OverrideVerkle (TODO: remove after the fork)
OverrideVerkle *uint64 `toml:",omitempty"` OverrideVerkle *uint64 `toml:",omitempty"`
// EIP-7966: eth_sendRawTransactionSync timeouts
TxSyncDefaultTimeout time.Duration `toml:",omitempty"`
TxSyncMaxTimeout time.Duration `toml:",omitempty"`
} }
// CreateConsensusEngine creates a consensus engine for the given chain config. // CreateConsensusEngine creates a consensus engine for the given chain config.

View file

@ -58,10 +58,12 @@ func (c Config) MarshalTOML() (interface{}, error) {
RPCGasCap uint64 RPCGasCap uint64
RPCEVMTimeout time.Duration RPCEVMTimeout time.Duration
RPCTxFeeCap float64 RPCTxFeeCap float64
OverrideOsaka *uint64 `toml:",omitempty"` OverrideOsaka *uint64 `toml:",omitempty"`
OverrideBPO1 *uint64 `toml:",omitempty"` OverrideBPO1 *uint64 `toml:",omitempty"`
OverrideBPO2 *uint64 `toml:",omitempty"` OverrideBPO2 *uint64 `toml:",omitempty"`
OverrideVerkle *uint64 `toml:",omitempty"` OverrideVerkle *uint64 `toml:",omitempty"`
TxSyncDefaultTimeout time.Duration `toml:",omitempty"`
TxSyncMaxTimeout time.Duration `toml:",omitempty"`
} }
var enc Config var enc Config
enc.Genesis = c.Genesis enc.Genesis = c.Genesis
@ -109,6 +111,8 @@ func (c Config) MarshalTOML() (interface{}, error) {
enc.OverrideBPO1 = c.OverrideBPO1 enc.OverrideBPO1 = c.OverrideBPO1
enc.OverrideBPO2 = c.OverrideBPO2 enc.OverrideBPO2 = c.OverrideBPO2
enc.OverrideVerkle = c.OverrideVerkle enc.OverrideVerkle = c.OverrideVerkle
enc.TxSyncDefaultTimeout = c.TxSyncDefaultTimeout
enc.TxSyncMaxTimeout = c.TxSyncMaxTimeout
return &enc, nil return &enc, nil
} }
@ -156,10 +160,12 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error {
RPCGasCap *uint64 RPCGasCap *uint64
RPCEVMTimeout *time.Duration RPCEVMTimeout *time.Duration
RPCTxFeeCap *float64 RPCTxFeeCap *float64
OverrideOsaka *uint64 `toml:",omitempty"` OverrideOsaka *uint64 `toml:",omitempty"`
OverrideBPO1 *uint64 `toml:",omitempty"` OverrideBPO1 *uint64 `toml:",omitempty"`
OverrideBPO2 *uint64 `toml:",omitempty"` OverrideBPO2 *uint64 `toml:",omitempty"`
OverrideVerkle *uint64 `toml:",omitempty"` OverrideVerkle *uint64 `toml:",omitempty"`
TxSyncDefaultTimeout *time.Duration `toml:",omitempty"`
TxSyncMaxTimeout *time.Duration `toml:",omitempty"`
} }
var dec Config var dec Config
if err := unmarshal(&dec); err != nil { if err := unmarshal(&dec); err != nil {
@ -300,5 +306,11 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error {
if dec.OverrideVerkle != nil { if dec.OverrideVerkle != nil {
c.OverrideVerkle = dec.OverrideVerkle c.OverrideVerkle = dec.OverrideVerkle
} }
if dec.TxSyncDefaultTimeout != nil {
c.TxSyncDefaultTimeout = *dec.TxSyncDefaultTimeout
}
if dec.TxSyncMaxTimeout != nil {
c.TxSyncMaxTimeout = *dec.TxSyncMaxTimeout
}
return nil return nil
} }

View file

@ -144,6 +144,7 @@ func (api *FilterAPI) NewPendingTransactionFilter(fullTx *bool) rpc.ID {
api.filtersMu.Unlock() api.filtersMu.Unlock()
go func() { go func() {
defer pendingTxSub.Unsubscribe()
for { for {
select { select {
case pTx := <-pendingTxs: case pTx := <-pendingTxs:
@ -156,7 +157,6 @@ func (api *FilterAPI) NewPendingTransactionFilter(fullTx *bool) rpc.ID {
api.filtersMu.Lock() api.filtersMu.Lock()
delete(api.filters, pendingTxSub.ID) delete(api.filters, pendingTxSub.ID)
api.filtersMu.Unlock() api.filtersMu.Unlock()
pendingTxSub.Unsubscribe()
return return
} }
} }
@ -219,6 +219,7 @@ func (api *FilterAPI) NewBlockFilter() rpc.ID {
api.filtersMu.Unlock() api.filtersMu.Unlock()
go func() { go func() {
defer headerSub.Unsubscribe()
for { for {
select { select {
case h := <-headers: case h := <-headers:
@ -405,6 +406,7 @@ func (api *FilterAPI) NewFilter(crit FilterCriteria) (rpc.ID, error) {
api.filtersMu.Unlock() api.filtersMu.Unlock()
go func() { go func() {
defer logsSub.Unsubscribe()
for { for {
select { select {
case l := <-logs: case l := <-logs:

View file

@ -181,8 +181,7 @@ func newHandler(config *handlerConfig) (*handler, error) {
} else { } else {
head := h.chain.CurrentBlock() head := h.chain.CurrentBlock()
if head.Number.Uint64() > 0 && h.chain.HasState(head.Root) { if head.Number.Uint64() > 0 && h.chain.HasState(head.Root) {
// Print warning log if database is not empty to run snap sync. log.Info("Switch sync mode from snap sync to full sync", "reason", "snap sync complete")
log.Warn("Switch sync mode from snap sync to full sync", "reason", "snap sync complete")
} else { } else {
// If snap sync was requested and our database is empty, grant it // If snap sync was requested and our database is empty, grant it
h.snapSync.Store(true) h.snapSync.Store(true)

View file

@ -494,12 +494,19 @@ func handleTransactions(backend Backend, msg Decoder, peer *Peer) error {
if err := msg.Decode(&txs); err != nil { if err := msg.Decode(&txs); err != nil {
return err return err
} }
// Duplicate transactions are not allowed
seen := make(map[common.Hash]struct{})
for i, tx := range txs { for i, tx := range txs {
// Validate and mark the remote transaction // Validate and mark the remote transaction
if tx == nil { if tx == nil {
return fmt.Errorf("Transactions: transaction %d is nil", i) return fmt.Errorf("Transactions: transaction %d is nil", i)
} }
peer.markTransaction(tx.Hash()) hash := tx.Hash()
if _, exists := seen[hash]; exists {
return fmt.Errorf("Transactions: multiple copies of the same hash %v", hash)
}
seen[hash] = struct{}{}
peer.markTransaction(hash)
} }
return backend.Handle(peer, &txs) return backend.Handle(peer, &txs)
} }
@ -514,12 +521,19 @@ func handlePooledTransactions(backend Backend, msg Decoder, peer *Peer) error {
if err := msg.Decode(&txs); err != nil { if err := msg.Decode(&txs); err != nil {
return err return err
} }
// Duplicate transactions are not allowed
seen := make(map[common.Hash]struct{})
for i, tx := range txs.PooledTransactionsResponse { for i, tx := range txs.PooledTransactionsResponse {
// Validate and mark the remote transaction // Validate and mark the remote transaction
if tx == nil { if tx == nil {
return fmt.Errorf("PooledTransactions: transaction %d is nil", i) return fmt.Errorf("PooledTransactions: transaction %d is nil", i)
} }
peer.markTransaction(tx.Hash()) hash := tx.Hash()
if _, exists := seen[hash]; exists {
return fmt.Errorf("PooledTransactions: multiple copies of the same hash %v", hash)
}
seen[hash] = struct{}{}
peer.markTransaction(hash)
} }
requestTracker.Fulfil(peer.id, peer.version, PooledTransactionsMsg, txs.RequestId) requestTracker.Fulfil(peer.id, peer.version, PooledTransactionsMsg, txs.RequestId)

View file

@ -23,6 +23,7 @@ import (
"errors" "errors"
"fmt" "fmt"
"math/big" "math/big"
"time"
"github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
@ -705,6 +706,39 @@ func (ec *Client) SendTransaction(ctx context.Context, tx *types.Transaction) er
return ec.c.CallContext(ctx, nil, "eth_sendRawTransaction", hexutil.Encode(data)) return ec.c.CallContext(ctx, nil, "eth_sendRawTransaction", hexutil.Encode(data))
} }
// SendTransactionSync submits a signed tx and waits for a receipt (or until
// the optional timeout elapses on the server side). If timeout == 0, the server
// uses its default.
func (ec *Client) SendTransactionSync(
ctx context.Context,
tx *types.Transaction,
timeout *time.Duration,
) (*types.Receipt, error) {
raw, err := tx.MarshalBinary()
if err != nil {
return nil, err
}
return ec.SendRawTransactionSync(ctx, raw, timeout)
}
func (ec *Client) SendRawTransactionSync(
ctx context.Context,
rawTx []byte,
timeout *time.Duration,
) (*types.Receipt, error) {
var ms *hexutil.Uint64
if timeout != nil {
if d := hexutil.Uint64(timeout.Milliseconds()); d > 0 {
ms = &d
}
}
var receipt types.Receipt
if err := ec.c.CallContext(ctx, &receipt, "eth_sendRawTransactionSync", hexutil.Bytes(rawTx), ms); err != nil {
return nil, err
}
return &receipt, nil
}
// RevertErrorData returns the 'revert reason' data of a contract call. // RevertErrorData returns the 'revert reason' data of a contract call.
// //
// This can be used with CallContract and EstimateGas, and only when the server is Geth. // This can be used with CallContract and EstimateGas, and only when the server is Geth.
@ -828,3 +862,89 @@ func (p *rpcProgress) toSyncProgress() *ethereum.SyncProgress {
StateIndexRemaining: uint64(p.StateIndexRemaining), StateIndexRemaining: uint64(p.StateIndexRemaining),
} }
} }
// SimulateOptions represents the options for eth_simulateV1.
type SimulateOptions struct {
BlockStateCalls []SimulateBlock `json:"blockStateCalls"`
TraceTransfers bool `json:"traceTransfers"`
Validation bool `json:"validation"`
ReturnFullTransactions bool `json:"returnFullTransactions"`
}
// SimulateBlock represents a batch of calls to be simulated.
type SimulateBlock struct {
BlockOverrides *ethereum.BlockOverrides `json:"blockOverrides,omitempty"`
StateOverrides map[common.Address]ethereum.OverrideAccount `json:"stateOverrides,omitempty"`
Calls []ethereum.CallMsg `json:"calls"`
}
// MarshalJSON implements json.Marshaler for SimulateBlock.
func (s SimulateBlock) MarshalJSON() ([]byte, error) {
type Alias struct {
BlockOverrides *ethereum.BlockOverrides `json:"blockOverrides,omitempty"`
StateOverrides map[common.Address]ethereum.OverrideAccount `json:"stateOverrides,omitempty"`
Calls []interface{} `json:"calls"`
}
calls := make([]interface{}, len(s.Calls))
for i, call := range s.Calls {
calls[i] = toCallArg(call)
}
return json.Marshal(Alias{
BlockOverrides: s.BlockOverrides,
StateOverrides: s.StateOverrides,
Calls: calls,
})
}
//go:generate go run github.com/fjl/gencodec -type SimulateCallResult -field-override simulateCallResultMarshaling -out gen_simulate_call_result.go
// SimulateCallResult is the result of a simulated call.
type SimulateCallResult struct {
ReturnValue []byte `json:"returnData"`
Logs []*types.Log `json:"logs"`
GasUsed uint64 `json:"gasUsed"`
Status uint64 `json:"status"`
Error *CallError `json:"error,omitempty"`
}
type simulateCallResultMarshaling struct {
ReturnValue hexutil.Bytes
GasUsed hexutil.Uint64
Status hexutil.Uint64
}
// CallError represents an error from a simulated call.
type CallError struct {
Code int `json:"code"`
Message string `json:"message"`
Data string `json:"data,omitempty"`
}
//go:generate go run github.com/fjl/gencodec -type SimulateBlockResult -field-override simulateBlockResultMarshaling -out gen_simulate_block_result.go
// SimulateBlockResult represents the result of a simulated block.
type SimulateBlockResult struct {
Number *big.Int `json:"number"`
Hash common.Hash `json:"hash"`
Timestamp uint64 `json:"timestamp"`
GasLimit uint64 `json:"gasLimit"`
GasUsed uint64 `json:"gasUsed"`
FeeRecipient common.Address `json:"miner"`
BaseFeePerGas *big.Int `json:"baseFeePerGas,omitempty"`
Calls []SimulateCallResult `json:"calls"`
}
type simulateBlockResultMarshaling struct {
Number *hexutil.Big
Timestamp hexutil.Uint64
GasLimit hexutil.Uint64
GasUsed hexutil.Uint64
BaseFeePerGas *hexutil.Big
}
// SimulateV1 executes transactions on top of a base state.
func (ec *Client) SimulateV1(ctx context.Context, opts SimulateOptions, blockNrOrHash *rpc.BlockNumberOrHash) ([]SimulateBlockResult, error) {
var result []SimulateBlockResult
err := ec.c.CallContext(ctx, &result, "eth_simulateV1", opts, blockNrOrHash)
return result, err
}

View file

@ -754,3 +754,250 @@ func ExampleRevertErrorData() {
// revert: 08c379a00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000a75736572206572726f72 // revert: 08c379a00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000a75736572206572726f72
// message: user error // message: user error
} }
func TestSimulateV1(t *testing.T) {
backend, _, err := newTestBackend(nil)
if err != nil {
t.Fatalf("Failed to create test backend: %v", err)
}
defer backend.Close()
client := ethclient.NewClient(backend.Attach())
defer client.Close()
ctx := context.Background()
// Get current base fee
header, err := client.HeaderByNumber(ctx, nil)
if err != nil {
t.Fatalf("Failed to get header: %v", err)
}
// Simple test: transfer ETH from one account to another
from := testAddr
to := common.HexToAddress("0x0000000000000000000000000000000000000001")
value := big.NewInt(100)
gas := uint64(100000)
maxFeePerGas := new(big.Int).Mul(header.BaseFee, big.NewInt(2))
opts := ethclient.SimulateOptions{
BlockStateCalls: []ethclient.SimulateBlock{
{
Calls: []ethereum.CallMsg{
{
From: from,
To: &to,
Value: value,
Gas: gas,
GasFeeCap: maxFeePerGas,
},
},
},
},
Validation: true,
}
results, err := client.SimulateV1(ctx, opts, nil)
if err != nil {
t.Fatalf("SimulateV1 failed: %v", err)
}
if len(results) != 1 {
t.Fatalf("expected 1 block result, got %d", len(results))
}
if len(results[0].Calls) != 1 {
t.Fatalf("expected 1 call result, got %d", len(results[0].Calls))
}
// Check that the transaction succeeded
if results[0].Calls[0].Status != 1 {
t.Errorf("expected status 1 (success), got %d", results[0].Calls[0].Status)
}
if results[0].Calls[0].Error != nil {
t.Errorf("expected no error, got %v", results[0].Calls[0].Error)
}
}
func TestSimulateV1WithBlockOverrides(t *testing.T) {
backend, _, err := newTestBackend(nil)
if err != nil {
t.Fatalf("Failed to create test backend: %v", err)
}
defer backend.Close()
client := ethclient.NewClient(backend.Attach())
defer client.Close()
ctx := context.Background()
// Get current base fee
header, err := client.HeaderByNumber(ctx, nil)
if err != nil {
t.Fatalf("Failed to get header: %v", err)
}
from := testAddr
to := common.HexToAddress("0x0000000000000000000000000000000000000001")
value := big.NewInt(100)
gas := uint64(100000)
maxFeePerGas := new(big.Int).Mul(header.BaseFee, big.NewInt(2))
// Override timestamp only
timestamp := uint64(1234567890)
opts := ethclient.SimulateOptions{
BlockStateCalls: []ethclient.SimulateBlock{
{
BlockOverrides: &ethereum.BlockOverrides{
Time: timestamp,
},
Calls: []ethereum.CallMsg{
{
From: from,
To: &to,
Value: value,
Gas: gas,
GasFeeCap: maxFeePerGas,
},
},
},
},
Validation: true,
}
results, err := client.SimulateV1(ctx, opts, nil)
if err != nil {
t.Fatalf("SimulateV1 with block overrides failed: %v", err)
}
if len(results) != 1 {
t.Fatalf("expected 1 block result, got %d", len(results))
}
// Verify the timestamp was overridden
if results[0].Timestamp != timestamp {
t.Errorf("expected timestamp %d, got %d", timestamp, results[0].Timestamp)
}
}
func TestSimulateV1WithStateOverrides(t *testing.T) {
backend, _, err := newTestBackend(nil)
if err != nil {
t.Fatalf("Failed to create test backend: %v", err)
}
defer backend.Close()
client := ethclient.NewClient(backend.Attach())
defer client.Close()
ctx := context.Background()
// Get current base fee
header, err := client.HeaderByNumber(ctx, nil)
if err != nil {
t.Fatalf("Failed to get header: %v", err)
}
from := testAddr
to := common.HexToAddress("0x0000000000000000000000000000000000000001")
value := big.NewInt(1000000000000000000) // 1 ETH
gas := uint64(100000)
maxFeePerGas := new(big.Int).Mul(header.BaseFee, big.NewInt(2))
// Override the balance of the 'from' address
balanceStr := "1000000000000000000000"
balance := new(big.Int)
balance.SetString(balanceStr, 10)
stateOverrides := map[common.Address]ethereum.OverrideAccount{
from: {
Balance: balance,
},
}
opts := ethclient.SimulateOptions{
BlockStateCalls: []ethclient.SimulateBlock{
{
StateOverrides: stateOverrides,
Calls: []ethereum.CallMsg{
{
From: from,
To: &to,
Value: value,
Gas: gas,
GasFeeCap: maxFeePerGas,
},
},
},
},
Validation: true,
}
results, err := client.SimulateV1(ctx, opts, nil)
if err != nil {
t.Fatalf("SimulateV1 with state overrides failed: %v", err)
}
if len(results) != 1 {
t.Fatalf("expected 1 block result, got %d", len(results))
}
if results[0].Calls[0].Status != 1 {
t.Errorf("expected status 1 (success), got %d", results[0].Calls[0].Status)
}
}
func TestSimulateV1WithBlockNumberOrHash(t *testing.T) {
backend, _, err := newTestBackend(nil)
if err != nil {
t.Fatalf("Failed to create test backend: %v", err)
}
defer backend.Close()
client := ethclient.NewClient(backend.Attach())
defer client.Close()
ctx := context.Background()
// Get current base fee
header, err := client.HeaderByNumber(ctx, nil)
if err != nil {
t.Fatalf("Failed to get header: %v", err)
}
from := testAddr
to := common.HexToAddress("0x0000000000000000000000000000000000000001")
value := big.NewInt(100)
gas := uint64(100000)
maxFeePerGas := new(big.Int).Mul(header.BaseFee, big.NewInt(2))
opts := ethclient.SimulateOptions{
BlockStateCalls: []ethclient.SimulateBlock{
{
Calls: []ethereum.CallMsg{
{
From: from,
To: &to,
Value: value,
Gas: gas,
GasFeeCap: maxFeePerGas,
},
},
},
},
Validation: true,
}
// Simulate on the latest block
latest := rpc.BlockNumberOrHashWithNumber(rpc.LatestBlockNumber)
results, err := client.SimulateV1(ctx, opts, &latest)
if err != nil {
t.Fatalf("SimulateV1 with latest block failed: %v", err)
}
if len(results) != 1 {
t.Fatalf("expected 1 block result, got %d", len(results))
}
}

View file

@ -0,0 +1,80 @@
// Code generated by github.com/fjl/gencodec. DO NOT EDIT.
package ethclient
import (
"encoding/json"
"math/big"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
)
var _ = (*simulateBlockResultMarshaling)(nil)
// MarshalJSON marshals as JSON.
func (s SimulateBlockResult) MarshalJSON() ([]byte, error) {
type SimulateBlockResult struct {
Number *hexutil.Big `json:"number"`
Hash common.Hash `json:"hash"`
Timestamp hexutil.Uint64 `json:"timestamp"`
GasLimit hexutil.Uint64 `json:"gasLimit"`
GasUsed hexutil.Uint64 `json:"gasUsed"`
FeeRecipient common.Address `json:"miner"`
BaseFeePerGas *hexutil.Big `json:"baseFeePerGas,omitempty"`
Calls []SimulateCallResult `json:"calls"`
}
var enc SimulateBlockResult
enc.Number = (*hexutil.Big)(s.Number)
enc.Hash = s.Hash
enc.Timestamp = hexutil.Uint64(s.Timestamp)
enc.GasLimit = hexutil.Uint64(s.GasLimit)
enc.GasUsed = hexutil.Uint64(s.GasUsed)
enc.FeeRecipient = s.FeeRecipient
enc.BaseFeePerGas = (*hexutil.Big)(s.BaseFeePerGas)
enc.Calls = s.Calls
return json.Marshal(&enc)
}
// UnmarshalJSON unmarshals from JSON.
func (s *SimulateBlockResult) UnmarshalJSON(input []byte) error {
type SimulateBlockResult struct {
Number *hexutil.Big `json:"number"`
Hash *common.Hash `json:"hash"`
Timestamp *hexutil.Uint64 `json:"timestamp"`
GasLimit *hexutil.Uint64 `json:"gasLimit"`
GasUsed *hexutil.Uint64 `json:"gasUsed"`
FeeRecipient *common.Address `json:"miner"`
BaseFeePerGas *hexutil.Big `json:"baseFeePerGas,omitempty"`
Calls []SimulateCallResult `json:"calls"`
}
var dec SimulateBlockResult
if err := json.Unmarshal(input, &dec); err != nil {
return err
}
if dec.Number != nil {
s.Number = (*big.Int)(dec.Number)
}
if dec.Hash != nil {
s.Hash = *dec.Hash
}
if dec.Timestamp != nil {
s.Timestamp = uint64(*dec.Timestamp)
}
if dec.GasLimit != nil {
s.GasLimit = uint64(*dec.GasLimit)
}
if dec.GasUsed != nil {
s.GasUsed = uint64(*dec.GasUsed)
}
if dec.FeeRecipient != nil {
s.FeeRecipient = *dec.FeeRecipient
}
if dec.BaseFeePerGas != nil {
s.BaseFeePerGas = (*big.Int)(dec.BaseFeePerGas)
}
if dec.Calls != nil {
s.Calls = dec.Calls
}
return nil
}

View file

@ -0,0 +1,61 @@
// Code generated by github.com/fjl/gencodec. DO NOT EDIT.
package ethclient
import (
"encoding/json"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/types"
)
var _ = (*simulateCallResultMarshaling)(nil)
// MarshalJSON marshals as JSON.
func (s SimulateCallResult) MarshalJSON() ([]byte, error) {
type SimulateCallResult struct {
ReturnValue hexutil.Bytes `json:"returnData"`
Logs []*types.Log `json:"logs"`
GasUsed hexutil.Uint64 `json:"gasUsed"`
Status hexutil.Uint64 `json:"status"`
Error *CallError `json:"error,omitempty"`
}
var enc SimulateCallResult
enc.ReturnValue = s.ReturnValue
enc.Logs = s.Logs
enc.GasUsed = hexutil.Uint64(s.GasUsed)
enc.Status = hexutil.Uint64(s.Status)
enc.Error = s.Error
return json.Marshal(&enc)
}
// UnmarshalJSON unmarshals from JSON.
func (s *SimulateCallResult) UnmarshalJSON(input []byte) error {
type SimulateCallResult struct {
ReturnValue *hexutil.Bytes `json:"returnData"`
Logs []*types.Log `json:"logs"`
GasUsed *hexutil.Uint64 `json:"gasUsed"`
Status *hexutil.Uint64 `json:"status"`
Error *CallError `json:"error,omitempty"`
}
var dec SimulateCallResult
if err := json.Unmarshal(input, &dec); err != nil {
return err
}
if dec.ReturnValue != nil {
s.ReturnValue = *dec.ReturnValue
}
if dec.Logs != nil {
s.Logs = dec.Logs
}
if dec.GasUsed != nil {
s.GasUsed = uint64(*dec.GasUsed)
}
if dec.Status != nil {
s.Status = uint64(*dec.Status)
}
if dec.Error != nil {
s.Error = dec.Error
}
return nil
}

View file

@ -19,7 +19,6 @@ package gethclient
import ( import (
"context" "context"
"encoding/json"
"fmt" "fmt"
"math/big" "math/big"
"runtime" "runtime"
@ -280,97 +279,8 @@ func toCallArg(msg ethereum.CallMsg) interface{} {
return arg return arg
} }
// OverrideAccount specifies the state of an account to be overridden. // OverrideAccount is an alias for ethereum.OverrideAccount.
type OverrideAccount struct { type OverrideAccount = ethereum.OverrideAccount
// Nonce sets nonce of the account. Note: the nonce override will only
// be applied when it is set to a non-zero value.
Nonce uint64
// Code sets the contract code. The override will be applied // BlockOverrides is an alias for ethereum.BlockOverrides.
// when the code is non-nil, i.e. setting empty code is possible type BlockOverrides = ethereum.BlockOverrides
// using an empty slice.
Code []byte
// Balance sets the account balance.
Balance *big.Int
// State sets the complete storage. The override will be applied
// when the given map is non-nil. Using an empty map wipes the
// entire contract storage during the call.
State map[common.Hash]common.Hash
// StateDiff allows overriding individual storage slots.
StateDiff map[common.Hash]common.Hash
}
func (a OverrideAccount) MarshalJSON() ([]byte, error) {
type acc struct {
Nonce hexutil.Uint64 `json:"nonce,omitempty"`
Code string `json:"code,omitempty"`
Balance *hexutil.Big `json:"balance,omitempty"`
State interface{} `json:"state,omitempty"`
StateDiff map[common.Hash]common.Hash `json:"stateDiff,omitempty"`
}
output := acc{
Nonce: hexutil.Uint64(a.Nonce),
Balance: (*hexutil.Big)(a.Balance),
StateDiff: a.StateDiff,
}
if a.Code != nil {
output.Code = hexutil.Encode(a.Code)
}
if a.State != nil {
output.State = a.State
}
return json.Marshal(output)
}
// BlockOverrides specifies the set of header fields to override.
type BlockOverrides struct {
// Number overrides the block number.
Number *big.Int
// Difficulty overrides the block difficulty.
Difficulty *big.Int
// Time overrides the block timestamp. Time is applied only when
// it is non-zero.
Time uint64
// GasLimit overrides the block gas limit. GasLimit is applied only when
// it is non-zero.
GasLimit uint64
// Coinbase overrides the block coinbase. Coinbase is applied only when
// it is different from the zero address.
Coinbase common.Address
// Random overrides the block extra data which feeds into the RANDOM opcode.
// Random is applied only when it is a non-zero hash.
Random common.Hash
// BaseFee overrides the block base fee.
BaseFee *big.Int
}
func (o BlockOverrides) MarshalJSON() ([]byte, error) {
type override struct {
Number *hexutil.Big `json:"number,omitempty"`
Difficulty *hexutil.Big `json:"difficulty,omitempty"`
Time hexutil.Uint64 `json:"time,omitempty"`
GasLimit hexutil.Uint64 `json:"gasLimit,omitempty"`
Coinbase *common.Address `json:"feeRecipient,omitempty"`
Random *common.Hash `json:"prevRandao,omitempty"`
BaseFee *hexutil.Big `json:"baseFeePerGas,omitempty"`
}
output := override{
Number: (*hexutil.Big)(o.Number),
Difficulty: (*hexutil.Big)(o.Difficulty),
Time: hexutil.Uint64(o.Time),
GasLimit: hexutil.Uint64(o.GasLimit),
BaseFee: (*hexutil.Big)(o.BaseFee),
}
if o.Coinbase != (common.Address{}) {
output.Coinbase = &o.Coinbase
}
if o.Random != (common.Hash{}) {
output.Random = &o.Random
}
return json.Marshal(output)
}

View file

@ -121,6 +121,10 @@ type AncientReaderOp interface {
// - if maxBytes is not specified, 'count' items will be returned if they are present // - if maxBytes is not specified, 'count' items will be returned if they are present
AncientRange(kind string, start, count, maxBytes uint64) ([][]byte, error) AncientRange(kind string, start, count, maxBytes uint64) ([][]byte, error)
// AncientBytes retrieves the value segment of the element specified by the id
// and value offsets.
AncientBytes(kind string, id, offset, length uint64) ([]byte, error)
// Ancients returns the ancient item numbers in the ancient store. // Ancients returns the ancient item numbers in the ancient store.
Ancients() (uint64, error) Ancients() (uint64, error)

View file

@ -140,6 +140,10 @@ func (db *Database) Close() error {
return nil return nil
} }
func (db *Database) AncientBytes(kind string, id, offset, length uint64) ([]byte, error) {
panic("not supported")
}
func New(client *rpc.Client) ethdb.Database { func New(client *rpc.Client) ethdb.Database {
if client == nil { if client == nil {
return nil return nil

View file

@ -19,10 +19,12 @@ package ethereum
import ( import (
"context" "context"
"encoding/json"
"errors" "errors"
"math/big" "math/big"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
) )
@ -293,3 +295,98 @@ type BlockNumberReader interface {
type ChainIDReader interface { type ChainIDReader interface {
ChainID(ctx context.Context) (*big.Int, error) ChainID(ctx context.Context) (*big.Int, error)
} }
// OverrideAccount specifies the state of an account to be overridden.
type OverrideAccount struct {
// Nonce sets nonce of the account. Note: the nonce override will only
// be applied when it is set to a non-zero value.
Nonce uint64
// Code sets the contract code. The override will be applied
// when the code is non-nil, i.e. setting empty code is possible
// using an empty slice.
Code []byte
// Balance sets the account balance.
Balance *big.Int
// State sets the complete storage. The override will be applied
// when the given map is non-nil. Using an empty map wipes the
// entire contract storage during the call.
State map[common.Hash]common.Hash
// StateDiff allows overriding individual storage slots.
StateDiff map[common.Hash]common.Hash
}
func (a OverrideAccount) MarshalJSON() ([]byte, error) {
type acc struct {
Nonce hexutil.Uint64 `json:"nonce,omitempty"`
Code string `json:"code,omitempty"`
Balance *hexutil.Big `json:"balance,omitempty"`
State interface{} `json:"state,omitempty"`
StateDiff map[common.Hash]common.Hash `json:"stateDiff,omitempty"`
}
output := acc{
Nonce: hexutil.Uint64(a.Nonce),
Balance: (*hexutil.Big)(a.Balance),
StateDiff: a.StateDiff,
}
if a.Code != nil {
output.Code = hexutil.Encode(a.Code)
}
if a.State != nil {
output.State = a.State
}
return json.Marshal(output)
}
// BlockOverrides specifies the set of header fields to override.
type BlockOverrides struct {
// Number overrides the block number.
Number *big.Int
// Difficulty overrides the block difficulty.
Difficulty *big.Int
// Time overrides the block timestamp. Time is applied only when
// it is non-zero.
Time uint64
// GasLimit overrides the block gas limit. GasLimit is applied only when
// it is non-zero.
GasLimit uint64
// Coinbase overrides the block coinbase. Coinbase is applied only when
// it is different from the zero address.
Coinbase common.Address
// Random overrides the block extra data which feeds into the RANDOM opcode.
// Random is applied only when it is a non-zero hash.
Random common.Hash
// BaseFee overrides the block base fee.
BaseFee *big.Int
}
func (o BlockOverrides) MarshalJSON() ([]byte, error) {
type override struct {
Number *hexutil.Big `json:"number,omitempty"`
Difficulty *hexutil.Big `json:"difficulty,omitempty"`
Time hexutil.Uint64 `json:"time,omitempty"`
GasLimit hexutil.Uint64 `json:"gasLimit,omitempty"`
Coinbase *common.Address `json:"feeRecipient,omitempty"`
Random *common.Hash `json:"prevRandao,omitempty"`
BaseFee *hexutil.Big `json:"baseFeePerGas,omitempty"`
}
output := override{
Number: (*hexutil.Big)(o.Number),
Difficulty: (*hexutil.Big)(o.Difficulty),
Time: hexutil.Uint64(o.Time),
GasLimit: hexutil.Uint64(o.GasLimit),
BaseFee: (*hexutil.Big)(o.BaseFee),
}
if o.Coinbase != (common.Address{}) {
output.Coinbase = &o.Coinbase
}
if o.Random != (common.Hash{}) {
output.Random = &o.Random
}
return json.Marshal(output)
}

View file

@ -55,6 +55,7 @@ import (
const estimateGasErrorRatio = 0.015 const estimateGasErrorRatio = 0.015
var errBlobTxNotSupported = errors.New("signing blob transactions not supported") var errBlobTxNotSupported = errors.New("signing blob transactions not supported")
var errSubClosed = errors.New("chain subscription closed")
// EthereumAPI provides an API to access Ethereum related information. // EthereumAPI provides an API to access Ethereum related information.
type EthereumAPI struct { type EthereumAPI struct {
@ -1619,16 +1620,9 @@ func (api *TransactionAPI) SendTransaction(ctx context.Context, args Transaction
// processing (signing + broadcast). // processing (signing + broadcast).
func (api *TransactionAPI) FillTransaction(ctx context.Context, args TransactionArgs) (*SignTransactionResult, error) { func (api *TransactionAPI) FillTransaction(ctx context.Context, args TransactionArgs) (*SignTransactionResult, error) {
// Set some sanity defaults and terminate on failure // Set some sanity defaults and terminate on failure
sidecarVersion := types.BlobSidecarVersion0
if len(args.Blobs) > 0 {
h := api.b.CurrentHeader()
if api.b.ChainConfig().IsOsaka(h.Number, h.Time) {
sidecarVersion = types.BlobSidecarVersion1
}
}
config := sidecarConfig{ config := sidecarConfig{
blobSidecarAllowed: true, blobSidecarAllowed: true,
blobSidecarVersion: sidecarVersion, blobSidecarVersion: api.currentBlobSidecarVersion(),
} }
if err := args.setDefaults(ctx, api.b, config); err != nil { if err := args.setDefaults(ctx, api.b, config); err != nil {
return nil, err return nil, err
@ -1642,6 +1636,14 @@ func (api *TransactionAPI) FillTransaction(ctx context.Context, args Transaction
return &SignTransactionResult{data, tx}, nil return &SignTransactionResult{data, tx}, nil
} }
func (api *TransactionAPI) currentBlobSidecarVersion() byte {
h := api.b.CurrentHeader()
if api.b.ChainConfig().IsOsaka(h.Number, h.Time) {
return types.BlobSidecarVersion1
}
return types.BlobSidecarVersion0
}
// SendRawTransaction will add the signed transaction to the transaction pool. // SendRawTransaction will add the signed transaction to the transaction pool.
// The sender is responsible for signing the transaction and using the correct nonce. // The sender is responsible for signing the transaction and using the correct nonce.
func (api *TransactionAPI) SendRawTransaction(ctx context.Context, input hexutil.Bytes) (common.Hash, error) { func (api *TransactionAPI) SendRawTransaction(ctx context.Context, input hexutil.Bytes) (common.Hash, error) {
@ -1649,9 +1651,119 @@ func (api *TransactionAPI) SendRawTransaction(ctx context.Context, input hexutil
if err := tx.UnmarshalBinary(input); err != nil { if err := tx.UnmarshalBinary(input); err != nil {
return common.Hash{}, err return common.Hash{}, err
} }
// Convert legacy blob transaction proofs.
// TODO: remove in go-ethereum v1.17.x
if sc := tx.BlobTxSidecar(); sc != nil {
exp := api.currentBlobSidecarVersion()
if sc.Version == types.BlobSidecarVersion0 && exp == types.BlobSidecarVersion1 {
if err := sc.ToV1(); err != nil {
return common.Hash{}, fmt.Errorf("blob sidecar conversion failed: %v", err)
}
tx = tx.WithBlobTxSidecar(sc)
}
}
return SubmitTransaction(ctx, api.b, tx) return SubmitTransaction(ctx, api.b, tx)
} }
// SendRawTransactionSync will add the signed transaction to the transaction pool
// and wait until the transaction has been included in a block and return the receipt, or the timeout.
func (api *TransactionAPI) SendRawTransactionSync(ctx context.Context, input hexutil.Bytes, timeoutMs *hexutil.Uint64) (map[string]interface{}, error) {
tx := new(types.Transaction)
if err := tx.UnmarshalBinary(input); err != nil {
return nil, err
}
// Convert legacy blob transaction proofs.
// TODO: remove in go-ethereum v1.17.x
if sc := tx.BlobTxSidecar(); sc != nil {
exp := api.currentBlobSidecarVersion()
if sc.Version == types.BlobSidecarVersion0 && exp == types.BlobSidecarVersion1 {
if err := sc.ToV1(); err != nil {
return nil, fmt.Errorf("blob sidecar conversion failed: %v", err)
}
tx = tx.WithBlobTxSidecar(sc)
}
}
ch := make(chan core.ChainEvent, 128)
sub := api.b.SubscribeChainEvent(ch)
defer sub.Unsubscribe()
hash, err := SubmitTransaction(ctx, api.b, tx)
if err != nil {
return nil, err
}
var (
maxTimeout = api.b.RPCTxSyncMaxTimeout()
defaultTimeout = api.b.RPCTxSyncDefaultTimeout()
timeout = defaultTimeout
)
if timeoutMs != nil && *timeoutMs > 0 {
req := time.Duration(*timeoutMs) * time.Millisecond
if req > maxTimeout {
timeout = maxTimeout
} else {
timeout = req
}
}
receiptCtx, cancel := context.WithTimeout(ctx, timeout)
defer cancel()
// Fast path.
if r, err := api.GetTransactionReceipt(receiptCtx, hash); err == nil && r != nil {
return r, nil
}
// Monitor the receipts
for {
select {
case <-receiptCtx.Done():
// If server-side wait window elapsed, return the structured timeout.
if errors.Is(receiptCtx.Err(), context.DeadlineExceeded) {
return nil, &txSyncTimeoutError{
msg: fmt.Sprintf("The transaction was added to the transaction pool but wasn't processed in %v", timeout),
hash: hash,
}
}
return nil, receiptCtx.Err()
case err, ok := <-sub.Err():
if !ok {
return nil, errSubClosed
}
return nil, err
case ev, ok := <-ch:
if !ok {
return nil, errSubClosed
}
rs, txs := ev.Receipts, ev.Transactions
if len(rs) == 0 || len(rs) != len(txs) {
continue
}
for i := range rs {
if rs[i].TxHash == hash {
if rs[i].BlockNumber != nil && rs[i].BlockHash != (common.Hash{}) {
signer := types.LatestSigner(api.b.ChainConfig())
return MarshalReceipt(
rs[i],
rs[i].BlockHash,
rs[i].BlockNumber.Uint64(),
signer,
txs[i],
int(rs[i].TransactionIndex),
), nil
}
return api.GetTransactionReceipt(receiptCtx, hash)
}
}
}
}
}
// Sign calculates an ECDSA signature for: // Sign calculates an ECDSA signature for:
// keccak256("\x19Ethereum Signed Message:\n" + len(message) + message). // keccak256("\x19Ethereum Signed Message:\n" + len(message) + message).
// //

View file

@ -440,6 +440,19 @@ type testBackend struct {
pending *types.Block pending *types.Block
pendingReceipts types.Receipts pendingReceipts types.Receipts
chainFeed *event.Feed
autoMine bool
sentTx *types.Transaction
sentTxHash common.Hash
syncDefaultTimeout time.Duration
syncMaxTimeout time.Duration
}
func fakeBlockHash(txh common.Hash) common.Hash {
return crypto.Keccak256Hash([]byte("testblock"), txh.Bytes())
} }
func newTestBackend(t *testing.T, n int, gspec *core.Genesis, engine consensus.Engine, generator func(i int, b *core.BlockGen)) *testBackend { func newTestBackend(t *testing.T, n int, gspec *core.Genesis, engine consensus.Engine, generator func(i int, b *core.BlockGen)) *testBackend {
@ -466,6 +479,7 @@ func newTestBackend(t *testing.T, n int, gspec *core.Genesis, engine consensus.E
acc: acc, acc: acc,
pending: blocks[n], pending: blocks[n],
pendingReceipts: receipts[n], pendingReceipts: receipts[n],
chainFeed: new(event.Feed),
} }
return backend return backend
} }
@ -587,19 +601,64 @@ func (b testBackend) GetEVM(ctx context.Context, state *state.StateDB, header *t
return vm.NewEVM(context, state, b.chain.Config(), *vmConfig) return vm.NewEVM(context, state, b.chain.Config(), *vmConfig)
} }
func (b testBackend) SubscribeChainEvent(ch chan<- core.ChainEvent) event.Subscription { func (b testBackend) SubscribeChainEvent(ch chan<- core.ChainEvent) event.Subscription {
panic("implement me") return b.chainFeed.Subscribe(ch)
} }
func (b testBackend) SubscribeChainHeadEvent(ch chan<- core.ChainHeadEvent) event.Subscription { func (b testBackend) SubscribeChainHeadEvent(ch chan<- core.ChainHeadEvent) event.Subscription {
panic("implement me") panic("implement me")
} }
func (b testBackend) SendTx(ctx context.Context, signedTx *types.Transaction) error { func (b *testBackend) SendTx(ctx context.Context, tx *types.Transaction) error {
panic("implement me") b.sentTx = tx
b.sentTxHash = tx.Hash()
if b.autoMine {
// Synthesize a "mined" receipt at head+1
num := b.chain.CurrentHeader().Number.Uint64() + 1
receipt := &types.Receipt{
TxHash: tx.Hash(),
Status: types.ReceiptStatusSuccessful,
BlockHash: fakeBlockHash(tx.Hash()),
BlockNumber: new(big.Int).SetUint64(num),
TransactionIndex: 0,
CumulativeGasUsed: 21000,
GasUsed: 21000,
}
// Broadcast a ChainEvent that includes the receipts and txs
b.chainFeed.Send(core.ChainEvent{
Header: &types.Header{
Number: new(big.Int).SetUint64(num),
},
Receipts: types.Receipts{receipt},
Transactions: types.Transactions{tx},
})
}
return nil
} }
func (b testBackend) GetCanonicalTransaction(txHash common.Hash) (bool, *types.Transaction, common.Hash, uint64, uint64) { func (b *testBackend) GetCanonicalTransaction(txHash common.Hash) (bool, *types.Transaction, common.Hash, uint64, uint64) {
// Treat the auto-mined tx as canonically placed at head+1.
if b.autoMine && txHash == b.sentTxHash {
num := b.chain.CurrentHeader().Number.Uint64() + 1
return true, b.sentTx, fakeBlockHash(txHash), num, 0
}
tx, blockHash, blockNumber, index := rawdb.ReadCanonicalTransaction(b.db, txHash) tx, blockHash, blockNumber, index := rawdb.ReadCanonicalTransaction(b.db, txHash)
return tx != nil, tx, blockHash, blockNumber, index return tx != nil, tx, blockHash, blockNumber, index
} }
func (b testBackend) GetCanonicalReceipt(tx *types.Transaction, blockHash common.Hash, blockNumber, blockIndex uint64) (*types.Receipt, error) { func (b *testBackend) GetCanonicalReceipt(tx *types.Transaction, blockHash common.Hash, blockNumber, blockIndex uint64) (*types.Receipt, error) {
if b.autoMine && tx != nil && tx.Hash() == b.sentTxHash &&
blockHash == fakeBlockHash(tx.Hash()) &&
blockIndex == 0 &&
blockNumber == b.chain.CurrentHeader().Number.Uint64()+1 {
return &types.Receipt{
Type: tx.Type(),
Status: types.ReceiptStatusSuccessful,
CumulativeGasUsed: 21000,
GasUsed: 21000,
EffectiveGasPrice: big.NewInt(1),
BlockHash: blockHash,
BlockNumber: new(big.Int).SetUint64(blockNumber),
TransactionIndex: 0,
TxHash: tx.Hash(),
}, nil
}
return b.chain.GetCanonicalReceipt(tx, blockHash, blockNumber, blockIndex) return b.chain.GetCanonicalReceipt(tx, blockHash, blockNumber, blockIndex)
} }
func (b testBackend) TxIndexDone() bool { func (b testBackend) TxIndexDone() bool {
@ -3889,3 +3948,109 @@ func (b configTimeBackend) HeaderByNumber(_ context.Context, n rpc.BlockNumber)
func (b configTimeBackend) CurrentHeader() *types.Header { func (b configTimeBackend) CurrentHeader() *types.Header {
return &types.Header{Time: b.time} return &types.Header{Time: b.time}
} }
func (b *testBackend) RPCTxSyncDefaultTimeout() time.Duration {
if b.syncDefaultTimeout != 0 {
return b.syncDefaultTimeout
}
return 2 * time.Second
}
func (b *testBackend) RPCTxSyncMaxTimeout() time.Duration {
if b.syncMaxTimeout != 0 {
return b.syncMaxTimeout
}
return 5 * time.Minute
}
func (b *backendMock) RPCTxSyncDefaultTimeout() time.Duration { return 2 * time.Second }
func (b *backendMock) RPCTxSyncMaxTimeout() time.Duration { return 5 * time.Minute }
func makeSignedRaw(t *testing.T, api *TransactionAPI, from, to common.Address, value *big.Int) (hexutil.Bytes, *types.Transaction) {
t.Helper()
fillRes, err := api.FillTransaction(context.Background(), TransactionArgs{
From: &from,
To: &to,
Value: (*hexutil.Big)(value),
})
if err != nil {
t.Fatalf("FillTransaction failed: %v", err)
}
signRes, err := api.SignTransaction(context.Background(), argsFromTransaction(fillRes.Tx, from))
if err != nil {
t.Fatalf("SignTransaction failed: %v", err)
}
return signRes.Raw, signRes.Tx
}
// makeSelfSignedRaw is a convenience for a 0-ETH self-transfer.
func makeSelfSignedRaw(t *testing.T, api *TransactionAPI, addr common.Address) (hexutil.Bytes, *types.Transaction) {
return makeSignedRaw(t, api, addr, addr, big.NewInt(0))
}
func TestSendRawTransactionSync_Success(t *testing.T) {
t.Parallel()
genesis := &core.Genesis{
Config: params.TestChainConfig,
Alloc: types.GenesisAlloc{},
}
b := newTestBackend(t, 0, genesis, ethash.NewFaker(), nil)
b.autoMine = true // immediately “mines” the tx in-memory
api := NewTransactionAPI(b, new(AddrLocker))
raw, _ := makeSelfSignedRaw(t, api, b.acc.Address)
receipt, err := api.SendRawTransactionSync(context.Background(), raw, nil)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if receipt == nil {
t.Fatalf("expected non-nil receipt")
}
if _, ok := receipt["blockNumber"]; !ok {
t.Fatalf("expected blockNumber in receipt, got %#v", receipt)
}
}
func TestSendRawTransactionSync_Timeout(t *testing.T) {
t.Parallel()
genesis := &core.Genesis{
Config: params.TestChainConfig,
Alloc: types.GenesisAlloc{},
}
b := newTestBackend(t, 0, genesis, ethash.NewFaker(), nil)
b.autoMine = false // don't mine, should time out
api := NewTransactionAPI(b, new(AddrLocker))
raw, _ := makeSelfSignedRaw(t, api, b.acc.Address)
timeout := hexutil.Uint64(200) // 200ms
receipt, err := api.SendRawTransactionSync(context.Background(), raw, &timeout)
if receipt != nil {
t.Fatalf("expected nil receipt, got %#v", receipt)
}
if err == nil {
t.Fatalf("expected timeout error, got nil")
}
// assert error shape & data (hash)
var de interface {
ErrorCode() int
ErrorData() interface{}
}
if !errors.As(err, &de) {
t.Fatalf("expected data error with code/data, got %T %v", err, err)
}
if de.ErrorCode() != errCodeTxSyncTimeout {
t.Fatalf("expected code %d, got %d", errCodeTxSyncTimeout, de.ErrorCode())
}
tx := new(types.Transaction)
if e := tx.UnmarshalBinary(raw); e != nil {
t.Fatal(e)
}
if got, want := de.ErrorData(), tx.Hash().Hex(); got != want {
t.Fatalf("expected ErrorData=%s, got %v", want, got)
}
}

View file

@ -53,6 +53,8 @@ type Backend interface {
RPCEVMTimeout() time.Duration // global timeout for eth_call over rpc: DoS protection RPCEVMTimeout() time.Duration // global timeout for eth_call over rpc: DoS protection
RPCTxFeeCap() float64 // global tx fee cap for all transaction related APIs RPCTxFeeCap() float64 // global tx fee cap for all transaction related APIs
UnprotectedAllowed() bool // allows only for EIP155 transactions. UnprotectedAllowed() bool // allows only for EIP155 transactions.
RPCTxSyncDefaultTimeout() time.Duration
RPCTxSyncMaxTimeout() time.Duration
// Blockchain API // Blockchain API
SetHead(number uint64) SetHead(number uint64)

View file

@ -21,6 +21,7 @@ import (
"fmt" "fmt"
"github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/core/vm"
@ -33,6 +34,11 @@ type revertError struct {
reason string // revert reason hex encoded reason string // revert reason hex encoded
} }
type txSyncTimeoutError struct {
msg string
hash common.Hash
}
// ErrorCode returns the JSON error code for a revert. // ErrorCode returns the JSON error code for a revert.
// See: https://ethereum.org/en/developers/docs/apis/json-rpc/#error-codes // See: https://ethereum.org/en/developers/docs/apis/json-rpc/#error-codes
func (e *revertError) ErrorCode() int { func (e *revertError) ErrorCode() int {
@ -108,6 +114,7 @@ const (
errCodeInvalidParams = -32602 errCodeInvalidParams = -32602
errCodeReverted = -32000 errCodeReverted = -32000
errCodeVMError = -32015 errCodeVMError = -32015
errCodeTxSyncTimeout = 4
) )
func txValidationError(err error) *invalidTxError { func txValidationError(err error) *invalidTxError {
@ -168,3 +175,7 @@ type blockGasLimitReachedError struct{ message string }
func (e *blockGasLimitReachedError) Error() string { return e.message } func (e *blockGasLimitReachedError) Error() string { return e.message }
func (e *blockGasLimitReachedError) ErrorCode() int { return errCodeBlockGasLimitReached } func (e *blockGasLimitReachedError) ErrorCode() int { return errCodeBlockGasLimitReached }
func (e *txSyncTimeoutError) Error() string { return e.msg }
func (e *txSyncTimeoutError) ErrorCode() int { return errCodeTxSyncTimeout }
func (e *txSyncTimeoutError) ErrorData() interface{} { return e.hash.Hex() }

View file

@ -500,7 +500,7 @@ func (w *gzipResponseWriter) init() {
hdr := w.resp.Header() hdr := w.resp.Header()
length := hdr.Get("content-length") length := hdr.Get("content-length")
if len(length) > 0 { if len(length) > 0 {
if n, err := strconv.ParseUint(length, 10, 64); err != nil { if n, err := strconv.ParseUint(length, 10, 64); err == nil {
w.hasLength = true w.hasLength = true
w.contentLength = n w.contentLength = n
} }

View file

@ -153,6 +153,7 @@ type lookupIterator struct {
cancel func() cancel func()
lookup *lookup lookup *lookup
tabRefreshing <-chan struct{} tabRefreshing <-chan struct{}
lastLookup time.Time
} }
type lookupFunc func(ctx context.Context) *lookup type lookupFunc func(ctx context.Context) *lookup
@ -185,6 +186,9 @@ func (it *lookupIterator) Next() bool {
return false return false
} }
if it.lookup == nil { if it.lookup == nil {
// Ensure enough time has passed between lookup creations.
it.slowdown()
it.lookup = it.nextLookup(it.ctx) it.lookup = it.nextLookup(it.ctx)
if it.lookup.empty() { if it.lookup.empty() {
// If the lookup is empty right after creation, it means the local table // If the lookup is empty right after creation, it means the local table
@ -235,6 +239,25 @@ func (it *lookupIterator) lookupFailed(tab *Table, timeout time.Duration) {
tab.waitForNodes(tout, 1) tab.waitForNodes(tout, 1)
} }
// slowdown applies a delay between creating lookups. This exists to prevent hot-spinning
// in some test environments where lookups don't yield any results.
func (it *lookupIterator) slowdown() {
const minInterval = 1 * time.Second
now := time.Now()
diff := now.Sub(it.lastLookup)
it.lastLookup = now
if diff > minInterval {
return
}
wait := time.NewTimer(diff)
defer wait.Stop()
select {
case <-wait.C:
case <-it.ctx.Done():
}
}
// Close ends the iterator. // Close ends the iterator.
func (it *lookupIterator) Close() { func (it *lookupIterator) Close() {
it.cancel() it.cancel()

View file

@ -54,9 +54,8 @@ const (
bucketIPLimit, bucketSubnet = 2, 24 // at most 2 addresses from the same /24 bucketIPLimit, bucketSubnet = 2, 24 // at most 2 addresses from the same /24
tableIPLimit, tableSubnet = 10, 24 tableIPLimit, tableSubnet = 10, 24
seedMinTableTime = 5 * time.Minute seedCount = 30
seedCount = 30 seedMaxAge = 5 * 24 * time.Hour
seedMaxAge = 5 * 24 * time.Hour
) )
// Table is the 'node table', a Kademlia-like index of neighbor nodes. The table keeps // Table is the 'node table', a Kademlia-like index of neighbor nodes. The table keeps

View file

@ -575,6 +575,13 @@ func startLocalhostV4(t *testing.T, cfg Config) *UDPv4 {
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
// Wait for bootstrap to complete.
select {
case <-udp.tab.initDone:
case <-time.After(5 * time.Second):
t.Fatalf("timed out waiting for table initialization")
}
return udp return udp
} }

View file

@ -19,6 +19,7 @@ package enode
import ( import (
"crypto/ecdsa" "crypto/ecdsa"
"encoding/base64" "encoding/base64"
"encoding/binary"
"encoding/hex" "encoding/hex"
"errors" "errors"
"fmt" "fmt"
@ -358,9 +359,10 @@ func ParseID(in string) (ID, error) {
// Returns -1 if a is closer to target, 1 if b is closer to target // Returns -1 if a is closer to target, 1 if b is closer to target
// and 0 if they are equal. // and 0 if they are equal.
func DistCmp(target, a, b ID) int { func DistCmp(target, a, b ID) int {
for i := range target { for i := 0; i < len(target); i += 8 {
da := a[i] ^ target[i] tn := binary.BigEndian.Uint64(target[i : i+8])
db := b[i] ^ target[i] da := tn ^ binary.BigEndian.Uint64(a[i:i+8])
db := tn ^ binary.BigEndian.Uint64(b[i:i+8])
if da > db { if da > db {
return 1 return 1
} else if da < db { } else if da < db {
@ -373,12 +375,14 @@ func DistCmp(target, a, b ID) int {
// LogDist returns the logarithmic distance between a and b, log2(a ^ b). // LogDist returns the logarithmic distance between a and b, log2(a ^ b).
func LogDist(a, b ID) int { func LogDist(a, b ID) int {
lz := 0 lz := 0
for i := range a { for i := 0; i < len(a); i += 8 {
x := a[i] ^ b[i] ai := binary.BigEndian.Uint64(a[i : i+8])
bi := binary.BigEndian.Uint64(b[i : i+8])
x := ai ^ bi
if x == 0 { if x == 0 {
lz += 8 lz += 64
} else { } else {
lz += bits.LeadingZeros8(x) lz += bits.LeadingZeros64(x)
break break
} }
} }

View file

@ -368,6 +368,16 @@ func TestID_distcmpEqual(t *testing.T) {
} }
} }
func BenchmarkDistCmp(b *testing.B) {
base := ID{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}
aID := ID{15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0}
bID := ID{15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 1}
b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = DistCmp(base, aID, bID)
}
}
func TestID_logdist(t *testing.T) { func TestID_logdist(t *testing.T) {
logdistBig := func(a, b ID) int { logdistBig := func(a, b ID) int {
abig, bbig := new(big.Int).SetBytes(a[:]), new(big.Int).SetBytes(b[:]) abig, bbig := new(big.Int).SetBytes(a[:]), new(big.Int).SetBytes(b[:])
@ -378,6 +388,28 @@ func TestID_logdist(t *testing.T) {
} }
} }
func makeIDs() (ID, ID) {
var a, b ID
size := len(a)
// last byte differs
for i := 0; i < size-1; i++ {
a[i] = 0xAA
b[i] = 0xAA
}
a[size-1] = 0xAA
b[size-1] = 0xAB
return a, b
}
// Benchmark LogDist
func BenchmarkLogDist(b *testing.B) {
aID, bID := makeIDs() // 256-bit ID
b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = LogDist(aID, bID)
}
}
// The random tests is likely to miss the case where a and b are equal, // The random tests is likely to miss the case where a and b are equal,
// this test checks it explicitly. // this test checks it explicitly.
func TestID_logdistEqual(t *testing.T) { func TestID_logdistEqual(t *testing.T) {

View file

@ -273,7 +273,8 @@ func TestServerWebsocketReadLimit(t *testing.T) {
} }
} else if !errors.Is(err, websocket.ErrReadLimit) && } else if !errors.Is(err, websocket.ErrReadLimit) &&
!strings.Contains(strings.ToLower(err.Error()), "1009") && !strings.Contains(strings.ToLower(err.Error()), "1009") &&
!strings.Contains(strings.ToLower(err.Error()), "message too big") { !strings.Contains(strings.ToLower(err.Error()), "message too big") &&
!strings.Contains(strings.ToLower(err.Error()), "connection reset by peer") {
// Not the error we expect from exceeding the message size limit. // Not the error we expect from exceeding the message size limit.
t.Fatalf("unexpected error for read limit violation: %v", err) t.Fatalf("unexpected error for read limit violation: %v", err)
} }

View file

@ -211,7 +211,8 @@ func (t *TransitionTrie) UpdateStem(key []byte, values [][]byte) error {
func (t *TransitionTrie) Copy() *TransitionTrie { func (t *TransitionTrie) Copy() *TransitionTrie {
return &TransitionTrie{ return &TransitionTrie{
overlay: t.overlay.Copy(), overlay: t.overlay.Copy(),
base: t.base.Copy(), // base in immutable, so there is no need to copy it
base: t.base,
storage: t.storage, storage: t.storage,
} }
} }

View file

@ -144,25 +144,17 @@ func (r *historyReader) readAccountMetadata(address common.Address, historyID ui
// readStorageMetadata resolves the storage slot metadata within the specified // readStorageMetadata resolves the storage slot metadata within the specified
// state history. // state history.
func (r *historyReader) readStorageMetadata(storageKey common.Hash, storageHash common.Hash, historyID uint64, slotOffset, slotNumber int) ([]byte, error) { func (r *historyReader) readStorageMetadata(storageKey common.Hash, storageHash common.Hash, historyID uint64, slotOffset, slotNumber int) ([]byte, error) {
// TODO(rj493456442) optimize it with partial read data, err := rawdb.ReadStateStorageIndex(r.freezer, historyID, slotIndexSize*slotOffset, slotIndexSize*slotNumber)
blob := rawdb.ReadStateStorageIndex(r.freezer, historyID) if err != nil {
if len(blob) == 0 { msg := fmt.Sprintf("id: %d, slot-offset: %d, slot-length: %d", historyID, slotOffset, slotNumber)
return nil, fmt.Errorf("storage index is truncated, historyID: %d", historyID) return nil, fmt.Errorf("storage indices corrupted, %s, %w", msg, err)
} }
if len(blob)%slotIndexSize != 0 {
return nil, fmt.Errorf("storage indices is corrupted, historyID: %d, size: %d", historyID, len(blob))
}
if slotIndexSize*(slotOffset+slotNumber) > len(blob) {
return nil, fmt.Errorf("storage indices is truncated, historyID: %d, size: %d, offset: %d, length: %d", historyID, len(blob), slotOffset, slotNumber)
}
subSlice := blob[slotIndexSize*slotOffset : slotIndexSize*(slotOffset+slotNumber)]
// TODO(rj493456442) get rid of the metadata resolution // TODO(rj493456442) get rid of the metadata resolution
var ( var (
m meta m meta
target common.Hash target common.Hash
) )
blob = rawdb.ReadStateHistoryMeta(r.freezer, historyID) blob := rawdb.ReadStateHistoryMeta(r.freezer, historyID)
if err := m.decode(blob); err != nil { if err := m.decode(blob); err != nil {
return nil, err return nil, err
} }
@ -172,17 +164,17 @@ func (r *historyReader) readStorageMetadata(storageKey common.Hash, storageHash
target = storageKey target = storageKey
} }
pos := sort.Search(slotNumber, func(i int) bool { pos := sort.Search(slotNumber, func(i int) bool {
slotID := subSlice[slotIndexSize*i : slotIndexSize*i+common.HashLength] slotID := data[slotIndexSize*i : slotIndexSize*i+common.HashLength]
return bytes.Compare(slotID, target.Bytes()) >= 0 return bytes.Compare(slotID, target.Bytes()) >= 0
}) })
if pos == slotNumber { if pos == slotNumber {
return nil, fmt.Errorf("storage metadata is not found, slot key: %#x, historyID: %d", storageKey, historyID) return nil, fmt.Errorf("storage metadata is not found, slot key: %#x, historyID: %d", storageKey, historyID)
} }
offset := slotIndexSize * pos offset := slotIndexSize * pos
if target != common.BytesToHash(subSlice[offset:offset+common.HashLength]) { if target != common.BytesToHash(data[offset:offset+common.HashLength]) {
return nil, fmt.Errorf("storage metadata is not found, slot key: %#x, historyID: %d", storageKey, historyID) return nil, fmt.Errorf("storage metadata is not found, slot key: %#x, historyID: %d", storageKey, historyID)
} }
return subSlice[offset : slotIndexSize*(pos+1)], nil return data[offset : slotIndexSize*(pos+1)], nil
} }
// readAccount retrieves the account data from the specified state history. // readAccount retrieves the account data from the specified state history.
@ -194,12 +186,11 @@ func (r *historyReader) readAccount(address common.Address, historyID uint64) ([
length := int(metadata[common.AddressLength]) // one byte for account data length length := int(metadata[common.AddressLength]) // one byte for account data length
offset := int(binary.BigEndian.Uint32(metadata[common.AddressLength+1 : common.AddressLength+5])) // four bytes for the account data offset offset := int(binary.BigEndian.Uint32(metadata[common.AddressLength+1 : common.AddressLength+5])) // four bytes for the account data offset
// TODO(rj493456442) optimize it with partial read data, err := rawdb.ReadStateAccountHistory(r.freezer, historyID, offset, length)
data := rawdb.ReadStateAccountHistory(r.freezer, historyID) if err != nil {
if len(data) < length+offset {
return nil, fmt.Errorf("account data is truncated, address: %#x, historyID: %d, size: %d, offset: %d, len: %d", address, historyID, len(data), offset, length) return nil, fmt.Errorf("account data is truncated, address: %#x, historyID: %d, size: %d, offset: %d, len: %d", address, historyID, len(data), offset, length)
} }
return data[offset : offset+length], nil return data, nil
} }
// readStorage retrieves the storage slot data from the specified state history. // readStorage retrieves the storage slot data from the specified state history.
@ -222,12 +213,11 @@ func (r *historyReader) readStorage(address common.Address, storageKey common.Ha
length := int(slotMetadata[common.HashLength]) // one byte for slot data length length := int(slotMetadata[common.HashLength]) // one byte for slot data length
offset := int(binary.BigEndian.Uint32(slotMetadata[common.HashLength+1 : common.HashLength+5])) // four bytes for slot data offset offset := int(binary.BigEndian.Uint32(slotMetadata[common.HashLength+1 : common.HashLength+5])) // four bytes for slot data offset
// TODO(rj493456442) optimize it with partial read data, err := rawdb.ReadStateStorageHistory(r.freezer, historyID, offset, length)
data := rawdb.ReadStateStorageHistory(r.freezer, historyID) if err != nil {
if len(data) < offset+length {
return nil, fmt.Errorf("storage data is truncated, address: %#x, key: %#x, historyID: %d, size: %d, offset: %d, len: %d", address, storageKey, historyID, len(data), offset, length) return nil, fmt.Errorf("storage data is truncated, address: %#x, key: %#x, historyID: %d, size: %d, offset: %d, len: %d", address, storageKey, historyID, len(data), offset, length)
} }
return data[offset : offset+length], nil return data, nil
} }
// read retrieves the state element data associated with the stateID. // read retrieves the state element data associated with the stateID.

View file

@ -161,7 +161,7 @@ func newTrienodeHistory(root common.Hash, parent common.Hash, block uint64, node
// sharedLen returns the length of the common prefix shared by a and b. // sharedLen returns the length of the common prefix shared by a and b.
func sharedLen(a, b []byte) int { func sharedLen(a, b []byte) int {
n := min(len(a), len(b)) n := min(len(a), len(b))
for i := 0; i < n; i++ { for i := range n {
if a[i] != b[i] { if a[i] != b[i] {
return i return i
} }
@ -295,7 +295,7 @@ func decodeHeader(data []byte) (*trienodeMetadata, []common.Hash, []uint32, []ui
keyOffsets = make([]uint32, 0, count) keyOffsets = make([]uint32, 0, count)
valOffsets = make([]uint32, 0, count) valOffsets = make([]uint32, 0, count)
) )
for i := 0; i < count; i++ { for i := range count {
n := trienodeMetadataSize + trienodeTrieHeaderSize*i n := trienodeMetadataSize + trienodeTrieHeaderSize*i
owner := common.BytesToHash(data[n : n+common.HashLength]) owner := common.BytesToHash(data[n : n+common.HashLength])
if i != 0 && bytes.Compare(owner.Bytes(), owners[i-1].Bytes()) <= 0 { if i != 0 && bytes.Compare(owner.Bytes(), owners[i-1].Bytes()) <= 0 {
@ -348,7 +348,7 @@ func decodeSingle(keySection []byte, onValue func([]byte, int, int) error) ([]st
if len(keySection) < int(8*nRestarts)+4 { if len(keySection) < int(8*nRestarts)+4 {
return nil, fmt.Errorf("key section too short, restarts: %d, size: %d", nRestarts, len(keySection)) return nil, fmt.Errorf("key section too short, restarts: %d, size: %d", nRestarts, len(keySection))
} }
for i := 0; i < int(nRestarts); i++ { for i := range int(nRestarts) {
o := len(keySection) - 4 - (int(nRestarts)-i)*8 o := len(keySection) - 4 - (int(nRestarts)-i)*8
keyOffset := binary.BigEndian.Uint32(keySection[o : o+4]) keyOffset := binary.BigEndian.Uint32(keySection[o : o+4])
if i != 0 && keyOffset <= keyOffsets[i-1] { if i != 0 && keyOffset <= keyOffsets[i-1] {
@ -469,7 +469,7 @@ func (h *trienodeHistory) decode(header []byte, keySection []byte, valueSection
h.nodeList = make(map[common.Hash][]string) h.nodeList = make(map[common.Hash][]string)
h.nodes = make(map[common.Hash]map[string][]byte) h.nodes = make(map[common.Hash]map[string][]byte)
for i := 0; i < len(owners); i++ { for i := range len(owners) {
// Resolve the boundary of key section // Resolve the boundary of key section
keyStart := keyOffsets[i] keyStart := keyOffsets[i]
keyLimit := len(keySection) keyLimit := len(keySection)
@ -524,7 +524,7 @@ func newSingleTrienodeHistoryReader(id uint64, reader ethdb.AncientReader, keyRa
} }
keyStart := int(keyRange.start) keyStart := int(keyRange.start)
keyLimit := int(keyRange.limit) keyLimit := int(keyRange.limit)
if keyLimit == math.MaxUint32 { if keyRange.limit == math.MaxUint32 {
keyLimit = len(keyData) keyLimit = len(keyData)
} }
if len(keyData) < keyStart || len(keyData) < keyLimit { if len(keyData) < keyStart || len(keyData) < keyLimit {

View file

@ -19,6 +19,6 @@ package version
const ( const (
Major = 1 // Major version component of the current release Major = 1 // Major version component of the current release
Minor = 16 // Minor version component of the current release Minor = 16 // Minor version component of the current release
Patch = 5 // Patch version component of the current release Patch = 6 // Patch version component of the current release
Meta = "unstable" // Version metadata to append to the version string Meta = "unstable" // Version metadata to append to the version string
) )