mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-23 05:06:43 +00:00
Merge branch 'ethereum:master' into master
This commit is contained in:
commit
5ffb60402f
54 changed files with 2081 additions and 453 deletions
|
|
@ -485,13 +485,13 @@ var bindTests = []struct {
|
|||
contract Defaulter {
|
||||
address public caller;
|
||||
|
||||
function() {
|
||||
fallback() external payable {
|
||||
caller = msg.sender;
|
||||
}
|
||||
}
|
||||
`,
|
||||
[]string{`6060604052606a8060106000396000f360606040523615601d5760e060020a6000350463fc9c8d3981146040575b605e6000805473ffffffffffffffffffffffffffffffffffffffff191633179055565b606060005473ffffffffffffffffffffffffffffffffffffffff1681565b005b6060908152602090f3`},
|
||||
[]string{`[{"constant":true,"inputs":[],"name":"caller","outputs":[{"name":"","type":"address"}],"type":"function"}]`},
|
||||
[]string{`608060405234801561000f575f80fd5b5061013d8061001d5f395ff3fe608060405260043610610021575f3560e01c8063fc9c8d391461006257610022565b5b335f806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055005b34801561006d575f80fd5b5061007661008c565b60405161008391906100ee565b60405180910390f35b5f8054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f6100d8826100af565b9050919050565b6100e8816100ce565b82525050565b5f6020820190506101015f8301846100df565b9291505056fea26469706673582212201e9273ecfb1f534644c77f09a25c21baaba81cf1c444ebc071e12a225a23c72964736f6c63430008140033`},
|
||||
[]string{`[{"stateMutability":"payable","type":"fallback"},{"inputs":[],"name":"caller","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"}]`},
|
||||
`
|
||||
"math/big"
|
||||
|
||||
|
|
|
|||
|
|
@ -277,8 +277,10 @@ func (c *BoundContract) RawCreationTransact(opts *TransactOpts, calldata []byte)
|
|||
// Transfer initiates a plain transaction to move funds to the contract, calling
|
||||
// its default method if one is available.
|
||||
func (c *BoundContract) Transfer(opts *TransactOpts) (*types.Transaction, error) {
|
||||
// todo(rjl493456442) check the payable fallback or receive is defined
|
||||
// or not, reject invalid transaction at the first place
|
||||
// Check if payable fallback or receive is defined
|
||||
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)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -188,6 +188,8 @@ var (
|
|||
utils.AllowUnprotectedTxs,
|
||||
utils.BatchRequestLimit,
|
||||
utils.BatchResponseMaxSize,
|
||||
utils.RPCTxSyncDefaultTimeoutFlag,
|
||||
utils.RPCTxSyncMaxTimeoutFlag,
|
||||
}
|
||||
|
||||
metricsFlags = []cli.Flag{
|
||||
|
|
|
|||
|
|
@ -201,7 +201,7 @@ func expandVerkle(ctx *cli.Context) error {
|
|||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -615,6 +615,18 @@ var (
|
|||
Value: ethconfig.Defaults.LogQueryLimit,
|
||||
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
|
||||
AuthListenFlag = &cli.StringFlag{
|
||||
Name: "authrpc.addr",
|
||||
|
|
@ -1717,6 +1729,12 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) {
|
|||
if ctx.IsSet(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 snap-sync is requested, this flag is also required
|
||||
if cfg.SyncMode == ethconfig.SnapSync {
|
||||
|
|
|
|||
|
|
@ -182,13 +182,14 @@ func (s *filterTestSuite) loadQueries() error {
|
|||
|
||||
// filterQuery is a single query for testing.
|
||||
type filterQuery struct {
|
||||
FromBlock int64 `json:"fromBlock"`
|
||||
ToBlock int64 `json:"toBlock"`
|
||||
Address []common.Address `json:"address"`
|
||||
Topics [][]common.Hash `json:"topics"`
|
||||
ResultHash *common.Hash `json:"resultHash,omitempty"`
|
||||
results []types.Log
|
||||
Err error `json:"error,omitempty"`
|
||||
FromBlock int64 `json:"fromBlock"`
|
||||
ToBlock int64 `json:"toBlock"`
|
||||
lastBlockHash common.Hash
|
||||
Address []common.Address `json:"address"`
|
||||
Topics [][]common.Hash `json:"topics"`
|
||||
ResultHash *common.Hash `json:"resultHash,omitempty"`
|
||||
results []types.Log
|
||||
Err error `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
func (fq *filterQuery) isWildcard() bool {
|
||||
|
|
|
|||
337
cmd/workload/filtertestfuzz.go
Normal file
337
cmd/workload/filtertestfuzz.go
Normal 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
|
||||
}
|
||||
|
|
@ -32,6 +32,17 @@ import (
|
|||
"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 (
|
||||
filterGenerateCommand = &cli.Command{
|
||||
Name: "filtergen",
|
||||
|
|
@ -58,7 +69,7 @@ var (
|
|||
|
||||
// filterGenCmd is the main function of the filter tests generator.
|
||||
func filterGenCmd(ctx *cli.Context) error {
|
||||
f := newFilterTestGen(ctx)
|
||||
f := newFilterTestGen(ctx, maxFilterRangeForTestGen)
|
||||
lastWrite := time.Now()
|
||||
for {
|
||||
select {
|
||||
|
|
@ -67,7 +78,7 @@ func filterGenCmd(ctx *cli.Context) error {
|
|||
default:
|
||||
}
|
||||
|
||||
f.updateFinalizedBlock()
|
||||
f.setLimitToFinalizedBlock()
|
||||
query := f.newQuery()
|
||||
query.run(f.client, nil)
|
||||
if query.Err != nil {
|
||||
|
|
@ -75,7 +86,7 @@ func filterGenCmd(ctx *cli.Context) error {
|
|||
exit("filter query failed")
|
||||
}
|
||||
if len(query.results) > 0 && len(query.results) <= maxFilterResultSize {
|
||||
for {
|
||||
for rand.Intn(100) < filterExtendChance {
|
||||
extQuery := f.extendRange(query)
|
||||
if extQuery == nil {
|
||||
break
|
||||
|
|
@ -108,39 +119,32 @@ func filterGenCmd(ctx *cli.Context) error {
|
|||
|
||||
// filterTestGen is the filter query test generator.
|
||||
type filterTestGen struct {
|
||||
client *client
|
||||
queryFile string
|
||||
client *client
|
||||
queryFile string
|
||||
maxFilterRange int64
|
||||
|
||||
finalizedBlock int64
|
||||
queries [filterBuckets][]*filterQuery
|
||||
blockLimit int64
|
||||
queries [filterBuckets][]*filterQuery
|
||||
}
|
||||
|
||||
func newFilterTestGen(ctx *cli.Context) *filterTestGen {
|
||||
func newFilterTestGen(ctx *cli.Context, maxFilterRange int64) *filterTestGen {
|
||||
return &filterTestGen{
|
||||
client: makeClient(ctx),
|
||||
queryFile: ctx.String(filterQueryFileFlag.Name),
|
||||
client: makeClient(ctx),
|
||||
queryFile: ctx.String(filterQueryFileFlag.Name),
|
||||
maxFilterRange: maxFilterRange,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *filterTestGen) updateFinalizedBlock() {
|
||||
s.finalizedBlock = mustGetFinalizedBlock(s.client)
|
||||
func (s *filterTestGen) setLimitToFinalizedBlock() {
|
||||
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.
|
||||
func (s *filterTestGen) storeQuery(query *filterQuery) {
|
||||
query.ResultHash = new(common.Hash)
|
||||
*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))
|
||||
if bucket >= filterBuckets {
|
||||
bucket = filterBuckets - 1
|
||||
|
|
@ -160,13 +164,13 @@ func (s *filterTestGen) storeQuery(query *filterQuery) {
|
|||
func (s *filterTestGen) extendRange(q *filterQuery) *filterQuery {
|
||||
rangeLen := q.ToBlock + 1 - q.FromBlock
|
||||
extLen := rand.Int63n(rangeLen) + 1
|
||||
if rangeLen+extLen > s.finalizedBlock {
|
||||
if rangeLen+extLen > min(s.maxFilterRange, s.blockLimit) {
|
||||
return nil
|
||||
}
|
||||
extBefore := min(rand.Int63n(extLen+1), q.FromBlock)
|
||||
extAfter := extLen - extBefore
|
||||
if q.ToBlock+extAfter > s.finalizedBlock {
|
||||
d := q.ToBlock + extAfter - s.finalizedBlock
|
||||
if q.ToBlock+extAfter > s.blockLimit {
|
||||
d := q.ToBlock + extAfter - s.blockLimit
|
||||
extAfter -= d
|
||||
if extBefore+d <= q.FromBlock {
|
||||
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.
|
||||
func (s *filterTestGen) newSeedQuery() *filterQuery {
|
||||
block := rand.Int63n(s.finalizedBlock + 1)
|
||||
block := rand.Int63n(s.blockLimit + 1)
|
||||
return &filterQuery{
|
||||
FromBlock: block,
|
||||
ToBlock: block,
|
||||
|
|
@ -358,6 +362,7 @@ func (s *filterTestGen) writeQueries() {
|
|||
func mustGetFinalizedBlock(client *client) int64 {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
|
||||
defer cancel()
|
||||
|
||||
header, err := client.Eth.HeaderByNumber(ctx, big.NewInt(int64(rpc.FinalizedBlockNumber)))
|
||||
if err != nil {
|
||||
exit(fmt.Errorf("could not fetch finalized header (error: %v)", err))
|
||||
|
|
|
|||
|
|
@ -49,6 +49,7 @@ func init() {
|
|||
filterGenerateCommand,
|
||||
traceGenerateCommand,
|
||||
filterPerfCommand,
|
||||
filterFuzzCommand,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -188,24 +188,16 @@ func ReadStateAccountIndex(db ethdb.AncientReaderOp, id uint64) []byte {
|
|||
// 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
|
||||
// from one (zero for initial state).
|
||||
func ReadStateStorageIndex(db ethdb.AncientReaderOp, id uint64) []byte {
|
||||
blob, err := db.Ancient(stateHistoryStorageIndex, id-1)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
return blob
|
||||
func ReadStateStorageIndex(db ethdb.AncientReaderOp, id uint64, offset, length int) ([]byte, error) {
|
||||
return db.AncientBytes(stateHistoryStorageIndex, id-1, uint64(offset), uint64(length))
|
||||
}
|
||||
|
||||
// ReadStateAccountHistory retrieves the concatenated account data blob for the
|
||||
// specified state history. Offsets and lengths are resolved via the account
|
||||
// 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).
|
||||
func ReadStateAccountHistory(db ethdb.AncientReaderOp, id uint64) []byte {
|
||||
blob, err := db.Ancient(stateHistoryAccountData, id-1)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
return blob
|
||||
func ReadStateAccountHistory(db ethdb.AncientReaderOp, id uint64, offset, length int) ([]byte, error) {
|
||||
return db.AncientBytes(stateHistoryAccountData, id-1, uint64(offset), uint64(length))
|
||||
}
|
||||
|
||||
// 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
|
||||
// one since the id of first state history starts from one (zero for initial
|
||||
// state).
|
||||
func ReadStateStorageHistory(db ethdb.AncientReaderOp, id uint64) []byte {
|
||||
blob, err := db.Ancient(stateHistoryStorageData, id-1)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
return blob
|
||||
func ReadStateStorageHistory(db ethdb.AncientReaderOp, id uint64, offset, length int) ([]byte, error) {
|
||||
return db.AncientBytes(stateHistoryStorageData, id-1, uint64(offset), uint64(length))
|
||||
}
|
||||
|
||||
// ReadStateHistory retrieves the state history from database with provided id.
|
||||
|
|
|
|||
|
|
@ -403,6 +403,10 @@ func (f *chainFreezer) AncientRange(kind string, start, count, maxBytes uint64)
|
|||
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) {
|
||||
return f.ancients.ModifyAncients(fn)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -100,6 +100,12 @@ func (db *nofreezedb) AncientRange(kind string, start, max, maxByteSize uint64)
|
|||
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.
|
||||
func (db *nofreezedb) Ancients() (uint64, error) {
|
||||
return 0, errNotSupported
|
||||
|
|
|
|||
|
|
@ -202,6 +202,15 @@ func (f *Freezer) AncientRange(kind string, start, count, maxBytes uint64) ([][]
|
|||
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.
|
||||
func (f *Freezer) Ancients() (uint64, error) {
|
||||
return f.frozen.Load(), nil
|
||||
|
|
|
|||
|
|
@ -412,3 +412,28 @@ func (f *MemoryFreezer) Reset() error {
|
|||
func (f *MemoryFreezer) AncientDatadir() (string, error) {
|
||||
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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -126,6 +126,15 @@ func (f *resettableFreezer) AncientRange(kind string, start, count, maxBytes uin
|
|||
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.
|
||||
func (f *resettableFreezer) Ancients() (uint64, error) {
|
||||
f.lock.RLock()
|
||||
|
|
|
|||
|
|
@ -1107,6 +1107,71 @@ func (t *freezerTable) retrieveItems(start, count, maxBytes uint64) ([]byte, []i
|
|||
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.
|
||||
func (t *freezerTable) size() (uint64, error) {
|
||||
t.lock.RLock()
|
||||
|
|
|
|||
|
|
@ -1571,3 +1571,65 @@ func TestTailTruncationCrash(t *testing.T) {
|
|||
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)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -62,6 +62,12 @@ func (t *table) AncientRange(kind string, start, count, maxBytes uint64) ([][]by
|
|||
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
|
||||
// database.
|
||||
func (t *table) Ancients() (uint64, error) {
|
||||
|
|
|
|||
|
|
@ -302,6 +302,8 @@ func mustCopyTrie(t Trie) Trie {
|
|||
return t.Copy()
|
||||
case *trie.VerkleTrie:
|
||||
return t.Copy()
|
||||
case *trie.TransitionTrie:
|
||||
return t.Copy()
|
||||
default:
|
||||
panic(fmt.Errorf("unknown trie type %T", t))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ import (
|
|||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
"github.com/ethereum/go-ethereum/trie"
|
||||
"github.com/ethereum/go-ethereum/trie/trienode"
|
||||
"github.com/holiman/uint256"
|
||||
)
|
||||
|
|
@ -494,8 +495,20 @@ func (s *stateObject) deepCopy(db *StateDB) *stateObject {
|
|||
selfDestructed: s.selfDestructed,
|
||||
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)
|
||||
case nil:
|
||||
// do nothing
|
||||
}
|
||||
return obj
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,7 +23,6 @@ import (
|
|||
"math"
|
||||
"math/big"
|
||||
"slices"
|
||||
"sort"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
|
@ -238,11 +237,10 @@ type LegacyPool struct {
|
|||
pendingNonces *noncer // Pending state tracking virtual nonces
|
||||
reserver txpool.Reserver // Address reserver to ensure exclusivity across subpools
|
||||
|
||||
pending map[common.Address]*list // All currently processable transactions
|
||||
queue map[common.Address]*list // Queued but non-processable transactions
|
||||
beats map[common.Address]time.Time // Last heartbeat from each known account
|
||||
all *lookup // All transactions to allow lookups
|
||||
priced *pricedList // All transactions sorted by price
|
||||
pending map[common.Address]*list // All currently processable transactions
|
||||
queue *queue
|
||||
all *lookup // All transactions to allow lookups
|
||||
priced *pricedList // All transactions sorted by price
|
||||
|
||||
reqResetCh chan *txpoolResetRequest
|
||||
reqPromoteCh chan *accountSet
|
||||
|
|
@ -266,14 +264,14 @@ func New(config Config, chain BlockChain) *LegacyPool {
|
|||
config = (&config).sanitize()
|
||||
|
||||
// Create the transaction pool with its initial settings
|
||||
signer := types.LatestSigner(chain.Config())
|
||||
pool := &LegacyPool{
|
||||
config: config,
|
||||
chain: chain,
|
||||
chainconfig: chain.Config(),
|
||||
signer: types.LatestSigner(chain.Config()),
|
||||
signer: signer,
|
||||
pending: make(map[common.Address]*list),
|
||||
queue: make(map[common.Address]*list),
|
||||
beats: make(map[common.Address]time.Time),
|
||||
queue: newQueue(config, signer),
|
||||
all: newLookup(),
|
||||
reqResetCh: make(chan *txpoolResetRequest),
|
||||
reqPromoteCh: make(chan *accountSet),
|
||||
|
|
@ -369,15 +367,8 @@ func (pool *LegacyPool) loop() {
|
|||
// Handle inactive account transaction eviction
|
||||
case <-evict.C:
|
||||
pool.mu.Lock()
|
||||
for addr := range pool.queue {
|
||||
// Any old enough should be removed
|
||||
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)))
|
||||
}
|
||||
for _, hash := range pool.queue.evictList() {
|
||||
pool.removeTx(hash, true, true)
|
||||
}
|
||||
pool.mu.Unlock()
|
||||
}
|
||||
|
|
@ -459,11 +450,7 @@ func (pool *LegacyPool) stats() (int, int) {
|
|||
for _, list := range pool.pending {
|
||||
pending += list.Len()
|
||||
}
|
||||
queued := 0
|
||||
for _, list := range pool.queue {
|
||||
queued += list.Len()
|
||||
}
|
||||
return pending, queued
|
||||
return pending, pool.queue.stats()
|
||||
}
|
||||
|
||||
// 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 {
|
||||
pending[addr] = list.Flatten()
|
||||
}
|
||||
queued := make(map[common.Address][]*types.Transaction, len(pool.queue))
|
||||
for addr, list := range pool.queue {
|
||||
queued[addr] = list.Flatten()
|
||||
}
|
||||
queued := pool.queue.content()
|
||||
return pending, queued
|
||||
}
|
||||
|
||||
|
|
@ -493,10 +477,7 @@ func (pool *LegacyPool) ContentFrom(addr common.Address) ([]*types.Transaction,
|
|||
if list, ok := pool.pending[addr]; ok {
|
||||
pending = list.Flatten()
|
||||
}
|
||||
var queued []*types.Transaction
|
||||
if list, ok := pool.queue[addr]; ok {
|
||||
queued = list.Flatten()
|
||||
}
|
||||
queued := pool.queue.contentFrom(addr)
|
||||
return pending, queued
|
||||
}
|
||||
|
||||
|
|
@ -644,7 +625,7 @@ func (pool *LegacyPool) validateAuth(tx *types.Transaction) error {
|
|||
if pending := pool.pending[auth]; pending != nil {
|
||||
count += pending.Len()
|
||||
}
|
||||
if queue := pool.queue[auth]; queue != nil {
|
||||
if queue, ok := pool.queue.get(auth); ok {
|
||||
count += queue.Len()
|
||||
}
|
||||
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
|
||||
var (
|
||||
_, hasPending = pool.pending[from]
|
||||
_, hasQueued = pool.queue[from]
|
||||
_, hasQueued = pool.queue.get(from)
|
||||
)
|
||||
if !hasPending && !hasQueued {
|
||||
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())
|
||||
|
||||
// Successful promotion, bump the heartbeat
|
||||
pool.beats[from] = time.Now()
|
||||
pool.queue.bump(from)
|
||||
return old != nil, nil
|
||||
}
|
||||
// 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
|
||||
// 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 {
|
||||
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!
|
||||
func (pool *LegacyPool) enqueueTx(hash common.Hash, tx *types.Transaction, addAll bool) (bool, error) {
|
||||
// Try to insert the transaction into the future queue
|
||||
from, _ := types.Sender(pool.signer, tx) // already validated
|
||||
if pool.queue[from] == nil {
|
||||
pool.queue[from] = newList(false)
|
||||
replaced, err := pool.queue.add(tx)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
inserted, old := pool.queue[from].Add(tx, pool.config.PriceBump)
|
||||
if !inserted {
|
||||
// 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 replaced != nil {
|
||||
pool.removeTx(*replaced, true, true)
|
||||
}
|
||||
// If the transaction isn't in lookup set but it's expected to be there,
|
||||
// show the error log.
|
||||
|
|
@ -860,11 +828,7 @@ func (pool *LegacyPool) enqueueTx(hash common.Hash, tx *types.Transaction, addAl
|
|||
pool.all.Add(tx)
|
||||
pool.priced.Put(tx)
|
||||
}
|
||||
// If we never record the heartbeat, do it right now.
|
||||
if _, exist := pool.beats[from]; !exist {
|
||||
pool.beats[from] = time.Now()
|
||||
}
|
||||
return old != nil, nil
|
||||
return replaced != nil, nil
|
||||
}
|
||||
|
||||
// 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)
|
||||
|
||||
// Successful promotion, bump the heartbeat
|
||||
pool.beats[addr] = time.Now()
|
||||
pool.queue.bump(addr)
|
||||
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 {
|
||||
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.TxStatusUnknown
|
||||
|
|
@ -1096,7 +1060,7 @@ func (pool *LegacyPool) removeTx(hash common.Hash, outofbound bool, unreserve bo
|
|||
defer func() {
|
||||
var (
|
||||
_, hasPending = pool.pending[addr]
|
||||
_, hasQueued = pool.queue[addr]
|
||||
_, hasQueued = pool.queue.get(addr)
|
||||
)
|
||||
if !hasPending && !hasQueued {
|
||||
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
|
||||
if future := pool.queue[addr]; future != nil {
|
||||
if removed, _ := future.Remove(tx); removed {
|
||||
// Reduce the queued counter
|
||||
queuedGauge.Dec(1)
|
||||
}
|
||||
if future.Empty() {
|
||||
delete(pool.queue, addr)
|
||||
delete(pool.beats, addr)
|
||||
}
|
||||
}
|
||||
pool.queue.remove(addr, tx)
|
||||
return 0
|
||||
}
|
||||
|
||||
|
|
@ -1285,10 +1240,7 @@ func (pool *LegacyPool) runReorg(done chan struct{}, reset *txpoolResetRequest,
|
|||
}
|
||||
}
|
||||
// Reset needs promote for all addresses
|
||||
promoteAddrs = make([]common.Address, 0, len(pool.queue))
|
||||
for addr := range pool.queue {
|
||||
promoteAddrs = append(promoteAddrs, addr)
|
||||
}
|
||||
promoteAddrs = pool.queue.addresses()
|
||||
}
|
||||
// Check for pending transactions for every account that sent new ones
|
||||
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
|
||||
// invalidated transactions (low nonce, low balance) are deleted.
|
||||
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
|
||||
for _, addr := range accounts {
|
||||
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)))
|
||||
promotable, dropped, removedAddresses := pool.queue.promoteExecutables(accounts, gasLimit, pool.currentState, pool.pendingNonces)
|
||||
|
||||
// Gather all executable transactions and promote them
|
||||
readies := list.Ready(pool.pendingNonces.get(addr))
|
||||
for _, tx := range readies {
|
||||
hash := tx.Hash()
|
||||
if pool.promoteTx(addr, hash, tx) {
|
||||
promoted = append(promoted, tx)
|
||||
}
|
||||
// promote all promotable transactions
|
||||
promoted := make([]*types.Transaction, 0, len(promotable))
|
||||
for _, tx := range promotable {
|
||||
from, _ := pool.signer.Sender(tx)
|
||||
if pool.promoteTx(from, tx.Hash(), 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
|
||||
var caps = list.Cap(int(pool.config.AccountQueue))
|
||||
for _, tx := range caps {
|
||||
hash := tx.Hash()
|
||||
pool.all.Remove(hash)
|
||||
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)))
|
||||
// remove all removable transactions
|
||||
for _, hash := range dropped {
|
||||
pool.all.Remove(hash)
|
||||
}
|
||||
pool.priced.Removed(len(dropped))
|
||||
|
||||
// Delete the entire queue entry if it became empty.
|
||||
if list.Empty() {
|
||||
delete(pool.queue, addr)
|
||||
delete(pool.beats, addr)
|
||||
if _, ok := pool.pending[addr]; !ok {
|
||||
pool.reserver.Release(addr)
|
||||
}
|
||||
// release all accounts that have no more transactions in the pool
|
||||
for _, addr := range removedAddresses {
|
||||
_, hasPending := pool.pending[addr]
|
||||
if !hasPending {
|
||||
pool.reserver.Release(addr)
|
||||
}
|
||||
}
|
||||
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.
|
||||
func (pool *LegacyPool) truncateQueue() {
|
||||
queued := uint64(0)
|
||||
for _, list := range pool.queue {
|
||||
queued += uint64(list.Len())
|
||||
}
|
||||
if queued <= pool.config.GlobalQueue {
|
||||
return
|
||||
removed, removedAddresses := pool.queue.truncate()
|
||||
|
||||
// Remove all removable transactions from the lookup and global price list
|
||||
for _, hash := range removed {
|
||||
pool.all.Remove(hash)
|
||||
}
|
||||
pool.priced.Removed(len(removed))
|
||||
|
||||
// Sort all accounts with queued transactions by heartbeat
|
||||
addresses := make(addressesByHeartbeat, 0, len(pool.queue))
|
||||
for addr := range pool.queue {
|
||||
addresses = append(addresses, addressByHeartbeat{addr, pool.beats[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)
|
||||
for _, addr := range removedAddresses {
|
||||
_, hasPending := pool.pending[addr]
|
||||
if !hasPending {
|
||||
pool.reserver.Release(addr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1679,25 +1575,13 @@ func (pool *LegacyPool) demoteUnexecutables() {
|
|||
// Delete the entire pending entry if it became empty.
|
||||
if list.Empty() {
|
||||
delete(pool.pending, addr)
|
||||
if _, ok := pool.queue[addr]; !ok {
|
||||
if _, ok := pool.queue.get(addr); !ok {
|
||||
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
|
||||
// capable of deriving addresses from transactions.
|
||||
type accountSet struct {
|
||||
|
|
@ -1938,17 +1822,17 @@ func (pool *LegacyPool) Clear() {
|
|||
// acquire the subpool lock until the transaction addition is completed.
|
||||
|
||||
for addr := range pool.pending {
|
||||
if _, ok := pool.queue[addr]; !ok {
|
||||
if _, ok := pool.queue.get(addr); !ok {
|
||||
pool.reserver.Release(addr)
|
||||
}
|
||||
}
|
||||
for addr := range pool.queue {
|
||||
for _, addr := range pool.queue.addresses() {
|
||||
pool.reserver.Release(addr)
|
||||
}
|
||||
pool.all.Clear()
|
||||
pool.priced.Reheap()
|
||||
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)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -466,8 +466,8 @@ func TestQueue(t *testing.T) {
|
|||
if _, ok := pool.pending[from].txs.items[tx.Nonce()]; ok {
|
||||
t.Error("expected transaction to be in tx pool")
|
||||
}
|
||||
if len(pool.queue) > 0 {
|
||||
t.Error("expected transaction queue to be empty. is", len(pool.queue))
|
||||
if len(pool.queue.queued) > 0 {
|
||||
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 {
|
||||
t.Error("expected pending length to be 1, got", len(pool.pending))
|
||||
}
|
||||
if pool.queue[from].Len() != 2 {
|
||||
t.Error("expected len(queue) == 2, got", pool.queue[from].Len())
|
||||
if list, _ := pool.queue.get(from); list.Len() != 2 {
|
||||
t.Error("expected len(queue) == 2, got", list.Len())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -639,8 +639,8 @@ func TestMissingNonce(t *testing.T) {
|
|||
if len(pool.pending) != 0 {
|
||||
t.Error("expected 0 pending transactions, got", len(pool.pending))
|
||||
}
|
||||
if pool.queue[addr].Len() != 1 {
|
||||
t.Error("expected 1 queued transaction, got", pool.queue[addr].Len())
|
||||
if list, _ := pool.queue.get(addr); list.Len() != 1 {
|
||||
t.Error("expected 1 queued transaction, got", list.Len())
|
||||
}
|
||||
if pool.all.Count() != 1 {
|
||||
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 {
|
||||
t.Errorf("pending transaction mismatch: have %d, want %d", pool.pending[account].Len(), 3)
|
||||
}
|
||||
if pool.queue[account].Len() != 3 {
|
||||
t.Errorf("queued transaction mismatch: have %d, want %d", pool.queue[account].Len(), 3)
|
||||
if list, _ := pool.queue.get(account); list.Len() != 3 {
|
||||
t.Errorf("queued transaction mismatch: have %d, want %d", list.Len(), 3)
|
||||
}
|
||||
if 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 {
|
||||
t.Errorf("pending transaction mismatch: have %d, want %d", pool.pending[account].Len(), 3)
|
||||
}
|
||||
if pool.queue[account].Len() != 3 {
|
||||
t.Errorf("queued transaction mismatch: have %d, want %d", pool.queue[account].Len(), 3)
|
||||
if list, _ := pool.queue.get(account); list.Len() != 3 {
|
||||
t.Errorf("queued transaction mismatch: have %d, want %d", list.Len(), 3)
|
||||
}
|
||||
if 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 {
|
||||
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)
|
||||
}
|
||||
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)
|
||||
}
|
||||
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)
|
||||
}
|
||||
if pool.all.Count() != 4 {
|
||||
|
|
@ -763,10 +764,11 @@ func TestDropping(t *testing.T) {
|
|||
if _, ok := pool.pending[account].txs.items[tx1.Nonce()]; ok {
|
||||
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)
|
||||
}
|
||||
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)
|
||||
}
|
||||
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) {
|
||||
t.Errorf("pending transaction mismatch: have %d, want %d", pending, len(txs))
|
||||
}
|
||||
if len(pool.queue) != 0 {
|
||||
t.Errorf("queued accounts mismatch: have %d, want %d", len(pool.queue), 0)
|
||||
if len(pool.queue.addresses()) != 0 {
|
||||
t.Errorf("queued accounts mismatch: have %d, want %d", len(pool.queue.addresses()), 0)
|
||||
}
|
||||
if 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) {
|
||||
t.Errorf("pending transaction mismatch: have %d, want %d", pending, len(txs))
|
||||
}
|
||||
if len(pool.queue) != 0 {
|
||||
t.Errorf("queued accounts mismatch: have %d, want %d", len(pool.queue), 0)
|
||||
if len(pool.queue.addresses()) != 0 {
|
||||
t.Errorf("queued accounts mismatch: have %d, want %d", len(pool.queue.addresses()), 0)
|
||||
}
|
||||
if 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 {
|
||||
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])
|
||||
}
|
||||
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 {
|
||||
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)
|
||||
}
|
||||
} else {
|
||||
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)
|
||||
}
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
|
@ -872,13 +875,14 @@ func TestPostponing(t *testing.T) {
|
|||
if pool.pending[accs[1]] != nil {
|
||||
t.Errorf("invalidated account still has pending transactions")
|
||||
}
|
||||
list, _ = pool.queue.get(accs[1])
|
||||
for i, tx := range txs[100:] {
|
||||
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)
|
||||
}
|
||||
} 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)
|
||||
}
|
||||
}
|
||||
|
|
@ -963,13 +967,14 @@ func TestQueueAccountLimiting(t *testing.T) {
|
|||
if 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 pool.queue[account].Len() != int(i) {
|
||||
t.Errorf("tx %d: queue size mismatch: have %d, want %d", i, pool.queue[account].Len(), i)
|
||||
if list.Len() != int(i) {
|
||||
t.Errorf("tx %d: queue size mismatch: have %d, want %d", i, list.Len(), i)
|
||||
}
|
||||
} else {
|
||||
if pool.queue[account].Len() != int(testTxPoolConfig.AccountQueue) {
|
||||
t.Errorf("tx %d: queue limit mismatch: have %d, want %d", i, pool.queue[account].Len(), testTxPoolConfig.AccountQueue)
|
||||
if list.Len() != int(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)
|
||||
|
||||
queued := 0
|
||||
for addr, list := range pool.queue {
|
||||
for addr, list := range pool.queue.queued {
|
||||
if list.Len() > int(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 {
|
||||
t.Errorf("tx %d: pending pool size mismatch: have %d, want %d", i, pool.pending[account].Len(), i+1)
|
||||
}
|
||||
if len(pool.queue) != 0 {
|
||||
t.Errorf("tx %d: queue size mismatch: have %d, want %d", i, pool.queue[account].Len(), 0)
|
||||
if len(pool.queue.addresses()) != 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) {
|
||||
|
|
|
|||
275
core/txpool/legacypool/queue.go
Normal file
275
core/txpool/legacypool/queue.go
Normal 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] }
|
||||
|
|
@ -21,6 +21,7 @@ import (
|
|||
"fmt"
|
||||
"math/big"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common/bitutil"
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
)
|
||||
|
|
@ -125,9 +126,7 @@ func MergeBloom(receipts Receipts) Bloom {
|
|||
for _, receipt := range receipts {
|
||||
if len(receipt.Logs) != 0 {
|
||||
bl := receipt.Bloom.Bytes()
|
||||
for i := range bin {
|
||||
bin[i] |= bl[i]
|
||||
}
|
||||
bitutil.ORBytes(bin[:], bin[:], bl)
|
||||
}
|
||||
}
|
||||
return bin
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -47,7 +47,6 @@ import (
|
|||
|
||||
// Register adds the engine API to the full node.
|
||||
func Register(stack *node.Node, backend *eth.Ethereum) error {
|
||||
log.Warn("Engine API enabled", "protocol", "eth")
|
||||
stack.RegisterAPIs([]rpc.API{
|
||||
{
|
||||
Namespace: "engine",
|
||||
|
|
|
|||
|
|
@ -49,27 +49,29 @@ var FullNodeGPO = gasprice.Config{
|
|||
|
||||
// Defaults contains default settings for use on the Ethereum main net.
|
||||
var Defaults = Config{
|
||||
HistoryMode: history.KeepAll,
|
||||
SyncMode: SnapSync,
|
||||
NetworkId: 0, // enable auto configuration of networkID == chainID
|
||||
TxLookupLimit: 2350000,
|
||||
TransactionHistory: 2350000,
|
||||
LogHistory: 2350000,
|
||||
StateHistory: params.FullImmutabilityThreshold,
|
||||
DatabaseCache: 512,
|
||||
TrieCleanCache: 154,
|
||||
TrieDirtyCache: 256,
|
||||
TrieTimeout: 60 * time.Minute,
|
||||
SnapshotCache: 102,
|
||||
FilterLogCacheSize: 32,
|
||||
LogQueryLimit: 1000,
|
||||
Miner: miner.DefaultConfig,
|
||||
TxPool: legacypool.DefaultConfig,
|
||||
BlobPool: blobpool.DefaultConfig,
|
||||
RPCGasCap: 50000000,
|
||||
RPCEVMTimeout: 5 * time.Second,
|
||||
GPO: FullNodeGPO,
|
||||
RPCTxFeeCap: 1, // 1 ether
|
||||
HistoryMode: history.KeepAll,
|
||||
SyncMode: SnapSync,
|
||||
NetworkId: 0, // enable auto configuration of networkID == chainID
|
||||
TxLookupLimit: 2350000,
|
||||
TransactionHistory: 2350000,
|
||||
LogHistory: 2350000,
|
||||
StateHistory: params.FullImmutabilityThreshold,
|
||||
DatabaseCache: 512,
|
||||
TrieCleanCache: 154,
|
||||
TrieDirtyCache: 256,
|
||||
TrieTimeout: 60 * time.Minute,
|
||||
SnapshotCache: 102,
|
||||
FilterLogCacheSize: 32,
|
||||
LogQueryLimit: 1000,
|
||||
Miner: miner.DefaultConfig,
|
||||
TxPool: legacypool.DefaultConfig,
|
||||
BlobPool: blobpool.DefaultConfig,
|
||||
RPCGasCap: 50000000,
|
||||
RPCEVMTimeout: 5 * time.Second,
|
||||
GPO: FullNodeGPO,
|
||||
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
|
||||
|
|
@ -183,6 +185,10 @@ type Config struct {
|
|||
|
||||
// OverrideVerkle (TODO: remove after the fork)
|
||||
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.
|
||||
|
|
|
|||
|
|
@ -58,10 +58,12 @@ func (c Config) MarshalTOML() (interface{}, error) {
|
|||
RPCGasCap uint64
|
||||
RPCEVMTimeout time.Duration
|
||||
RPCTxFeeCap float64
|
||||
OverrideOsaka *uint64 `toml:",omitempty"`
|
||||
OverrideBPO1 *uint64 `toml:",omitempty"`
|
||||
OverrideBPO2 *uint64 `toml:",omitempty"`
|
||||
OverrideVerkle *uint64 `toml:",omitempty"`
|
||||
OverrideOsaka *uint64 `toml:",omitempty"`
|
||||
OverrideBPO1 *uint64 `toml:",omitempty"`
|
||||
OverrideBPO2 *uint64 `toml:",omitempty"`
|
||||
OverrideVerkle *uint64 `toml:",omitempty"`
|
||||
TxSyncDefaultTimeout time.Duration `toml:",omitempty"`
|
||||
TxSyncMaxTimeout time.Duration `toml:",omitempty"`
|
||||
}
|
||||
var enc Config
|
||||
enc.Genesis = c.Genesis
|
||||
|
|
@ -109,6 +111,8 @@ func (c Config) MarshalTOML() (interface{}, error) {
|
|||
enc.OverrideBPO1 = c.OverrideBPO1
|
||||
enc.OverrideBPO2 = c.OverrideBPO2
|
||||
enc.OverrideVerkle = c.OverrideVerkle
|
||||
enc.TxSyncDefaultTimeout = c.TxSyncDefaultTimeout
|
||||
enc.TxSyncMaxTimeout = c.TxSyncMaxTimeout
|
||||
return &enc, nil
|
||||
}
|
||||
|
||||
|
|
@ -156,10 +160,12 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error {
|
|||
RPCGasCap *uint64
|
||||
RPCEVMTimeout *time.Duration
|
||||
RPCTxFeeCap *float64
|
||||
OverrideOsaka *uint64 `toml:",omitempty"`
|
||||
OverrideBPO1 *uint64 `toml:",omitempty"`
|
||||
OverrideBPO2 *uint64 `toml:",omitempty"`
|
||||
OverrideVerkle *uint64 `toml:",omitempty"`
|
||||
OverrideOsaka *uint64 `toml:",omitempty"`
|
||||
OverrideBPO1 *uint64 `toml:",omitempty"`
|
||||
OverrideBPO2 *uint64 `toml:",omitempty"`
|
||||
OverrideVerkle *uint64 `toml:",omitempty"`
|
||||
TxSyncDefaultTimeout *time.Duration `toml:",omitempty"`
|
||||
TxSyncMaxTimeout *time.Duration `toml:",omitempty"`
|
||||
}
|
||||
var dec Config
|
||||
if err := unmarshal(&dec); err != nil {
|
||||
|
|
@ -300,5 +306,11 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error {
|
|||
if dec.OverrideVerkle != nil {
|
||||
c.OverrideVerkle = dec.OverrideVerkle
|
||||
}
|
||||
if dec.TxSyncDefaultTimeout != nil {
|
||||
c.TxSyncDefaultTimeout = *dec.TxSyncDefaultTimeout
|
||||
}
|
||||
if dec.TxSyncMaxTimeout != nil {
|
||||
c.TxSyncMaxTimeout = *dec.TxSyncMaxTimeout
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -144,6 +144,7 @@ func (api *FilterAPI) NewPendingTransactionFilter(fullTx *bool) rpc.ID {
|
|||
api.filtersMu.Unlock()
|
||||
|
||||
go func() {
|
||||
defer pendingTxSub.Unsubscribe()
|
||||
for {
|
||||
select {
|
||||
case pTx := <-pendingTxs:
|
||||
|
|
@ -156,7 +157,6 @@ func (api *FilterAPI) NewPendingTransactionFilter(fullTx *bool) rpc.ID {
|
|||
api.filtersMu.Lock()
|
||||
delete(api.filters, pendingTxSub.ID)
|
||||
api.filtersMu.Unlock()
|
||||
pendingTxSub.Unsubscribe()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
|
@ -219,6 +219,7 @@ func (api *FilterAPI) NewBlockFilter() rpc.ID {
|
|||
api.filtersMu.Unlock()
|
||||
|
||||
go func() {
|
||||
defer headerSub.Unsubscribe()
|
||||
for {
|
||||
select {
|
||||
case h := <-headers:
|
||||
|
|
@ -405,6 +406,7 @@ func (api *FilterAPI) NewFilter(crit FilterCriteria) (rpc.ID, error) {
|
|||
api.filtersMu.Unlock()
|
||||
|
||||
go func() {
|
||||
defer logsSub.Unsubscribe()
|
||||
for {
|
||||
select {
|
||||
case l := <-logs:
|
||||
|
|
|
|||
|
|
@ -181,8 +181,7 @@ func newHandler(config *handlerConfig) (*handler, error) {
|
|||
} else {
|
||||
head := h.chain.CurrentBlock()
|
||||
if head.Number.Uint64() > 0 && h.chain.HasState(head.Root) {
|
||||
// Print warning log if database is not empty to run snap sync.
|
||||
log.Warn("Switch sync mode from snap sync to full sync", "reason", "snap sync complete")
|
||||
log.Info("Switch sync mode from snap sync to full sync", "reason", "snap sync complete")
|
||||
} else {
|
||||
// If snap sync was requested and our database is empty, grant it
|
||||
h.snapSync.Store(true)
|
||||
|
|
|
|||
|
|
@ -494,12 +494,19 @@ func handleTransactions(backend Backend, msg Decoder, peer *Peer) error {
|
|||
if err := msg.Decode(&txs); err != nil {
|
||||
return err
|
||||
}
|
||||
// Duplicate transactions are not allowed
|
||||
seen := make(map[common.Hash]struct{})
|
||||
for i, tx := range txs {
|
||||
// Validate and mark the remote transaction
|
||||
if tx == nil {
|
||||
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)
|
||||
}
|
||||
|
|
@ -514,12 +521,19 @@ func handlePooledTransactions(backend Backend, msg Decoder, peer *Peer) error {
|
|||
if err := msg.Decode(&txs); err != nil {
|
||||
return err
|
||||
}
|
||||
// Duplicate transactions are not allowed
|
||||
seen := make(map[common.Hash]struct{})
|
||||
for i, tx := range txs.PooledTransactionsResponse {
|
||||
// Validate and mark the remote transaction
|
||||
if tx == nil {
|
||||
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)
|
||||
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum"
|
||||
"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))
|
||||
}
|
||||
|
||||
// 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.
|
||||
//
|
||||
// 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),
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -754,3 +754,250 @@ func ExampleRevertErrorData() {
|
|||
// revert: 08c379a00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000a75736572206572726f72
|
||||
// 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: ðereum.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))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
80
ethclient/gen_simulate_block_result.go
Normal file
80
ethclient/gen_simulate_block_result.go
Normal 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
|
||||
}
|
||||
61
ethclient/gen_simulate_call_result.go
Normal file
61
ethclient/gen_simulate_call_result.go
Normal 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
|
||||
}
|
||||
|
|
@ -19,7 +19,6 @@ package gethclient
|
|||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"runtime"
|
||||
|
|
@ -280,97 +279,8 @@ func toCallArg(msg ethereum.CallMsg) interface{} {
|
|||
return arg
|
||||
}
|
||||
|
||||
// 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
|
||||
// OverrideAccount is an alias for ethereum.OverrideAccount.
|
||||
type OverrideAccount = ethereum.OverrideAccount
|
||||
|
||||
// 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)
|
||||
}
|
||||
// BlockOverrides is an alias for ethereum.BlockOverrides.
|
||||
type BlockOverrides = ethereum.BlockOverrides
|
||||
|
|
|
|||
|
|
@ -121,6 +121,10 @@ type AncientReaderOp interface {
|
|||
// - if maxBytes is not specified, 'count' items will be returned if they are present
|
||||
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() (uint64, error)
|
||||
|
||||
|
|
|
|||
|
|
@ -140,6 +140,10 @@ func (db *Database) Close() error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (db *Database) AncientBytes(kind string, id, offset, length uint64) ([]byte, error) {
|
||||
panic("not supported")
|
||||
}
|
||||
|
||||
func New(client *rpc.Client) ethdb.Database {
|
||||
if client == nil {
|
||||
return nil
|
||||
|
|
|
|||
|
|
@ -19,10 +19,12 @@ package ethereum
|
|||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"math/big"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
)
|
||||
|
||||
|
|
@ -293,3 +295,98 @@ type BlockNumberReader interface {
|
|||
type ChainIDReader interface {
|
||||
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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -55,6 +55,7 @@ import (
|
|||
const estimateGasErrorRatio = 0.015
|
||||
|
||||
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.
|
||||
type EthereumAPI struct {
|
||||
|
|
@ -1619,16 +1620,9 @@ func (api *TransactionAPI) SendTransaction(ctx context.Context, args Transaction
|
|||
// processing (signing + broadcast).
|
||||
func (api *TransactionAPI) FillTransaction(ctx context.Context, args TransactionArgs) (*SignTransactionResult, error) {
|
||||
// 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{
|
||||
blobSidecarAllowed: true,
|
||||
blobSidecarVersion: sidecarVersion,
|
||||
blobSidecarVersion: api.currentBlobSidecarVersion(),
|
||||
}
|
||||
if err := args.setDefaults(ctx, api.b, config); err != nil {
|
||||
return nil, err
|
||||
|
|
@ -1642,6 +1636,14 @@ func (api *TransactionAPI) FillTransaction(ctx context.Context, args Transaction
|
|||
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.
|
||||
// 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) {
|
||||
|
|
@ -1649,9 +1651,119 @@ func (api *TransactionAPI) SendRawTransaction(ctx context.Context, input hexutil
|
|||
if err := tx.UnmarshalBinary(input); err != nil {
|
||||
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)
|
||||
}
|
||||
|
||||
// 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:
|
||||
// keccak256("\x19Ethereum Signed Message:\n" + len(message) + message).
|
||||
//
|
||||
|
|
|
|||
|
|
@ -440,6 +440,19 @@ type testBackend struct {
|
|||
|
||||
pending *types.Block
|
||||
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 {
|
||||
|
|
@ -466,6 +479,7 @@ func newTestBackend(t *testing.T, n int, gspec *core.Genesis, engine consensus.E
|
|||
acc: acc,
|
||||
pending: blocks[n],
|
||||
pendingReceipts: receipts[n],
|
||||
chainFeed: new(event.Feed),
|
||||
}
|
||||
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)
|
||||
}
|
||||
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 {
|
||||
panic("implement me")
|
||||
}
|
||||
func (b testBackend) SendTx(ctx context.Context, signedTx *types.Transaction) error {
|
||||
panic("implement me")
|
||||
func (b *testBackend) SendTx(ctx context.Context, tx *types.Transaction) error {
|
||||
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)
|
||||
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)
|
||||
}
|
||||
func (b testBackend) TxIndexDone() bool {
|
||||
|
|
@ -3889,3 +3948,109 @@ func (b configTimeBackend) HeaderByNumber(_ context.Context, n rpc.BlockNumber)
|
|||
func (b configTimeBackend) CurrentHeader() *types.Header {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -53,6 +53,8 @@ type Backend interface {
|
|||
RPCEVMTimeout() time.Duration // global timeout for eth_call over rpc: DoS protection
|
||||
RPCTxFeeCap() float64 // global tx fee cap for all transaction related APIs
|
||||
UnprotectedAllowed() bool // allows only for EIP155 transactions.
|
||||
RPCTxSyncDefaultTimeout() time.Duration
|
||||
RPCTxSyncMaxTimeout() time.Duration
|
||||
|
||||
// Blockchain API
|
||||
SetHead(number uint64)
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ import (
|
|||
"fmt"
|
||||
|
||||
"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/core"
|
||||
"github.com/ethereum/go-ethereum/core/vm"
|
||||
|
|
@ -33,6 +34,11 @@ type revertError struct {
|
|||
reason string // revert reason hex encoded
|
||||
}
|
||||
|
||||
type txSyncTimeoutError struct {
|
||||
msg string
|
||||
hash common.Hash
|
||||
}
|
||||
|
||||
// ErrorCode returns the JSON error code for a revert.
|
||||
// See: https://ethereum.org/en/developers/docs/apis/json-rpc/#error-codes
|
||||
func (e *revertError) ErrorCode() int {
|
||||
|
|
@ -108,6 +114,7 @@ const (
|
|||
errCodeInvalidParams = -32602
|
||||
errCodeReverted = -32000
|
||||
errCodeVMError = -32015
|
||||
errCodeTxSyncTimeout = 4
|
||||
)
|
||||
|
||||
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) 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() }
|
||||
|
|
|
|||
|
|
@ -500,7 +500,7 @@ func (w *gzipResponseWriter) init() {
|
|||
hdr := w.resp.Header()
|
||||
length := hdr.Get("content-length")
|
||||
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.contentLength = n
|
||||
}
|
||||
|
|
|
|||
|
|
@ -153,6 +153,7 @@ type lookupIterator struct {
|
|||
cancel func()
|
||||
lookup *lookup
|
||||
tabRefreshing <-chan struct{}
|
||||
lastLookup time.Time
|
||||
}
|
||||
|
||||
type lookupFunc func(ctx context.Context) *lookup
|
||||
|
|
@ -185,6 +186,9 @@ func (it *lookupIterator) Next() bool {
|
|||
return false
|
||||
}
|
||||
if it.lookup == nil {
|
||||
// Ensure enough time has passed between lookup creations.
|
||||
it.slowdown()
|
||||
|
||||
it.lookup = it.nextLookup(it.ctx)
|
||||
if it.lookup.empty() {
|
||||
// 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)
|
||||
}
|
||||
|
||||
// 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.
|
||||
func (it *lookupIterator) Close() {
|
||||
it.cancel()
|
||||
|
|
|
|||
|
|
@ -54,9 +54,8 @@ const (
|
|||
bucketIPLimit, bucketSubnet = 2, 24 // at most 2 addresses from the same /24
|
||||
tableIPLimit, tableSubnet = 10, 24
|
||||
|
||||
seedMinTableTime = 5 * time.Minute
|
||||
seedCount = 30
|
||||
seedMaxAge = 5 * 24 * time.Hour
|
||||
seedCount = 30
|
||||
seedMaxAge = 5 * 24 * time.Hour
|
||||
)
|
||||
|
||||
// Table is the 'node table', a Kademlia-like index of neighbor nodes. The table keeps
|
||||
|
|
|
|||
|
|
@ -575,6 +575,13 @@ func startLocalhostV4(t *testing.T, cfg Config) *UDPv4 {
|
|||
if err != nil {
|
||||
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
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ package enode
|
|||
import (
|
||||
"crypto/ecdsa"
|
||||
"encoding/base64"
|
||||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"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
|
||||
// and 0 if they are equal.
|
||||
func DistCmp(target, a, b ID) int {
|
||||
for i := range target {
|
||||
da := a[i] ^ target[i]
|
||||
db := b[i] ^ target[i]
|
||||
for i := 0; i < len(target); i += 8 {
|
||||
tn := binary.BigEndian.Uint64(target[i : i+8])
|
||||
da := tn ^ binary.BigEndian.Uint64(a[i:i+8])
|
||||
db := tn ^ binary.BigEndian.Uint64(b[i:i+8])
|
||||
if da > db {
|
||||
return 1
|
||||
} 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).
|
||||
func LogDist(a, b ID) int {
|
||||
lz := 0
|
||||
for i := range a {
|
||||
x := a[i] ^ b[i]
|
||||
for i := 0; i < len(a); i += 8 {
|
||||
ai := binary.BigEndian.Uint64(a[i : i+8])
|
||||
bi := binary.BigEndian.Uint64(b[i : i+8])
|
||||
x := ai ^ bi
|
||||
if x == 0 {
|
||||
lz += 8
|
||||
lz += 64
|
||||
} else {
|
||||
lz += bits.LeadingZeros8(x)
|
||||
lz += bits.LeadingZeros64(x)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
logdistBig := func(a, b ID) int {
|
||||
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,
|
||||
// this test checks it explicitly.
|
||||
func TestID_logdistEqual(t *testing.T) {
|
||||
|
|
|
|||
|
|
@ -273,7 +273,8 @@ func TestServerWebsocketReadLimit(t *testing.T) {
|
|||
}
|
||||
} else if !errors.Is(err, websocket.ErrReadLimit) &&
|
||||
!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.
|
||||
t.Fatalf("unexpected error for read limit violation: %v", err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -211,7 +211,8 @@ func (t *TransitionTrie) UpdateStem(key []byte, values [][]byte) error {
|
|||
func (t *TransitionTrie) Copy() *TransitionTrie {
|
||||
return &TransitionTrie{
|
||||
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,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -144,25 +144,17 @@ func (r *historyReader) readAccountMetadata(address common.Address, historyID ui
|
|||
// readStorageMetadata resolves the storage slot metadata within the specified
|
||||
// state history.
|
||||
func (r *historyReader) readStorageMetadata(storageKey common.Hash, storageHash common.Hash, historyID uint64, slotOffset, slotNumber int) ([]byte, error) {
|
||||
// TODO(rj493456442) optimize it with partial read
|
||||
blob := rawdb.ReadStateStorageIndex(r.freezer, historyID)
|
||||
if len(blob) == 0 {
|
||||
return nil, fmt.Errorf("storage index is truncated, historyID: %d", historyID)
|
||||
data, err := rawdb.ReadStateStorageIndex(r.freezer, historyID, slotIndexSize*slotOffset, slotIndexSize*slotNumber)
|
||||
if err != nil {
|
||||
msg := fmt.Sprintf("id: %d, slot-offset: %d, slot-length: %d", historyID, slotOffset, slotNumber)
|
||||
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
|
||||
var (
|
||||
m meta
|
||||
target common.Hash
|
||||
)
|
||||
blob = rawdb.ReadStateHistoryMeta(r.freezer, historyID)
|
||||
blob := rawdb.ReadStateHistoryMeta(r.freezer, historyID)
|
||||
if err := m.decode(blob); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -172,17 +164,17 @@ func (r *historyReader) readStorageMetadata(storageKey common.Hash, storageHash
|
|||
target = storageKey
|
||||
}
|
||||
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
|
||||
})
|
||||
if pos == slotNumber {
|
||||
return nil, fmt.Errorf("storage metadata is not found, slot key: %#x, historyID: %d", storageKey, historyID)
|
||||
}
|
||||
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 subSlice[offset : slotIndexSize*(pos+1)], nil
|
||||
return data[offset : slotIndexSize*(pos+1)], nil
|
||||
}
|
||||
|
||||
// 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
|
||||
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 := rawdb.ReadStateAccountHistory(r.freezer, historyID)
|
||||
if len(data) < length+offset {
|
||||
data, err := rawdb.ReadStateAccountHistory(r.freezer, historyID, offset, length)
|
||||
if err != nil {
|
||||
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.
|
||||
|
|
@ -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
|
||||
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 := rawdb.ReadStateStorageHistory(r.freezer, historyID)
|
||||
if len(data) < offset+length {
|
||||
data, err := rawdb.ReadStateStorageHistory(r.freezer, historyID, offset, length)
|
||||
if err != nil {
|
||||
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.
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
func sharedLen(a, b []byte) int {
|
||||
n := min(len(a), len(b))
|
||||
for i := 0; i < n; i++ {
|
||||
for i := range n {
|
||||
if a[i] != b[i] {
|
||||
return i
|
||||
}
|
||||
|
|
@ -295,7 +295,7 @@ func decodeHeader(data []byte) (*trienodeMetadata, []common.Hash, []uint32, []ui
|
|||
keyOffsets = make([]uint32, 0, count)
|
||||
valOffsets = make([]uint32, 0, count)
|
||||
)
|
||||
for i := 0; i < count; i++ {
|
||||
for i := range count {
|
||||
n := trienodeMetadataSize + trienodeTrieHeaderSize*i
|
||||
owner := common.BytesToHash(data[n : n+common.HashLength])
|
||||
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 {
|
||||
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
|
||||
keyOffset := binary.BigEndian.Uint32(keySection[o : o+4])
|
||||
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.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
|
||||
keyStart := keyOffsets[i]
|
||||
keyLimit := len(keySection)
|
||||
|
|
@ -524,7 +524,7 @@ func newSingleTrienodeHistoryReader(id uint64, reader ethdb.AncientReader, keyRa
|
|||
}
|
||||
keyStart := int(keyRange.start)
|
||||
keyLimit := int(keyRange.limit)
|
||||
if keyLimit == math.MaxUint32 {
|
||||
if keyRange.limit == math.MaxUint32 {
|
||||
keyLimit = len(keyData)
|
||||
}
|
||||
if len(keyData) < keyStart || len(keyData) < keyLimit {
|
||||
|
|
|
|||
|
|
@ -19,6 +19,6 @@ package version
|
|||
const (
|
||||
Major = 1 // Major 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
|
||||
)
|
||||
|
|
|
|||
Loading…
Reference in a new issue