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 {
|
contract Defaulter {
|
||||||
address public caller;
|
address public caller;
|
||||||
|
|
||||||
function() {
|
fallback() external payable {
|
||||||
caller = msg.sender;
|
caller = msg.sender;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
[]string{`6060604052606a8060106000396000f360606040523615601d5760e060020a6000350463fc9c8d3981146040575b605e6000805473ffffffffffffffffffffffffffffffffffffffff191633179055565b606060005473ffffffffffffffffffffffffffffffffffffffff1681565b005b6060908152602090f3`},
|
[]string{`608060405234801561000f575f80fd5b5061013d8061001d5f395ff3fe608060405260043610610021575f3560e01c8063fc9c8d391461006257610022565b5b335f806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055005b34801561006d575f80fd5b5061007661008c565b60405161008391906100ee565b60405180910390f35b5f8054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f6100d8826100af565b9050919050565b6100e8816100ce565b82525050565b5f6020820190506101015f8301846100df565b9291505056fea26469706673582212201e9273ecfb1f534644c77f09a25c21baaba81cf1c444ebc071e12a225a23c72964736f6c63430008140033`},
|
||||||
[]string{`[{"constant":true,"inputs":[],"name":"caller","outputs":[{"name":"","type":"address"}],"type":"function"}]`},
|
[]string{`[{"stateMutability":"payable","type":"fallback"},{"inputs":[],"name":"caller","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"}]`},
|
||||||
`
|
`
|
||||||
"math/big"
|
"math/big"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -277,8 +277,10 @@ func (c *BoundContract) RawCreationTransact(opts *TransactOpts, calldata []byte)
|
||||||
// Transfer initiates a plain transaction to move funds to the contract, calling
|
// Transfer initiates a plain transaction to move funds to the contract, calling
|
||||||
// its default method if one is available.
|
// its default method if one is available.
|
||||||
func (c *BoundContract) Transfer(opts *TransactOpts) (*types.Transaction, error) {
|
func (c *BoundContract) Transfer(opts *TransactOpts) (*types.Transaction, error) {
|
||||||
// todo(rjl493456442) check the payable fallback or receive is defined
|
// Check if payable fallback or receive is defined
|
||||||
// or not, reject invalid transaction at the first place
|
if !c.abi.HasReceive() && !(c.abi.HasFallback() && c.abi.Fallback.IsPayable()) {
|
||||||
|
return nil, fmt.Errorf("contract does not have a payable fallback or receive function")
|
||||||
|
}
|
||||||
return c.transact(opts, &c.address, nil)
|
return c.transact(opts, &c.address, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -188,6 +188,8 @@ var (
|
||||||
utils.AllowUnprotectedTxs,
|
utils.AllowUnprotectedTxs,
|
||||||
utils.BatchRequestLimit,
|
utils.BatchRequestLimit,
|
||||||
utils.BatchResponseMaxSize,
|
utils.BatchResponseMaxSize,
|
||||||
|
utils.RPCTxSyncDefaultTimeoutFlag,
|
||||||
|
utils.RPCTxSyncMaxTimeoutFlag,
|
||||||
}
|
}
|
||||||
|
|
||||||
metricsFlags = []cli.Flag{
|
metricsFlags = []cli.Flag{
|
||||||
|
|
|
||||||
|
|
@ -201,7 +201,7 @@ func expandVerkle(ctx *cli.Context) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
for i, key := range keylist {
|
for i, key := range keylist {
|
||||||
log.Info("Reading key", "index", i, "key", keylist[0])
|
log.Info("Reading key", "index", i, "key", key)
|
||||||
root.Get(key, chaindb.Get)
|
root.Get(key, chaindb.Get)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -615,6 +615,18 @@ var (
|
||||||
Value: ethconfig.Defaults.LogQueryLimit,
|
Value: ethconfig.Defaults.LogQueryLimit,
|
||||||
Category: flags.APICategory,
|
Category: flags.APICategory,
|
||||||
}
|
}
|
||||||
|
RPCTxSyncDefaultTimeoutFlag = &cli.DurationFlag{
|
||||||
|
Name: "rpc.txsync.defaulttimeout",
|
||||||
|
Usage: "Default timeout for eth_sendRawTransactionSync (e.g. 2s, 500ms)",
|
||||||
|
Value: ethconfig.Defaults.TxSyncDefaultTimeout,
|
||||||
|
Category: flags.APICategory,
|
||||||
|
}
|
||||||
|
RPCTxSyncMaxTimeoutFlag = &cli.DurationFlag{
|
||||||
|
Name: "rpc.txsync.maxtimeout",
|
||||||
|
Usage: "Maximum allowed timeout for eth_sendRawTransactionSync (e.g. 5m)",
|
||||||
|
Value: ethconfig.Defaults.TxSyncMaxTimeout,
|
||||||
|
Category: flags.APICategory,
|
||||||
|
}
|
||||||
// Authenticated RPC HTTP settings
|
// Authenticated RPC HTTP settings
|
||||||
AuthListenFlag = &cli.StringFlag{
|
AuthListenFlag = &cli.StringFlag{
|
||||||
Name: "authrpc.addr",
|
Name: "authrpc.addr",
|
||||||
|
|
@ -1717,6 +1729,12 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) {
|
||||||
if ctx.IsSet(RPCGlobalLogQueryLimit.Name) {
|
if ctx.IsSet(RPCGlobalLogQueryLimit.Name) {
|
||||||
cfg.LogQueryLimit = ctx.Int(RPCGlobalLogQueryLimit.Name)
|
cfg.LogQueryLimit = ctx.Int(RPCGlobalLogQueryLimit.Name)
|
||||||
}
|
}
|
||||||
|
if ctx.IsSet(RPCTxSyncDefaultTimeoutFlag.Name) {
|
||||||
|
cfg.TxSyncDefaultTimeout = ctx.Duration(RPCTxSyncDefaultTimeoutFlag.Name)
|
||||||
|
}
|
||||||
|
if ctx.IsSet(RPCTxSyncMaxTimeoutFlag.Name) {
|
||||||
|
cfg.TxSyncMaxTimeout = ctx.Duration(RPCTxSyncMaxTimeoutFlag.Name)
|
||||||
|
}
|
||||||
if !ctx.Bool(SnapshotFlag.Name) || cfg.SnapshotCache == 0 {
|
if !ctx.Bool(SnapshotFlag.Name) || cfg.SnapshotCache == 0 {
|
||||||
// If snap-sync is requested, this flag is also required
|
// If snap-sync is requested, this flag is also required
|
||||||
if cfg.SyncMode == ethconfig.SnapSync {
|
if cfg.SyncMode == ethconfig.SnapSync {
|
||||||
|
|
|
||||||
|
|
@ -184,6 +184,7 @@ func (s *filterTestSuite) loadQueries() error {
|
||||||
type filterQuery struct {
|
type filterQuery struct {
|
||||||
FromBlock int64 `json:"fromBlock"`
|
FromBlock int64 `json:"fromBlock"`
|
||||||
ToBlock int64 `json:"toBlock"`
|
ToBlock int64 `json:"toBlock"`
|
||||||
|
lastBlockHash common.Hash
|
||||||
Address []common.Address `json:"address"`
|
Address []common.Address `json:"address"`
|
||||||
Topics [][]common.Hash `json:"topics"`
|
Topics [][]common.Hash `json:"topics"`
|
||||||
ResultHash *common.Hash `json:"resultHash,omitempty"`
|
ResultHash *common.Hash `json:"resultHash,omitempty"`
|
||||||
|
|
|
||||||
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"
|
"github.com/urfave/cli/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// Parameter of the random filter query generator.
|
||||||
|
maxFilterRangeForTestGen = 100000000000
|
||||||
|
maxFilterResultSize = 1000
|
||||||
|
filterBuckets = 10
|
||||||
|
maxFilterBucketSize = 100
|
||||||
|
filterSeedChance = 10
|
||||||
|
filterMergeChance = 45
|
||||||
|
filterExtendChance = 50
|
||||||
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
filterGenerateCommand = &cli.Command{
|
filterGenerateCommand = &cli.Command{
|
||||||
Name: "filtergen",
|
Name: "filtergen",
|
||||||
|
|
@ -58,7 +69,7 @@ var (
|
||||||
|
|
||||||
// filterGenCmd is the main function of the filter tests generator.
|
// filterGenCmd is the main function of the filter tests generator.
|
||||||
func filterGenCmd(ctx *cli.Context) error {
|
func filterGenCmd(ctx *cli.Context) error {
|
||||||
f := newFilterTestGen(ctx)
|
f := newFilterTestGen(ctx, maxFilterRangeForTestGen)
|
||||||
lastWrite := time.Now()
|
lastWrite := time.Now()
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
|
|
@ -67,7 +78,7 @@ func filterGenCmd(ctx *cli.Context) error {
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
|
|
||||||
f.updateFinalizedBlock()
|
f.setLimitToFinalizedBlock()
|
||||||
query := f.newQuery()
|
query := f.newQuery()
|
||||||
query.run(f.client, nil)
|
query.run(f.client, nil)
|
||||||
if query.Err != nil {
|
if query.Err != nil {
|
||||||
|
|
@ -75,7 +86,7 @@ func filterGenCmd(ctx *cli.Context) error {
|
||||||
exit("filter query failed")
|
exit("filter query failed")
|
||||||
}
|
}
|
||||||
if len(query.results) > 0 && len(query.results) <= maxFilterResultSize {
|
if len(query.results) > 0 && len(query.results) <= maxFilterResultSize {
|
||||||
for {
|
for rand.Intn(100) < filterExtendChance {
|
||||||
extQuery := f.extendRange(query)
|
extQuery := f.extendRange(query)
|
||||||
if extQuery == nil {
|
if extQuery == nil {
|
||||||
break
|
break
|
||||||
|
|
@ -110,37 +121,30 @@ func filterGenCmd(ctx *cli.Context) error {
|
||||||
type filterTestGen struct {
|
type filterTestGen struct {
|
||||||
client *client
|
client *client
|
||||||
queryFile string
|
queryFile string
|
||||||
|
maxFilterRange int64
|
||||||
|
|
||||||
finalizedBlock int64
|
blockLimit int64
|
||||||
queries [filterBuckets][]*filterQuery
|
queries [filterBuckets][]*filterQuery
|
||||||
}
|
}
|
||||||
|
|
||||||
func newFilterTestGen(ctx *cli.Context) *filterTestGen {
|
func newFilterTestGen(ctx *cli.Context, maxFilterRange int64) *filterTestGen {
|
||||||
return &filterTestGen{
|
return &filterTestGen{
|
||||||
client: makeClient(ctx),
|
client: makeClient(ctx),
|
||||||
queryFile: ctx.String(filterQueryFileFlag.Name),
|
queryFile: ctx.String(filterQueryFileFlag.Name),
|
||||||
|
maxFilterRange: maxFilterRange,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *filterTestGen) updateFinalizedBlock() {
|
func (s *filterTestGen) setLimitToFinalizedBlock() {
|
||||||
s.finalizedBlock = mustGetFinalizedBlock(s.client)
|
s.blockLimit = mustGetFinalizedBlock(s.client)
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
|
||||||
// Parameter of the random filter query generator.
|
|
||||||
maxFilterRange = 10000000
|
|
||||||
maxFilterResultSize = 300
|
|
||||||
filterBuckets = 10
|
|
||||||
maxFilterBucketSize = 100
|
|
||||||
filterSeedChance = 10
|
|
||||||
filterMergeChance = 45
|
|
||||||
)
|
|
||||||
|
|
||||||
// storeQuery adds a filter query to the output file.
|
// storeQuery adds a filter query to the output file.
|
||||||
func (s *filterTestGen) storeQuery(query *filterQuery) {
|
func (s *filterTestGen) storeQuery(query *filterQuery) {
|
||||||
query.ResultHash = new(common.Hash)
|
query.ResultHash = new(common.Hash)
|
||||||
*query.ResultHash = query.calculateHash()
|
*query.ResultHash = query.calculateHash()
|
||||||
logRatio := math.Log(float64(len(query.results))*float64(s.finalizedBlock)/float64(query.ToBlock+1-query.FromBlock)) / math.Log(float64(s.finalizedBlock)*maxFilterResultSize)
|
maxFilterRange := min(s.maxFilterRange, s.blockLimit)
|
||||||
|
logRatio := math.Log(float64(len(query.results))*float64(maxFilterRange)/float64(query.ToBlock+1-query.FromBlock)) / math.Log(float64(maxFilterRange)*maxFilterResultSize)
|
||||||
bucket := int(math.Floor(logRatio * filterBuckets))
|
bucket := int(math.Floor(logRatio * filterBuckets))
|
||||||
if bucket >= filterBuckets {
|
if bucket >= filterBuckets {
|
||||||
bucket = filterBuckets - 1
|
bucket = filterBuckets - 1
|
||||||
|
|
@ -160,13 +164,13 @@ func (s *filterTestGen) storeQuery(query *filterQuery) {
|
||||||
func (s *filterTestGen) extendRange(q *filterQuery) *filterQuery {
|
func (s *filterTestGen) extendRange(q *filterQuery) *filterQuery {
|
||||||
rangeLen := q.ToBlock + 1 - q.FromBlock
|
rangeLen := q.ToBlock + 1 - q.FromBlock
|
||||||
extLen := rand.Int63n(rangeLen) + 1
|
extLen := rand.Int63n(rangeLen) + 1
|
||||||
if rangeLen+extLen > s.finalizedBlock {
|
if rangeLen+extLen > min(s.maxFilterRange, s.blockLimit) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
extBefore := min(rand.Int63n(extLen+1), q.FromBlock)
|
extBefore := min(rand.Int63n(extLen+1), q.FromBlock)
|
||||||
extAfter := extLen - extBefore
|
extAfter := extLen - extBefore
|
||||||
if q.ToBlock+extAfter > s.finalizedBlock {
|
if q.ToBlock+extAfter > s.blockLimit {
|
||||||
d := q.ToBlock + extAfter - s.finalizedBlock
|
d := q.ToBlock + extAfter - s.blockLimit
|
||||||
extAfter -= d
|
extAfter -= d
|
||||||
if extBefore+d <= q.FromBlock {
|
if extBefore+d <= q.FromBlock {
|
||||||
extBefore += d
|
extBefore += d
|
||||||
|
|
@ -203,7 +207,7 @@ func (s *filterTestGen) newQuery() *filterQuery {
|
||||||
|
|
||||||
// newSeedQuery creates a query that gets all logs in a random non-finalized block.
|
// newSeedQuery creates a query that gets all logs in a random non-finalized block.
|
||||||
func (s *filterTestGen) newSeedQuery() *filterQuery {
|
func (s *filterTestGen) newSeedQuery() *filterQuery {
|
||||||
block := rand.Int63n(s.finalizedBlock + 1)
|
block := rand.Int63n(s.blockLimit + 1)
|
||||||
return &filterQuery{
|
return &filterQuery{
|
||||||
FromBlock: block,
|
FromBlock: block,
|
||||||
ToBlock: block,
|
ToBlock: block,
|
||||||
|
|
@ -358,6 +362,7 @@ func (s *filterTestGen) writeQueries() {
|
||||||
func mustGetFinalizedBlock(client *client) int64 {
|
func mustGetFinalizedBlock(client *client) int64 {
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
|
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
header, err := client.Eth.HeaderByNumber(ctx, big.NewInt(int64(rpc.FinalizedBlockNumber)))
|
header, err := client.Eth.HeaderByNumber(ctx, big.NewInt(int64(rpc.FinalizedBlockNumber)))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
exit(fmt.Errorf("could not fetch finalized header (error: %v)", err))
|
exit(fmt.Errorf("could not fetch finalized header (error: %v)", err))
|
||||||
|
|
|
||||||
|
|
@ -49,6 +49,7 @@ func init() {
|
||||||
filterGenerateCommand,
|
filterGenerateCommand,
|
||||||
traceGenerateCommand,
|
traceGenerateCommand,
|
||||||
filterPerfCommand,
|
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
|
// data in the concatenated storage data table. Compute the position of state
|
||||||
// history in freezer by minus one since the id of first state history starts
|
// history in freezer by minus one since the id of first state history starts
|
||||||
// from one (zero for initial state).
|
// from one (zero for initial state).
|
||||||
func ReadStateStorageIndex(db ethdb.AncientReaderOp, id uint64) []byte {
|
func ReadStateStorageIndex(db ethdb.AncientReaderOp, id uint64, offset, length int) ([]byte, error) {
|
||||||
blob, err := db.Ancient(stateHistoryStorageIndex, id-1)
|
return db.AncientBytes(stateHistoryStorageIndex, id-1, uint64(offset), uint64(length))
|
||||||
if err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return blob
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReadStateAccountHistory retrieves the concatenated account data blob for the
|
// ReadStateAccountHistory retrieves the concatenated account data blob for the
|
||||||
// specified state history. Offsets and lengths are resolved via the account
|
// specified state history. Offsets and lengths are resolved via the account
|
||||||
// index. Compute the position of state history in freezer by minus one since
|
// index. Compute the position of state history in freezer by minus one since
|
||||||
// the id of first state history starts from one (zero for initial state).
|
// the id of first state history starts from one (zero for initial state).
|
||||||
func ReadStateAccountHistory(db ethdb.AncientReaderOp, id uint64) []byte {
|
func ReadStateAccountHistory(db ethdb.AncientReaderOp, id uint64, offset, length int) ([]byte, error) {
|
||||||
blob, err := db.Ancient(stateHistoryAccountData, id-1)
|
return db.AncientBytes(stateHistoryAccountData, id-1, uint64(offset), uint64(length))
|
||||||
if err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return blob
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReadStateStorageHistory retrieves the concatenated storage slot data blob for
|
// ReadStateStorageHistory retrieves the concatenated storage slot data blob for
|
||||||
|
|
@ -213,12 +205,8 @@ func ReadStateAccountHistory(db ethdb.AncientReaderOp, id uint64) []byte {
|
||||||
// storage indexes. Compute the position of state history in freezer by minus
|
// storage indexes. Compute the position of state history in freezer by minus
|
||||||
// one since the id of first state history starts from one (zero for initial
|
// one since the id of first state history starts from one (zero for initial
|
||||||
// state).
|
// state).
|
||||||
func ReadStateStorageHistory(db ethdb.AncientReaderOp, id uint64) []byte {
|
func ReadStateStorageHistory(db ethdb.AncientReaderOp, id uint64, offset, length int) ([]byte, error) {
|
||||||
blob, err := db.Ancient(stateHistoryStorageData, id-1)
|
return db.AncientBytes(stateHistoryStorageData, id-1, uint64(offset), uint64(length))
|
||||||
if err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return blob
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReadStateHistory retrieves the state history from database with provided id.
|
// ReadStateHistory retrieves the state history from database with provided id.
|
||||||
|
|
|
||||||
|
|
@ -403,6 +403,10 @@ func (f *chainFreezer) AncientRange(kind string, start, count, maxBytes uint64)
|
||||||
return f.ancients.AncientRange(kind, start, count, maxBytes)
|
return f.ancients.AncientRange(kind, start, count, maxBytes)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (f *chainFreezer) AncientBytes(kind string, id, offset, length uint64) ([]byte, error) {
|
||||||
|
return f.ancients.AncientBytes(kind, id, offset, length)
|
||||||
|
}
|
||||||
|
|
||||||
func (f *chainFreezer) ModifyAncients(fn func(ethdb.AncientWriteOp) error) (int64, error) {
|
func (f *chainFreezer) ModifyAncients(fn func(ethdb.AncientWriteOp) error) (int64, error) {
|
||||||
return f.ancients.ModifyAncients(fn)
|
return f.ancients.ModifyAncients(fn)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -100,6 +100,12 @@ func (db *nofreezedb) AncientRange(kind string, start, max, maxByteSize uint64)
|
||||||
return nil, errNotSupported
|
return nil, errNotSupported
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AncientBytes retrieves the value segment of the element specified by the id
|
||||||
|
// and value offsets.
|
||||||
|
func (db *nofreezedb) AncientBytes(kind string, id, offset, length uint64) ([]byte, error) {
|
||||||
|
return nil, errNotSupported
|
||||||
|
}
|
||||||
|
|
||||||
// Ancients returns an error as we don't have a backing chain freezer.
|
// Ancients returns an error as we don't have a backing chain freezer.
|
||||||
func (db *nofreezedb) Ancients() (uint64, error) {
|
func (db *nofreezedb) Ancients() (uint64, error) {
|
||||||
return 0, errNotSupported
|
return 0, errNotSupported
|
||||||
|
|
|
||||||
|
|
@ -202,6 +202,15 @@ func (f *Freezer) AncientRange(kind string, start, count, maxBytes uint64) ([][]
|
||||||
return nil, errUnknownTable
|
return nil, errUnknownTable
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AncientBytes retrieves the value segment of the element specified by the id
|
||||||
|
// and value offsets.
|
||||||
|
func (f *Freezer) AncientBytes(kind string, id, offset, length uint64) ([]byte, error) {
|
||||||
|
if table := f.tables[kind]; table != nil {
|
||||||
|
return table.RetrieveBytes(id, offset, length)
|
||||||
|
}
|
||||||
|
return nil, errUnknownTable
|
||||||
|
}
|
||||||
|
|
||||||
// Ancients returns the length of the frozen items.
|
// Ancients returns the length of the frozen items.
|
||||||
func (f *Freezer) Ancients() (uint64, error) {
|
func (f *Freezer) Ancients() (uint64, error) {
|
||||||
return f.frozen.Load(), nil
|
return f.frozen.Load(), nil
|
||||||
|
|
|
||||||
|
|
@ -412,3 +412,28 @@ func (f *MemoryFreezer) Reset() error {
|
||||||
func (f *MemoryFreezer) AncientDatadir() (string, error) {
|
func (f *MemoryFreezer) AncientDatadir() (string, error) {
|
||||||
return "", nil
|
return "", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AncientBytes retrieves the value segment of the element specified by the id
|
||||||
|
// and value offsets.
|
||||||
|
func (f *MemoryFreezer) AncientBytes(kind string, id, offset, length uint64) ([]byte, error) {
|
||||||
|
f.lock.RLock()
|
||||||
|
defer f.lock.RUnlock()
|
||||||
|
|
||||||
|
table := f.tables[kind]
|
||||||
|
if table == nil {
|
||||||
|
return nil, errUnknownTable
|
||||||
|
}
|
||||||
|
entries, err := table.retrieve(id, 1, 0)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if len(entries) == 0 {
|
||||||
|
return nil, errOutOfBounds
|
||||||
|
}
|
||||||
|
data := entries[0]
|
||||||
|
|
||||||
|
if offset > uint64(len(data)) || offset+length > uint64(len(data)) {
|
||||||
|
return nil, fmt.Errorf("requested range out of bounds: item size %d, offset %d, length %d", len(data), offset, length)
|
||||||
|
}
|
||||||
|
return data[offset : offset+length], nil
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -126,6 +126,15 @@ func (f *resettableFreezer) AncientRange(kind string, start, count, maxBytes uin
|
||||||
return f.freezer.AncientRange(kind, start, count, maxBytes)
|
return f.freezer.AncientRange(kind, start, count, maxBytes)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AncientBytes retrieves the value segment of the element specified by the id
|
||||||
|
// and value offsets.
|
||||||
|
func (f *resettableFreezer) AncientBytes(kind string, id, offset, length uint64) ([]byte, error) {
|
||||||
|
f.lock.RLock()
|
||||||
|
defer f.lock.RUnlock()
|
||||||
|
|
||||||
|
return f.freezer.AncientBytes(kind, id, offset, length)
|
||||||
|
}
|
||||||
|
|
||||||
// Ancients returns the length of the frozen items.
|
// Ancients returns the length of the frozen items.
|
||||||
func (f *resettableFreezer) Ancients() (uint64, error) {
|
func (f *resettableFreezer) Ancients() (uint64, error) {
|
||||||
f.lock.RLock()
|
f.lock.RLock()
|
||||||
|
|
|
||||||
|
|
@ -1107,6 +1107,71 @@ func (t *freezerTable) retrieveItems(start, count, maxBytes uint64) ([]byte, []i
|
||||||
return output, sizes, nil
|
return output, sizes, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RetrieveBytes retrieves the value segment of the element specified by the id
|
||||||
|
// and value offsets.
|
||||||
|
func (t *freezerTable) RetrieveBytes(item, offset, length uint64) ([]byte, error) {
|
||||||
|
t.lock.RLock()
|
||||||
|
defer t.lock.RUnlock()
|
||||||
|
|
||||||
|
if t.index == nil || t.head == nil || t.metadata.file == nil {
|
||||||
|
return nil, errClosed
|
||||||
|
}
|
||||||
|
items, hidden := t.items.Load(), t.itemHidden.Load()
|
||||||
|
if items <= item || hidden > item {
|
||||||
|
return nil, errOutOfBounds
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retrieves the index entries for the specified ID and its immediate successor
|
||||||
|
indices, err := t.getIndices(item, 1)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
index0, index1 := indices[0], indices[1]
|
||||||
|
|
||||||
|
itemStart, itemLimit, fileId := index0.bounds(index1)
|
||||||
|
itemSize := itemLimit - itemStart
|
||||||
|
|
||||||
|
dataFile, exist := t.files[fileId]
|
||||||
|
if !exist {
|
||||||
|
return nil, fmt.Errorf("missing data file %d", fileId)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Perform the partial read if no-compression was enabled upon
|
||||||
|
if t.config.noSnappy {
|
||||||
|
if offset > uint64(itemSize) || offset+length > uint64(itemSize) {
|
||||||
|
return nil, fmt.Errorf("requested range out of bounds: item size %d, offset %d, length %d", itemSize, offset, length)
|
||||||
|
}
|
||||||
|
itemStart += uint32(offset)
|
||||||
|
|
||||||
|
buf := make([]byte, length)
|
||||||
|
_, err = dataFile.ReadAt(buf, int64(itemStart))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
t.readMeter.Mark(int64(length))
|
||||||
|
return buf, nil
|
||||||
|
} else {
|
||||||
|
// If compressed, read the full item, decompress, then slice.
|
||||||
|
// Unfortunately, in this case, there is no performance gain
|
||||||
|
// by performing the partial read at all.
|
||||||
|
buf := make([]byte, itemSize)
|
||||||
|
_, err = dataFile.ReadAt(buf, int64(itemStart))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
t.readMeter.Mark(int64(itemSize))
|
||||||
|
|
||||||
|
data, err := snappy.Decode(nil, buf)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if offset > uint64(len(data)) || offset+length > uint64(len(data)) {
|
||||||
|
return nil, fmt.Errorf("requested range out of bounds: item size %d, offset %d, length %d", len(data), offset, length)
|
||||||
|
}
|
||||||
|
return data[offset : offset+length], nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// size returns the total data size in the freezer table.
|
// size returns the total data size in the freezer table.
|
||||||
func (t *freezerTable) size() (uint64, error) {
|
func (t *freezerTable) size() (uint64, error) {
|
||||||
t.lock.RLock()
|
t.lock.RLock()
|
||||||
|
|
|
||||||
|
|
@ -1571,3 +1571,65 @@ func TestTailTruncationCrash(t *testing.T) {
|
||||||
t.Fatalf("Unexpected index flush offset, want: %d, got: %d", 26*indexEntrySize, f.metadata.flushOffset)
|
t.Fatalf("Unexpected index flush offset, want: %d, got: %d", 26*indexEntrySize, f.metadata.flushOffset)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestFreezerAncientBytes(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
types := []struct {
|
||||||
|
name string
|
||||||
|
config freezerTableConfig
|
||||||
|
}{
|
||||||
|
{"uncompressed", freezerTableConfig{noSnappy: true}},
|
||||||
|
{"compressed", freezerTableConfig{noSnappy: false}},
|
||||||
|
}
|
||||||
|
for _, typ := range types {
|
||||||
|
t.Run(typ.name, func(t *testing.T) {
|
||||||
|
f, err := newTable(os.TempDir(), fmt.Sprintf("ancientbytes-%s-%d", typ.name, rand.Uint64()), metrics.NewMeter(), metrics.NewMeter(), metrics.NewGauge(), 1000, typ.config, false)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
for i := 0; i < 10; i++ {
|
||||||
|
data := getChunk(100, i)
|
||||||
|
batch := f.newBatch()
|
||||||
|
require.NoError(t, batch.AppendRaw(uint64(i), data))
|
||||||
|
require.NoError(t, batch.commit())
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < 10; i++ {
|
||||||
|
full, err := f.Retrieve(uint64(i))
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Full read
|
||||||
|
got, err := f.RetrieveBytes(uint64(i), 0, uint64(len(full)))
|
||||||
|
require.NoError(t, err)
|
||||||
|
if !bytes.Equal(got, full) {
|
||||||
|
t.Fatalf("full read mismatch for entry %d", i)
|
||||||
|
}
|
||||||
|
// Empty read
|
||||||
|
got, err = f.RetrieveBytes(uint64(i), 0, 0)
|
||||||
|
require.NoError(t, err)
|
||||||
|
if !bytes.Equal(got, full[:0]) {
|
||||||
|
t.Fatalf("empty read mismatch for entry %d", i)
|
||||||
|
}
|
||||||
|
// Middle slice
|
||||||
|
got, err = f.RetrieveBytes(uint64(i), 10, 50)
|
||||||
|
require.NoError(t, err)
|
||||||
|
if !bytes.Equal(got, full[10:60]) {
|
||||||
|
t.Fatalf("middle slice mismatch for entry %d", i)
|
||||||
|
}
|
||||||
|
// Single byte
|
||||||
|
got, err = f.RetrieveBytes(uint64(i), 99, 1)
|
||||||
|
require.NoError(t, err)
|
||||||
|
if !bytes.Equal(got, full[99:100]) {
|
||||||
|
t.Fatalf("single byte mismatch for entry %d", i)
|
||||||
|
}
|
||||||
|
// Out of bounds
|
||||||
|
_, err = f.RetrieveBytes(uint64(i), 100, 1)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("expected error for out-of-bounds read for entry %d", i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -62,6 +62,12 @@ func (t *table) AncientRange(kind string, start, count, maxBytes uint64) ([][]by
|
||||||
return t.db.AncientRange(kind, start, count, maxBytes)
|
return t.db.AncientRange(kind, start, count, maxBytes)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AncientBytes is a noop passthrough that just forwards the request to the underlying
|
||||||
|
// database.
|
||||||
|
func (t *table) AncientBytes(kind string, id, offset, length uint64) ([]byte, error) {
|
||||||
|
return t.db.AncientBytes(kind, id, offset, length)
|
||||||
|
}
|
||||||
|
|
||||||
// Ancients is a noop passthrough that just forwards the request to the underlying
|
// Ancients is a noop passthrough that just forwards the request to the underlying
|
||||||
// database.
|
// database.
|
||||||
func (t *table) Ancients() (uint64, error) {
|
func (t *table) Ancients() (uint64, error) {
|
||||||
|
|
|
||||||
|
|
@ -302,6 +302,8 @@ func mustCopyTrie(t Trie) Trie {
|
||||||
return t.Copy()
|
return t.Copy()
|
||||||
case *trie.VerkleTrie:
|
case *trie.VerkleTrie:
|
||||||
return t.Copy()
|
return t.Copy()
|
||||||
|
case *trie.TransitionTrie:
|
||||||
|
return t.Copy()
|
||||||
default:
|
default:
|
||||||
panic(fmt.Errorf("unknown trie type %T", t))
|
panic(fmt.Errorf("unknown trie type %T", t))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,7 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
"github.com/ethereum/go-ethereum/log"
|
"github.com/ethereum/go-ethereum/log"
|
||||||
"github.com/ethereum/go-ethereum/rlp"
|
"github.com/ethereum/go-ethereum/rlp"
|
||||||
|
"github.com/ethereum/go-ethereum/trie"
|
||||||
"github.com/ethereum/go-ethereum/trie/trienode"
|
"github.com/ethereum/go-ethereum/trie/trienode"
|
||||||
"github.com/holiman/uint256"
|
"github.com/holiman/uint256"
|
||||||
)
|
)
|
||||||
|
|
@ -494,8 +495,20 @@ func (s *stateObject) deepCopy(db *StateDB) *stateObject {
|
||||||
selfDestructed: s.selfDestructed,
|
selfDestructed: s.selfDestructed,
|
||||||
newContract: s.newContract,
|
newContract: s.newContract,
|
||||||
}
|
}
|
||||||
if s.trie != nil {
|
|
||||||
|
switch s.trie.(type) {
|
||||||
|
case *trie.VerkleTrie:
|
||||||
|
// Verkle uses only one tree, and the copy has already been
|
||||||
|
// made in mustCopyTrie.
|
||||||
|
obj.trie = db.trie
|
||||||
|
case *trie.TransitionTrie:
|
||||||
|
// Same thing for the transition tree, since the MPT is
|
||||||
|
// read-only.
|
||||||
|
obj.trie = db.trie
|
||||||
|
case *trie.StateTrie:
|
||||||
obj.trie = mustCopyTrie(s.trie)
|
obj.trie = mustCopyTrie(s.trie)
|
||||||
|
case nil:
|
||||||
|
// do nothing
|
||||||
}
|
}
|
||||||
return obj
|
return obj
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,6 @@ import (
|
||||||
"math"
|
"math"
|
||||||
"math/big"
|
"math/big"
|
||||||
"slices"
|
"slices"
|
||||||
"sort"
|
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
@ -239,8 +238,7 @@ type LegacyPool struct {
|
||||||
reserver txpool.Reserver // Address reserver to ensure exclusivity across subpools
|
reserver txpool.Reserver // Address reserver to ensure exclusivity across subpools
|
||||||
|
|
||||||
pending map[common.Address]*list // All currently processable transactions
|
pending map[common.Address]*list // All currently processable transactions
|
||||||
queue map[common.Address]*list // Queued but non-processable transactions
|
queue *queue
|
||||||
beats map[common.Address]time.Time // Last heartbeat from each known account
|
|
||||||
all *lookup // All transactions to allow lookups
|
all *lookup // All transactions to allow lookups
|
||||||
priced *pricedList // All transactions sorted by price
|
priced *pricedList // All transactions sorted by price
|
||||||
|
|
||||||
|
|
@ -266,14 +264,14 @@ func New(config Config, chain BlockChain) *LegacyPool {
|
||||||
config = (&config).sanitize()
|
config = (&config).sanitize()
|
||||||
|
|
||||||
// Create the transaction pool with its initial settings
|
// Create the transaction pool with its initial settings
|
||||||
|
signer := types.LatestSigner(chain.Config())
|
||||||
pool := &LegacyPool{
|
pool := &LegacyPool{
|
||||||
config: config,
|
config: config,
|
||||||
chain: chain,
|
chain: chain,
|
||||||
chainconfig: chain.Config(),
|
chainconfig: chain.Config(),
|
||||||
signer: types.LatestSigner(chain.Config()),
|
signer: signer,
|
||||||
pending: make(map[common.Address]*list),
|
pending: make(map[common.Address]*list),
|
||||||
queue: make(map[common.Address]*list),
|
queue: newQueue(config, signer),
|
||||||
beats: make(map[common.Address]time.Time),
|
|
||||||
all: newLookup(),
|
all: newLookup(),
|
||||||
reqResetCh: make(chan *txpoolResetRequest),
|
reqResetCh: make(chan *txpoolResetRequest),
|
||||||
reqPromoteCh: make(chan *accountSet),
|
reqPromoteCh: make(chan *accountSet),
|
||||||
|
|
@ -369,15 +367,8 @@ func (pool *LegacyPool) loop() {
|
||||||
// Handle inactive account transaction eviction
|
// Handle inactive account transaction eviction
|
||||||
case <-evict.C:
|
case <-evict.C:
|
||||||
pool.mu.Lock()
|
pool.mu.Lock()
|
||||||
for addr := range pool.queue {
|
for _, hash := range pool.queue.evictList() {
|
||||||
// Any old enough should be removed
|
pool.removeTx(hash, true, true)
|
||||||
if time.Since(pool.beats[addr]) > pool.config.Lifetime {
|
|
||||||
list := pool.queue[addr].Flatten()
|
|
||||||
for _, tx := range list {
|
|
||||||
pool.removeTx(tx.Hash(), true, true)
|
|
||||||
}
|
|
||||||
queuedEvictionMeter.Mark(int64(len(list)))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
pool.mu.Unlock()
|
pool.mu.Unlock()
|
||||||
}
|
}
|
||||||
|
|
@ -459,11 +450,7 @@ func (pool *LegacyPool) stats() (int, int) {
|
||||||
for _, list := range pool.pending {
|
for _, list := range pool.pending {
|
||||||
pending += list.Len()
|
pending += list.Len()
|
||||||
}
|
}
|
||||||
queued := 0
|
return pending, pool.queue.stats()
|
||||||
for _, list := range pool.queue {
|
|
||||||
queued += list.Len()
|
|
||||||
}
|
|
||||||
return pending, queued
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Content retrieves the data content of the transaction pool, returning all the
|
// Content retrieves the data content of the transaction pool, returning all the
|
||||||
|
|
@ -476,10 +463,7 @@ func (pool *LegacyPool) Content() (map[common.Address][]*types.Transaction, map[
|
||||||
for addr, list := range pool.pending {
|
for addr, list := range pool.pending {
|
||||||
pending[addr] = list.Flatten()
|
pending[addr] = list.Flatten()
|
||||||
}
|
}
|
||||||
queued := make(map[common.Address][]*types.Transaction, len(pool.queue))
|
queued := pool.queue.content()
|
||||||
for addr, list := range pool.queue {
|
|
||||||
queued[addr] = list.Flatten()
|
|
||||||
}
|
|
||||||
return pending, queued
|
return pending, queued
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -493,10 +477,7 @@ func (pool *LegacyPool) ContentFrom(addr common.Address) ([]*types.Transaction,
|
||||||
if list, ok := pool.pending[addr]; ok {
|
if list, ok := pool.pending[addr]; ok {
|
||||||
pending = list.Flatten()
|
pending = list.Flatten()
|
||||||
}
|
}
|
||||||
var queued []*types.Transaction
|
queued := pool.queue.contentFrom(addr)
|
||||||
if list, ok := pool.queue[addr]; ok {
|
|
||||||
queued = list.Flatten()
|
|
||||||
}
|
|
||||||
return pending, queued
|
return pending, queued
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -644,7 +625,7 @@ func (pool *LegacyPool) validateAuth(tx *types.Transaction) error {
|
||||||
if pending := pool.pending[auth]; pending != nil {
|
if pending := pool.pending[auth]; pending != nil {
|
||||||
count += pending.Len()
|
count += pending.Len()
|
||||||
}
|
}
|
||||||
if queue := pool.queue[auth]; queue != nil {
|
if queue, ok := pool.queue.get(auth); ok {
|
||||||
count += queue.Len()
|
count += queue.Len()
|
||||||
}
|
}
|
||||||
if count > 1 {
|
if count > 1 {
|
||||||
|
|
@ -691,7 +672,7 @@ func (pool *LegacyPool) add(tx *types.Transaction) (replaced bool, err error) {
|
||||||
// only by this subpool until all transactions are evicted
|
// only by this subpool until all transactions are evicted
|
||||||
var (
|
var (
|
||||||
_, hasPending = pool.pending[from]
|
_, hasPending = pool.pending[from]
|
||||||
_, hasQueued = pool.queue[from]
|
_, hasQueued = pool.queue.get(from)
|
||||||
)
|
)
|
||||||
if !hasPending && !hasQueued {
|
if !hasPending && !hasQueued {
|
||||||
if err := pool.reserver.Hold(from); err != nil {
|
if err := pool.reserver.Hold(from); err != nil {
|
||||||
|
|
@ -790,7 +771,7 @@ func (pool *LegacyPool) add(tx *types.Transaction) (replaced bool, err error) {
|
||||||
log.Trace("Pooled new executable transaction", "hash", hash, "from", from, "to", tx.To())
|
log.Trace("Pooled new executable transaction", "hash", hash, "from", from, "to", tx.To())
|
||||||
|
|
||||||
// Successful promotion, bump the heartbeat
|
// Successful promotion, bump the heartbeat
|
||||||
pool.beats[from] = time.Now()
|
pool.queue.bump(from)
|
||||||
return old != nil, nil
|
return old != nil, nil
|
||||||
}
|
}
|
||||||
// New transaction isn't replacing a pending one, push into queue
|
// New transaction isn't replacing a pending one, push into queue
|
||||||
|
|
@ -815,7 +796,7 @@ func (pool *LegacyPool) isGapped(from common.Address, tx *types.Transaction) boo
|
||||||
}
|
}
|
||||||
// The transaction has a nonce gap with pending list, it's only considered
|
// The transaction has a nonce gap with pending list, it's only considered
|
||||||
// as executable if transactions in queue can fill up the nonce gap.
|
// as executable if transactions in queue can fill up the nonce gap.
|
||||||
queue, ok := pool.queue[from]
|
queue, ok := pool.queue.get(from)
|
||||||
if !ok {
|
if !ok {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
@ -831,25 +812,12 @@ func (pool *LegacyPool) isGapped(from common.Address, tx *types.Transaction) boo
|
||||||
//
|
//
|
||||||
// Note, this method assumes the pool lock is held!
|
// Note, this method assumes the pool lock is held!
|
||||||
func (pool *LegacyPool) enqueueTx(hash common.Hash, tx *types.Transaction, addAll bool) (bool, error) {
|
func (pool *LegacyPool) enqueueTx(hash common.Hash, tx *types.Transaction, addAll bool) (bool, error) {
|
||||||
// Try to insert the transaction into the future queue
|
replaced, err := pool.queue.add(tx)
|
||||||
from, _ := types.Sender(pool.signer, tx) // already validated
|
if err != nil {
|
||||||
if pool.queue[from] == nil {
|
return false, err
|
||||||
pool.queue[from] = newList(false)
|
|
||||||
}
|
}
|
||||||
inserted, old := pool.queue[from].Add(tx, pool.config.PriceBump)
|
if replaced != nil {
|
||||||
if !inserted {
|
pool.removeTx(*replaced, true, true)
|
||||||
// An older transaction was better, discard this
|
|
||||||
queuedDiscardMeter.Mark(1)
|
|
||||||
return false, txpool.ErrReplaceUnderpriced
|
|
||||||
}
|
|
||||||
// Discard any previous transaction and mark this
|
|
||||||
if old != nil {
|
|
||||||
pool.all.Remove(old.Hash())
|
|
||||||
pool.priced.Removed(1)
|
|
||||||
queuedReplaceMeter.Mark(1)
|
|
||||||
} else {
|
|
||||||
// Nothing was replaced, bump the queued counter
|
|
||||||
queuedGauge.Inc(1)
|
|
||||||
}
|
}
|
||||||
// If the transaction isn't in lookup set but it's expected to be there,
|
// If the transaction isn't in lookup set but it's expected to be there,
|
||||||
// show the error log.
|
// show the error log.
|
||||||
|
|
@ -860,11 +828,7 @@ func (pool *LegacyPool) enqueueTx(hash common.Hash, tx *types.Transaction, addAl
|
||||||
pool.all.Add(tx)
|
pool.all.Add(tx)
|
||||||
pool.priced.Put(tx)
|
pool.priced.Put(tx)
|
||||||
}
|
}
|
||||||
// If we never record the heartbeat, do it right now.
|
return replaced != nil, nil
|
||||||
if _, exist := pool.beats[from]; !exist {
|
|
||||||
pool.beats[from] = time.Now()
|
|
||||||
}
|
|
||||||
return old != nil, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// promoteTx adds a transaction to the pending (processable) list of transactions
|
// promoteTx adds a transaction to the pending (processable) list of transactions
|
||||||
|
|
@ -899,7 +863,7 @@ func (pool *LegacyPool) promoteTx(addr common.Address, hash common.Hash, tx *typ
|
||||||
pool.pendingNonces.set(addr, tx.Nonce()+1)
|
pool.pendingNonces.set(addr, tx.Nonce()+1)
|
||||||
|
|
||||||
// Successful promotion, bump the heartbeat
|
// Successful promotion, bump the heartbeat
|
||||||
pool.beats[addr] = time.Now()
|
pool.queue.bump(addr)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1019,7 +983,7 @@ func (pool *LegacyPool) Status(hash common.Hash) txpool.TxStatus {
|
||||||
|
|
||||||
if txList := pool.pending[from]; txList != nil && txList.txs.items[tx.Nonce()] != nil {
|
if txList := pool.pending[from]; txList != nil && txList.txs.items[tx.Nonce()] != nil {
|
||||||
return txpool.TxStatusPending
|
return txpool.TxStatusPending
|
||||||
} else if txList := pool.queue[from]; txList != nil && txList.txs.items[tx.Nonce()] != nil {
|
} else if txList, ok := pool.queue.get(from); ok && txList.txs.items[tx.Nonce()] != nil {
|
||||||
return txpool.TxStatusQueued
|
return txpool.TxStatusQueued
|
||||||
}
|
}
|
||||||
return txpool.TxStatusUnknown
|
return txpool.TxStatusUnknown
|
||||||
|
|
@ -1096,7 +1060,7 @@ func (pool *LegacyPool) removeTx(hash common.Hash, outofbound bool, unreserve bo
|
||||||
defer func() {
|
defer func() {
|
||||||
var (
|
var (
|
||||||
_, hasPending = pool.pending[addr]
|
_, hasPending = pool.pending[addr]
|
||||||
_, hasQueued = pool.queue[addr]
|
_, hasQueued = pool.queue.get(addr)
|
||||||
)
|
)
|
||||||
if !hasPending && !hasQueued {
|
if !hasPending && !hasQueued {
|
||||||
pool.reserver.Release(addr)
|
pool.reserver.Release(addr)
|
||||||
|
|
@ -1128,16 +1092,7 @@ func (pool *LegacyPool) removeTx(hash common.Hash, outofbound bool, unreserve bo
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Transaction is in the future queue
|
// Transaction is in the future queue
|
||||||
if future := pool.queue[addr]; future != nil {
|
pool.queue.remove(addr, tx)
|
||||||
if removed, _ := future.Remove(tx); removed {
|
|
||||||
// Reduce the queued counter
|
|
||||||
queuedGauge.Dec(1)
|
|
||||||
}
|
|
||||||
if future.Empty() {
|
|
||||||
delete(pool.queue, addr)
|
|
||||||
delete(pool.beats, addr)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1285,10 +1240,7 @@ func (pool *LegacyPool) runReorg(done chan struct{}, reset *txpoolResetRequest,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Reset needs promote for all addresses
|
// Reset needs promote for all addresses
|
||||||
promoteAddrs = make([]common.Address, 0, len(pool.queue))
|
promoteAddrs = pool.queue.addresses()
|
||||||
for addr := range pool.queue {
|
|
||||||
promoteAddrs = append(promoteAddrs, addr)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// Check for pending transactions for every account that sent new ones
|
// Check for pending transactions for every account that sent new ones
|
||||||
promoted := pool.promoteExecutables(promoteAddrs)
|
promoted := pool.promoteExecutables(promoteAddrs)
|
||||||
|
|
@ -1442,62 +1394,31 @@ func (pool *LegacyPool) reset(oldHead, newHead *types.Header) {
|
||||||
// future queue to the set of pending transactions. During this process, all
|
// future queue to the set of pending transactions. During this process, all
|
||||||
// invalidated transactions (low nonce, low balance) are deleted.
|
// invalidated transactions (low nonce, low balance) are deleted.
|
||||||
func (pool *LegacyPool) promoteExecutables(accounts []common.Address) []*types.Transaction {
|
func (pool *LegacyPool) promoteExecutables(accounts []common.Address) []*types.Transaction {
|
||||||
// Track the promoted transactions to broadcast them at once
|
|
||||||
var promoted []*types.Transaction
|
|
||||||
|
|
||||||
// Iterate over all accounts and promote any executable transactions
|
|
||||||
gasLimit := pool.currentHead.Load().GasLimit
|
gasLimit := pool.currentHead.Load().GasLimit
|
||||||
for _, addr := range accounts {
|
promotable, dropped, removedAddresses := pool.queue.promoteExecutables(accounts, gasLimit, pool.currentState, pool.pendingNonces)
|
||||||
list := pool.queue[addr]
|
|
||||||
if list == nil {
|
|
||||||
continue // Just in case someone calls with a non existing account
|
|
||||||
}
|
|
||||||
// Drop all transactions that are deemed too old (low nonce)
|
|
||||||
forwards := list.Forward(pool.currentState.GetNonce(addr))
|
|
||||||
for _, tx := range forwards {
|
|
||||||
pool.all.Remove(tx.Hash())
|
|
||||||
}
|
|
||||||
log.Trace("Removed old queued transactions", "count", len(forwards))
|
|
||||||
// Drop all transactions that are too costly (low balance or out of gas)
|
|
||||||
drops, _ := list.Filter(pool.currentState.GetBalance(addr), gasLimit)
|
|
||||||
for _, tx := range drops {
|
|
||||||
pool.all.Remove(tx.Hash())
|
|
||||||
}
|
|
||||||
log.Trace("Removed unpayable queued transactions", "count", len(drops))
|
|
||||||
queuedNofundsMeter.Mark(int64(len(drops)))
|
|
||||||
|
|
||||||
// Gather all executable transactions and promote them
|
// promote all promotable transactions
|
||||||
readies := list.Ready(pool.pendingNonces.get(addr))
|
promoted := make([]*types.Transaction, 0, len(promotable))
|
||||||
for _, tx := range readies {
|
for _, tx := range promotable {
|
||||||
hash := tx.Hash()
|
from, _ := pool.signer.Sender(tx)
|
||||||
if pool.promoteTx(addr, hash, tx) {
|
if pool.promoteTx(from, tx.Hash(), tx) {
|
||||||
promoted = append(promoted, tx)
|
promoted = append(promoted, tx)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
log.Trace("Promoted queued transactions", "count", len(promoted))
|
|
||||||
queuedGauge.Dec(int64(len(readies)))
|
|
||||||
|
|
||||||
// Drop all transactions over the allowed limit
|
// remove all removable transactions
|
||||||
var caps = list.Cap(int(pool.config.AccountQueue))
|
for _, hash := range dropped {
|
||||||
for _, tx := range caps {
|
|
||||||
hash := tx.Hash()
|
|
||||||
pool.all.Remove(hash)
|
pool.all.Remove(hash)
|
||||||
log.Trace("Removed cap-exceeding queued transaction", "hash", hash)
|
|
||||||
}
|
}
|
||||||
queuedRateLimitMeter.Mark(int64(len(caps)))
|
pool.priced.Removed(len(dropped))
|
||||||
// Mark all the items dropped as removed
|
|
||||||
pool.priced.Removed(len(forwards) + len(drops) + len(caps))
|
|
||||||
queuedGauge.Dec(int64(len(forwards) + len(drops) + len(caps)))
|
|
||||||
|
|
||||||
// Delete the entire queue entry if it became empty.
|
// release all accounts that have no more transactions in the pool
|
||||||
if list.Empty() {
|
for _, addr := range removedAddresses {
|
||||||
delete(pool.queue, addr)
|
_, hasPending := pool.pending[addr]
|
||||||
delete(pool.beats, addr)
|
if !hasPending {
|
||||||
if _, ok := pool.pending[addr]; !ok {
|
|
||||||
pool.reserver.Release(addr)
|
pool.reserver.Release(addr)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return promoted
|
return promoted
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1585,43 +1506,18 @@ func (pool *LegacyPool) truncatePending() {
|
||||||
|
|
||||||
// truncateQueue drops the oldest transactions in the queue if the pool is above the global queue limit.
|
// truncateQueue drops the oldest transactions in the queue if the pool is above the global queue limit.
|
||||||
func (pool *LegacyPool) truncateQueue() {
|
func (pool *LegacyPool) truncateQueue() {
|
||||||
queued := uint64(0)
|
removed, removedAddresses := pool.queue.truncate()
|
||||||
for _, list := range pool.queue {
|
|
||||||
queued += uint64(list.Len())
|
|
||||||
}
|
|
||||||
if queued <= pool.config.GlobalQueue {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sort all accounts with queued transactions by heartbeat
|
// Remove all removable transactions from the lookup and global price list
|
||||||
addresses := make(addressesByHeartbeat, 0, len(pool.queue))
|
for _, hash := range removed {
|
||||||
for addr := range pool.queue {
|
pool.all.Remove(hash)
|
||||||
addresses = append(addresses, addressByHeartbeat{addr, pool.beats[addr]})
|
|
||||||
}
|
}
|
||||||
sort.Sort(sort.Reverse(addresses))
|
pool.priced.Removed(len(removed))
|
||||||
|
|
||||||
// Drop transactions until the total is below the limit
|
for _, addr := range removedAddresses {
|
||||||
for drop := queued - pool.config.GlobalQueue; drop > 0 && len(addresses) > 0; {
|
_, hasPending := pool.pending[addr]
|
||||||
addr := addresses[len(addresses)-1]
|
if !hasPending {
|
||||||
list := pool.queue[addr.address]
|
pool.reserver.Release(addr)
|
||||||
|
|
||||||
addresses = addresses[:len(addresses)-1]
|
|
||||||
|
|
||||||
// Drop all transactions if they are less than the overflow
|
|
||||||
if size := uint64(list.Len()); size <= drop {
|
|
||||||
for _, tx := range list.Flatten() {
|
|
||||||
pool.removeTx(tx.Hash(), true, true)
|
|
||||||
}
|
|
||||||
drop -= size
|
|
||||||
queuedRateLimitMeter.Mark(int64(size))
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
// Otherwise drop only last few transactions
|
|
||||||
txs := list.Flatten()
|
|
||||||
for i := len(txs) - 1; i >= 0 && drop > 0; i-- {
|
|
||||||
pool.removeTx(txs[i].Hash(), true, true)
|
|
||||||
drop--
|
|
||||||
queuedRateLimitMeter.Mark(1)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1679,25 +1575,13 @@ func (pool *LegacyPool) demoteUnexecutables() {
|
||||||
// Delete the entire pending entry if it became empty.
|
// Delete the entire pending entry if it became empty.
|
||||||
if list.Empty() {
|
if list.Empty() {
|
||||||
delete(pool.pending, addr)
|
delete(pool.pending, addr)
|
||||||
if _, ok := pool.queue[addr]; !ok {
|
if _, ok := pool.queue.get(addr); !ok {
|
||||||
pool.reserver.Release(addr)
|
pool.reserver.Release(addr)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// addressByHeartbeat is an account address tagged with its last activity timestamp.
|
|
||||||
type addressByHeartbeat struct {
|
|
||||||
address common.Address
|
|
||||||
heartbeat time.Time
|
|
||||||
}
|
|
||||||
|
|
||||||
type addressesByHeartbeat []addressByHeartbeat
|
|
||||||
|
|
||||||
func (a addressesByHeartbeat) Len() int { return len(a) }
|
|
||||||
func (a addressesByHeartbeat) Less(i, j int) bool { return a[i].heartbeat.Before(a[j].heartbeat) }
|
|
||||||
func (a addressesByHeartbeat) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
|
||||||
|
|
||||||
// accountSet is simply a set of addresses to check for existence, and a signer
|
// accountSet is simply a set of addresses to check for existence, and a signer
|
||||||
// capable of deriving addresses from transactions.
|
// capable of deriving addresses from transactions.
|
||||||
type accountSet struct {
|
type accountSet struct {
|
||||||
|
|
@ -1938,17 +1822,17 @@ func (pool *LegacyPool) Clear() {
|
||||||
// acquire the subpool lock until the transaction addition is completed.
|
// acquire the subpool lock until the transaction addition is completed.
|
||||||
|
|
||||||
for addr := range pool.pending {
|
for addr := range pool.pending {
|
||||||
if _, ok := pool.queue[addr]; !ok {
|
if _, ok := pool.queue.get(addr); !ok {
|
||||||
pool.reserver.Release(addr)
|
pool.reserver.Release(addr)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for addr := range pool.queue {
|
for _, addr := range pool.queue.addresses() {
|
||||||
pool.reserver.Release(addr)
|
pool.reserver.Release(addr)
|
||||||
}
|
}
|
||||||
pool.all.Clear()
|
pool.all.Clear()
|
||||||
pool.priced.Reheap()
|
pool.priced.Reheap()
|
||||||
pool.pending = make(map[common.Address]*list)
|
pool.pending = make(map[common.Address]*list)
|
||||||
pool.queue = make(map[common.Address]*list)
|
pool.queue = newQueue(pool.config, pool.signer)
|
||||||
pool.pendingNonces = newNoncer(pool.currentState)
|
pool.pendingNonces = newNoncer(pool.currentState)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -466,8 +466,8 @@ func TestQueue(t *testing.T) {
|
||||||
if _, ok := pool.pending[from].txs.items[tx.Nonce()]; ok {
|
if _, ok := pool.pending[from].txs.items[tx.Nonce()]; ok {
|
||||||
t.Error("expected transaction to be in tx pool")
|
t.Error("expected transaction to be in tx pool")
|
||||||
}
|
}
|
||||||
if len(pool.queue) > 0 {
|
if len(pool.queue.queued) > 0 {
|
||||||
t.Error("expected transaction queue to be empty. is", len(pool.queue))
|
t.Error("expected transaction queue to be empty. is", len(pool.queue.queued))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -492,8 +492,8 @@ func TestQueue2(t *testing.T) {
|
||||||
if len(pool.pending) != 1 {
|
if len(pool.pending) != 1 {
|
||||||
t.Error("expected pending length to be 1, got", len(pool.pending))
|
t.Error("expected pending length to be 1, got", len(pool.pending))
|
||||||
}
|
}
|
||||||
if pool.queue[from].Len() != 2 {
|
if list, _ := pool.queue.get(from); list.Len() != 2 {
|
||||||
t.Error("expected len(queue) == 2, got", pool.queue[from].Len())
|
t.Error("expected len(queue) == 2, got", list.Len())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -639,8 +639,8 @@ func TestMissingNonce(t *testing.T) {
|
||||||
if len(pool.pending) != 0 {
|
if len(pool.pending) != 0 {
|
||||||
t.Error("expected 0 pending transactions, got", len(pool.pending))
|
t.Error("expected 0 pending transactions, got", len(pool.pending))
|
||||||
}
|
}
|
||||||
if pool.queue[addr].Len() != 1 {
|
if list, _ := pool.queue.get(addr); list.Len() != 1 {
|
||||||
t.Error("expected 1 queued transaction, got", pool.queue[addr].Len())
|
t.Error("expected 1 queued transaction, got", list.Len())
|
||||||
}
|
}
|
||||||
if pool.all.Count() != 1 {
|
if pool.all.Count() != 1 {
|
||||||
t.Error("expected 1 total transactions, got", pool.all.Count())
|
t.Error("expected 1 total transactions, got", pool.all.Count())
|
||||||
|
|
@ -712,8 +712,8 @@ func TestDropping(t *testing.T) {
|
||||||
if pool.pending[account].Len() != 3 {
|
if pool.pending[account].Len() != 3 {
|
||||||
t.Errorf("pending transaction mismatch: have %d, want %d", pool.pending[account].Len(), 3)
|
t.Errorf("pending transaction mismatch: have %d, want %d", pool.pending[account].Len(), 3)
|
||||||
}
|
}
|
||||||
if pool.queue[account].Len() != 3 {
|
if list, _ := pool.queue.get(account); list.Len() != 3 {
|
||||||
t.Errorf("queued transaction mismatch: have %d, want %d", pool.queue[account].Len(), 3)
|
t.Errorf("queued transaction mismatch: have %d, want %d", list.Len(), 3)
|
||||||
}
|
}
|
||||||
if pool.all.Count() != 6 {
|
if pool.all.Count() != 6 {
|
||||||
t.Errorf("total transaction mismatch: have %d, want %d", pool.all.Count(), 6)
|
t.Errorf("total transaction mismatch: have %d, want %d", pool.all.Count(), 6)
|
||||||
|
|
@ -722,8 +722,8 @@ func TestDropping(t *testing.T) {
|
||||||
if pool.pending[account].Len() != 3 {
|
if pool.pending[account].Len() != 3 {
|
||||||
t.Errorf("pending transaction mismatch: have %d, want %d", pool.pending[account].Len(), 3)
|
t.Errorf("pending transaction mismatch: have %d, want %d", pool.pending[account].Len(), 3)
|
||||||
}
|
}
|
||||||
if pool.queue[account].Len() != 3 {
|
if list, _ := pool.queue.get(account); list.Len() != 3 {
|
||||||
t.Errorf("queued transaction mismatch: have %d, want %d", pool.queue[account].Len(), 3)
|
t.Errorf("queued transaction mismatch: have %d, want %d", list.Len(), 3)
|
||||||
}
|
}
|
||||||
if pool.all.Count() != 6 {
|
if pool.all.Count() != 6 {
|
||||||
t.Errorf("total transaction mismatch: have %d, want %d", pool.all.Count(), 6)
|
t.Errorf("total transaction mismatch: have %d, want %d", pool.all.Count(), 6)
|
||||||
|
|
@ -741,13 +741,14 @@ func TestDropping(t *testing.T) {
|
||||||
if _, ok := pool.pending[account].txs.items[tx2.Nonce()]; ok {
|
if _, ok := pool.pending[account].txs.items[tx2.Nonce()]; ok {
|
||||||
t.Errorf("out-of-fund pending transaction present: %v", tx1)
|
t.Errorf("out-of-fund pending transaction present: %v", tx1)
|
||||||
}
|
}
|
||||||
if _, ok := pool.queue[account].txs.items[tx10.Nonce()]; !ok {
|
list, _ := pool.queue.get(account)
|
||||||
|
if _, ok := list.txs.items[tx10.Nonce()]; !ok {
|
||||||
t.Errorf("funded queued transaction missing: %v", tx10)
|
t.Errorf("funded queued transaction missing: %v", tx10)
|
||||||
}
|
}
|
||||||
if _, ok := pool.queue[account].txs.items[tx11.Nonce()]; !ok {
|
if _, ok := list.txs.items[tx11.Nonce()]; !ok {
|
||||||
t.Errorf("funded queued transaction missing: %v", tx10)
|
t.Errorf("funded queued transaction missing: %v", tx10)
|
||||||
}
|
}
|
||||||
if _, ok := pool.queue[account].txs.items[tx12.Nonce()]; ok {
|
if _, ok := list.txs.items[tx12.Nonce()]; ok {
|
||||||
t.Errorf("out-of-fund queued transaction present: %v", tx11)
|
t.Errorf("out-of-fund queued transaction present: %v", tx11)
|
||||||
}
|
}
|
||||||
if pool.all.Count() != 4 {
|
if pool.all.Count() != 4 {
|
||||||
|
|
@ -763,10 +764,11 @@ func TestDropping(t *testing.T) {
|
||||||
if _, ok := pool.pending[account].txs.items[tx1.Nonce()]; ok {
|
if _, ok := pool.pending[account].txs.items[tx1.Nonce()]; ok {
|
||||||
t.Errorf("over-gased pending transaction present: %v", tx1)
|
t.Errorf("over-gased pending transaction present: %v", tx1)
|
||||||
}
|
}
|
||||||
if _, ok := pool.queue[account].txs.items[tx10.Nonce()]; !ok {
|
list, _ = pool.queue.get(account)
|
||||||
|
if _, ok := list.txs.items[tx10.Nonce()]; !ok {
|
||||||
t.Errorf("funded queued transaction missing: %v", tx10)
|
t.Errorf("funded queued transaction missing: %v", tx10)
|
||||||
}
|
}
|
||||||
if _, ok := pool.queue[account].txs.items[tx11.Nonce()]; ok {
|
if _, ok := list.txs.items[tx11.Nonce()]; ok {
|
||||||
t.Errorf("over-gased queued transaction present: %v", tx11)
|
t.Errorf("over-gased queued transaction present: %v", tx11)
|
||||||
}
|
}
|
||||||
if pool.all.Count() != 2 {
|
if pool.all.Count() != 2 {
|
||||||
|
|
@ -820,8 +822,8 @@ func TestPostponing(t *testing.T) {
|
||||||
if pending := pool.pending[accs[0]].Len() + pool.pending[accs[1]].Len(); pending != len(txs) {
|
if pending := pool.pending[accs[0]].Len() + pool.pending[accs[1]].Len(); pending != len(txs) {
|
||||||
t.Errorf("pending transaction mismatch: have %d, want %d", pending, len(txs))
|
t.Errorf("pending transaction mismatch: have %d, want %d", pending, len(txs))
|
||||||
}
|
}
|
||||||
if len(pool.queue) != 0 {
|
if len(pool.queue.addresses()) != 0 {
|
||||||
t.Errorf("queued accounts mismatch: have %d, want %d", len(pool.queue), 0)
|
t.Errorf("queued accounts mismatch: have %d, want %d", len(pool.queue.addresses()), 0)
|
||||||
}
|
}
|
||||||
if pool.all.Count() != len(txs) {
|
if pool.all.Count() != len(txs) {
|
||||||
t.Errorf("total transaction mismatch: have %d, want %d", pool.all.Count(), len(txs))
|
t.Errorf("total transaction mismatch: have %d, want %d", pool.all.Count(), len(txs))
|
||||||
|
|
@ -830,8 +832,8 @@ func TestPostponing(t *testing.T) {
|
||||||
if pending := pool.pending[accs[0]].Len() + pool.pending[accs[1]].Len(); pending != len(txs) {
|
if pending := pool.pending[accs[0]].Len() + pool.pending[accs[1]].Len(); pending != len(txs) {
|
||||||
t.Errorf("pending transaction mismatch: have %d, want %d", pending, len(txs))
|
t.Errorf("pending transaction mismatch: have %d, want %d", pending, len(txs))
|
||||||
}
|
}
|
||||||
if len(pool.queue) != 0 {
|
if len(pool.queue.addresses()) != 0 {
|
||||||
t.Errorf("queued accounts mismatch: have %d, want %d", len(pool.queue), 0)
|
t.Errorf("queued accounts mismatch: have %d, want %d", len(pool.queue.addresses()), 0)
|
||||||
}
|
}
|
||||||
if pool.all.Count() != len(txs) {
|
if pool.all.Count() != len(txs) {
|
||||||
t.Errorf("total transaction mismatch: have %d, want %d", pool.all.Count(), len(txs))
|
t.Errorf("total transaction mismatch: have %d, want %d", pool.all.Count(), len(txs))
|
||||||
|
|
@ -847,7 +849,8 @@ func TestPostponing(t *testing.T) {
|
||||||
if _, ok := pool.pending[accs[0]].txs.items[txs[0].Nonce()]; !ok {
|
if _, ok := pool.pending[accs[0]].txs.items[txs[0].Nonce()]; !ok {
|
||||||
t.Errorf("tx %d: valid and funded transaction missing from pending pool: %v", 0, txs[0])
|
t.Errorf("tx %d: valid and funded transaction missing from pending pool: %v", 0, txs[0])
|
||||||
}
|
}
|
||||||
if _, ok := pool.queue[accs[0]].txs.items[txs[0].Nonce()]; ok {
|
list, _ := pool.queue.get(accs[0])
|
||||||
|
if _, ok := list.txs.items[txs[0].Nonce()]; ok {
|
||||||
t.Errorf("tx %d: valid and funded transaction present in future queue: %v", 0, txs[0])
|
t.Errorf("tx %d: valid and funded transaction present in future queue: %v", 0, txs[0])
|
||||||
}
|
}
|
||||||
for i, tx := range txs[1:100] {
|
for i, tx := range txs[1:100] {
|
||||||
|
|
@ -855,14 +858,14 @@ func TestPostponing(t *testing.T) {
|
||||||
if _, ok := pool.pending[accs[0]].txs.items[tx.Nonce()]; ok {
|
if _, ok := pool.pending[accs[0]].txs.items[tx.Nonce()]; ok {
|
||||||
t.Errorf("tx %d: valid but future transaction present in pending pool: %v", i+1, tx)
|
t.Errorf("tx %d: valid but future transaction present in pending pool: %v", i+1, tx)
|
||||||
}
|
}
|
||||||
if _, ok := pool.queue[accs[0]].txs.items[tx.Nonce()]; !ok {
|
if _, ok := list.txs.items[tx.Nonce()]; !ok {
|
||||||
t.Errorf("tx %d: valid but future transaction missing from future queue: %v", i+1, tx)
|
t.Errorf("tx %d: valid but future transaction missing from future queue: %v", i+1, tx)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if _, ok := pool.pending[accs[0]].txs.items[tx.Nonce()]; ok {
|
if _, ok := pool.pending[accs[0]].txs.items[tx.Nonce()]; ok {
|
||||||
t.Errorf("tx %d: out-of-fund transaction present in pending pool: %v", i+1, tx)
|
t.Errorf("tx %d: out-of-fund transaction present in pending pool: %v", i+1, tx)
|
||||||
}
|
}
|
||||||
if _, ok := pool.queue[accs[0]].txs.items[tx.Nonce()]; ok {
|
if _, ok := list.txs.items[tx.Nonce()]; ok {
|
||||||
t.Errorf("tx %d: out-of-fund transaction present in future queue: %v", i+1, tx)
|
t.Errorf("tx %d: out-of-fund transaction present in future queue: %v", i+1, tx)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -872,13 +875,14 @@ func TestPostponing(t *testing.T) {
|
||||||
if pool.pending[accs[1]] != nil {
|
if pool.pending[accs[1]] != nil {
|
||||||
t.Errorf("invalidated account still has pending transactions")
|
t.Errorf("invalidated account still has pending transactions")
|
||||||
}
|
}
|
||||||
|
list, _ = pool.queue.get(accs[1])
|
||||||
for i, tx := range txs[100:] {
|
for i, tx := range txs[100:] {
|
||||||
if i%2 == 1 {
|
if i%2 == 1 {
|
||||||
if _, ok := pool.queue[accs[1]].txs.items[tx.Nonce()]; !ok {
|
if _, ok := list.txs.items[tx.Nonce()]; !ok {
|
||||||
t.Errorf("tx %d: valid but future transaction missing from future queue: %v", 100+i, tx)
|
t.Errorf("tx %d: valid but future transaction missing from future queue: %v", 100+i, tx)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if _, ok := pool.queue[accs[1]].txs.items[tx.Nonce()]; ok {
|
if _, ok := list.txs.items[tx.Nonce()]; ok {
|
||||||
t.Errorf("tx %d: out-of-fund transaction present in future queue: %v", 100+i, tx)
|
t.Errorf("tx %d: out-of-fund transaction present in future queue: %v", 100+i, tx)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -963,13 +967,14 @@ func TestQueueAccountLimiting(t *testing.T) {
|
||||||
if len(pool.pending) != 0 {
|
if len(pool.pending) != 0 {
|
||||||
t.Errorf("tx %d: pending pool size mismatch: have %d, want %d", i, len(pool.pending), 0)
|
t.Errorf("tx %d: pending pool size mismatch: have %d, want %d", i, len(pool.pending), 0)
|
||||||
}
|
}
|
||||||
|
list, _ := pool.queue.get(account)
|
||||||
if i <= testTxPoolConfig.AccountQueue {
|
if i <= testTxPoolConfig.AccountQueue {
|
||||||
if pool.queue[account].Len() != int(i) {
|
if list.Len() != int(i) {
|
||||||
t.Errorf("tx %d: queue size mismatch: have %d, want %d", i, pool.queue[account].Len(), i)
|
t.Errorf("tx %d: queue size mismatch: have %d, want %d", i, list.Len(), i)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if pool.queue[account].Len() != int(testTxPoolConfig.AccountQueue) {
|
if list.Len() != int(testTxPoolConfig.AccountQueue) {
|
||||||
t.Errorf("tx %d: queue limit mismatch: have %d, want %d", i, pool.queue[account].Len(), testTxPoolConfig.AccountQueue)
|
t.Errorf("tx %d: queue limit mismatch: have %d, want %d", i, list.Len(), testTxPoolConfig.AccountQueue)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1020,7 +1025,7 @@ func TestQueueGlobalLimiting(t *testing.T) {
|
||||||
pool.addRemotesSync(txs)
|
pool.addRemotesSync(txs)
|
||||||
|
|
||||||
queued := 0
|
queued := 0
|
||||||
for addr, list := range pool.queue {
|
for addr, list := range pool.queue.queued {
|
||||||
if list.Len() > int(config.AccountQueue) {
|
if list.Len() > int(config.AccountQueue) {
|
||||||
t.Errorf("addr %x: queued accounts overflown allowance: %d > %d", addr, list.Len(), config.AccountQueue)
|
t.Errorf("addr %x: queued accounts overflown allowance: %d > %d", addr, list.Len(), config.AccountQueue)
|
||||||
}
|
}
|
||||||
|
|
@ -1179,8 +1184,8 @@ func TestPendingLimiting(t *testing.T) {
|
||||||
if pool.pending[account].Len() != int(i)+1 {
|
if pool.pending[account].Len() != int(i)+1 {
|
||||||
t.Errorf("tx %d: pending pool size mismatch: have %d, want %d", i, pool.pending[account].Len(), i+1)
|
t.Errorf("tx %d: pending pool size mismatch: have %d, want %d", i, pool.pending[account].Len(), i+1)
|
||||||
}
|
}
|
||||||
if len(pool.queue) != 0 {
|
if len(pool.queue.addresses()) != 0 {
|
||||||
t.Errorf("tx %d: queue size mismatch: have %d, want %d", i, pool.queue[account].Len(), 0)
|
t.Errorf("tx %d: queue size mismatch: have %d, want %d", i, len(pool.queue.addresses()), 0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if pool.all.Count() != int(testTxPoolConfig.AccountQueue+5) {
|
if pool.all.Count() != int(testTxPoolConfig.AccountQueue+5) {
|
||||||
|
|
|
||||||
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"
|
"fmt"
|
||||||
"math/big"
|
"math/big"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common/bitutil"
|
||||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
)
|
)
|
||||||
|
|
@ -125,9 +126,7 @@ func MergeBloom(receipts Receipts) Bloom {
|
||||||
for _, receipt := range receipts {
|
for _, receipt := range receipts {
|
||||||
if len(receipt.Logs) != 0 {
|
if len(receipt.Logs) != 0 {
|
||||||
bl := receipt.Bloom.Bytes()
|
bl := receipt.Bloom.Bytes()
|
||||||
for i := range bin {
|
bitutil.ORBytes(bin[:], bin[:], bl)
|
||||||
bin[i] |= bl[i]
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return bin
|
return bin
|
||||||
|
|
|
||||||
|
|
@ -486,3 +486,11 @@ func (b *EthAPIBackend) StateAtBlock(ctx context.Context, block *types.Block, re
|
||||||
func (b *EthAPIBackend) StateAtTransaction(ctx context.Context, block *types.Block, txIndex int, reexec uint64) (*types.Transaction, vm.BlockContext, *state.StateDB, tracers.StateReleaseFunc, error) {
|
func (b *EthAPIBackend) StateAtTransaction(ctx context.Context, block *types.Block, txIndex int, reexec uint64) (*types.Transaction, vm.BlockContext, *state.StateDB, tracers.StateReleaseFunc, error) {
|
||||||
return b.eth.stateAtTransaction(ctx, block, txIndex, reexec)
|
return b.eth.stateAtTransaction(ctx, block, txIndex, reexec)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (b *EthAPIBackend) RPCTxSyncDefaultTimeout() time.Duration {
|
||||||
|
return b.eth.config.TxSyncDefaultTimeout
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *EthAPIBackend) RPCTxSyncMaxTimeout() time.Duration {
|
||||||
|
return b.eth.config.TxSyncMaxTimeout
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -47,7 +47,6 @@ import (
|
||||||
|
|
||||||
// Register adds the engine API to the full node.
|
// Register adds the engine API to the full node.
|
||||||
func Register(stack *node.Node, backend *eth.Ethereum) error {
|
func Register(stack *node.Node, backend *eth.Ethereum) error {
|
||||||
log.Warn("Engine API enabled", "protocol", "eth")
|
|
||||||
stack.RegisterAPIs([]rpc.API{
|
stack.RegisterAPIs([]rpc.API{
|
||||||
{
|
{
|
||||||
Namespace: "engine",
|
Namespace: "engine",
|
||||||
|
|
|
||||||
|
|
@ -70,6 +70,8 @@ var Defaults = Config{
|
||||||
RPCEVMTimeout: 5 * time.Second,
|
RPCEVMTimeout: 5 * time.Second,
|
||||||
GPO: FullNodeGPO,
|
GPO: FullNodeGPO,
|
||||||
RPCTxFeeCap: 1, // 1 ether
|
RPCTxFeeCap: 1, // 1 ether
|
||||||
|
TxSyncDefaultTimeout: 20 * time.Second,
|
||||||
|
TxSyncMaxTimeout: 1 * time.Minute,
|
||||||
}
|
}
|
||||||
|
|
||||||
//go:generate go run github.com/fjl/gencodec -type Config -formats toml -out gen_config.go
|
//go:generate go run github.com/fjl/gencodec -type Config -formats toml -out gen_config.go
|
||||||
|
|
@ -183,6 +185,10 @@ type Config struct {
|
||||||
|
|
||||||
// OverrideVerkle (TODO: remove after the fork)
|
// OverrideVerkle (TODO: remove after the fork)
|
||||||
OverrideVerkle *uint64 `toml:",omitempty"`
|
OverrideVerkle *uint64 `toml:",omitempty"`
|
||||||
|
|
||||||
|
// EIP-7966: eth_sendRawTransactionSync timeouts
|
||||||
|
TxSyncDefaultTimeout time.Duration `toml:",omitempty"`
|
||||||
|
TxSyncMaxTimeout time.Duration `toml:",omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateConsensusEngine creates a consensus engine for the given chain config.
|
// CreateConsensusEngine creates a consensus engine for the given chain config.
|
||||||
|
|
|
||||||
|
|
@ -62,6 +62,8 @@ func (c Config) MarshalTOML() (interface{}, error) {
|
||||||
OverrideBPO1 *uint64 `toml:",omitempty"`
|
OverrideBPO1 *uint64 `toml:",omitempty"`
|
||||||
OverrideBPO2 *uint64 `toml:",omitempty"`
|
OverrideBPO2 *uint64 `toml:",omitempty"`
|
||||||
OverrideVerkle *uint64 `toml:",omitempty"`
|
OverrideVerkle *uint64 `toml:",omitempty"`
|
||||||
|
TxSyncDefaultTimeout time.Duration `toml:",omitempty"`
|
||||||
|
TxSyncMaxTimeout time.Duration `toml:",omitempty"`
|
||||||
}
|
}
|
||||||
var enc Config
|
var enc Config
|
||||||
enc.Genesis = c.Genesis
|
enc.Genesis = c.Genesis
|
||||||
|
|
@ -109,6 +111,8 @@ func (c Config) MarshalTOML() (interface{}, error) {
|
||||||
enc.OverrideBPO1 = c.OverrideBPO1
|
enc.OverrideBPO1 = c.OverrideBPO1
|
||||||
enc.OverrideBPO2 = c.OverrideBPO2
|
enc.OverrideBPO2 = c.OverrideBPO2
|
||||||
enc.OverrideVerkle = c.OverrideVerkle
|
enc.OverrideVerkle = c.OverrideVerkle
|
||||||
|
enc.TxSyncDefaultTimeout = c.TxSyncDefaultTimeout
|
||||||
|
enc.TxSyncMaxTimeout = c.TxSyncMaxTimeout
|
||||||
return &enc, nil
|
return &enc, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -160,6 +164,8 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error {
|
||||||
OverrideBPO1 *uint64 `toml:",omitempty"`
|
OverrideBPO1 *uint64 `toml:",omitempty"`
|
||||||
OverrideBPO2 *uint64 `toml:",omitempty"`
|
OverrideBPO2 *uint64 `toml:",omitempty"`
|
||||||
OverrideVerkle *uint64 `toml:",omitempty"`
|
OverrideVerkle *uint64 `toml:",omitempty"`
|
||||||
|
TxSyncDefaultTimeout *time.Duration `toml:",omitempty"`
|
||||||
|
TxSyncMaxTimeout *time.Duration `toml:",omitempty"`
|
||||||
}
|
}
|
||||||
var dec Config
|
var dec Config
|
||||||
if err := unmarshal(&dec); err != nil {
|
if err := unmarshal(&dec); err != nil {
|
||||||
|
|
@ -300,5 +306,11 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error {
|
||||||
if dec.OverrideVerkle != nil {
|
if dec.OverrideVerkle != nil {
|
||||||
c.OverrideVerkle = dec.OverrideVerkle
|
c.OverrideVerkle = dec.OverrideVerkle
|
||||||
}
|
}
|
||||||
|
if dec.TxSyncDefaultTimeout != nil {
|
||||||
|
c.TxSyncDefaultTimeout = *dec.TxSyncDefaultTimeout
|
||||||
|
}
|
||||||
|
if dec.TxSyncMaxTimeout != nil {
|
||||||
|
c.TxSyncMaxTimeout = *dec.TxSyncMaxTimeout
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -144,6 +144,7 @@ func (api *FilterAPI) NewPendingTransactionFilter(fullTx *bool) rpc.ID {
|
||||||
api.filtersMu.Unlock()
|
api.filtersMu.Unlock()
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
|
defer pendingTxSub.Unsubscribe()
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case pTx := <-pendingTxs:
|
case pTx := <-pendingTxs:
|
||||||
|
|
@ -156,7 +157,6 @@ func (api *FilterAPI) NewPendingTransactionFilter(fullTx *bool) rpc.ID {
|
||||||
api.filtersMu.Lock()
|
api.filtersMu.Lock()
|
||||||
delete(api.filters, pendingTxSub.ID)
|
delete(api.filters, pendingTxSub.ID)
|
||||||
api.filtersMu.Unlock()
|
api.filtersMu.Unlock()
|
||||||
pendingTxSub.Unsubscribe()
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -219,6 +219,7 @@ func (api *FilterAPI) NewBlockFilter() rpc.ID {
|
||||||
api.filtersMu.Unlock()
|
api.filtersMu.Unlock()
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
|
defer headerSub.Unsubscribe()
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case h := <-headers:
|
case h := <-headers:
|
||||||
|
|
@ -405,6 +406,7 @@ func (api *FilterAPI) NewFilter(crit FilterCriteria) (rpc.ID, error) {
|
||||||
api.filtersMu.Unlock()
|
api.filtersMu.Unlock()
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
|
defer logsSub.Unsubscribe()
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case l := <-logs:
|
case l := <-logs:
|
||||||
|
|
|
||||||
|
|
@ -181,8 +181,7 @@ func newHandler(config *handlerConfig) (*handler, error) {
|
||||||
} else {
|
} else {
|
||||||
head := h.chain.CurrentBlock()
|
head := h.chain.CurrentBlock()
|
||||||
if head.Number.Uint64() > 0 && h.chain.HasState(head.Root) {
|
if head.Number.Uint64() > 0 && h.chain.HasState(head.Root) {
|
||||||
// Print warning log if database is not empty to run snap sync.
|
log.Info("Switch sync mode from snap sync to full sync", "reason", "snap sync complete")
|
||||||
log.Warn("Switch sync mode from snap sync to full sync", "reason", "snap sync complete")
|
|
||||||
} else {
|
} else {
|
||||||
// If snap sync was requested and our database is empty, grant it
|
// If snap sync was requested and our database is empty, grant it
|
||||||
h.snapSync.Store(true)
|
h.snapSync.Store(true)
|
||||||
|
|
|
||||||
|
|
@ -494,12 +494,19 @@ func handleTransactions(backend Backend, msg Decoder, peer *Peer) error {
|
||||||
if err := msg.Decode(&txs); err != nil {
|
if err := msg.Decode(&txs); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
// Duplicate transactions are not allowed
|
||||||
|
seen := make(map[common.Hash]struct{})
|
||||||
for i, tx := range txs {
|
for i, tx := range txs {
|
||||||
// Validate and mark the remote transaction
|
// Validate and mark the remote transaction
|
||||||
if tx == nil {
|
if tx == nil {
|
||||||
return fmt.Errorf("Transactions: transaction %d is nil", i)
|
return fmt.Errorf("Transactions: transaction %d is nil", i)
|
||||||
}
|
}
|
||||||
peer.markTransaction(tx.Hash())
|
hash := tx.Hash()
|
||||||
|
if _, exists := seen[hash]; exists {
|
||||||
|
return fmt.Errorf("Transactions: multiple copies of the same hash %v", hash)
|
||||||
|
}
|
||||||
|
seen[hash] = struct{}{}
|
||||||
|
peer.markTransaction(hash)
|
||||||
}
|
}
|
||||||
return backend.Handle(peer, &txs)
|
return backend.Handle(peer, &txs)
|
||||||
}
|
}
|
||||||
|
|
@ -514,12 +521,19 @@ func handlePooledTransactions(backend Backend, msg Decoder, peer *Peer) error {
|
||||||
if err := msg.Decode(&txs); err != nil {
|
if err := msg.Decode(&txs); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
// Duplicate transactions are not allowed
|
||||||
|
seen := make(map[common.Hash]struct{})
|
||||||
for i, tx := range txs.PooledTransactionsResponse {
|
for i, tx := range txs.PooledTransactionsResponse {
|
||||||
// Validate and mark the remote transaction
|
// Validate and mark the remote transaction
|
||||||
if tx == nil {
|
if tx == nil {
|
||||||
return fmt.Errorf("PooledTransactions: transaction %d is nil", i)
|
return fmt.Errorf("PooledTransactions: transaction %d is nil", i)
|
||||||
}
|
}
|
||||||
peer.markTransaction(tx.Hash())
|
hash := tx.Hash()
|
||||||
|
if _, exists := seen[hash]; exists {
|
||||||
|
return fmt.Errorf("PooledTransactions: multiple copies of the same hash %v", hash)
|
||||||
|
}
|
||||||
|
seen[hash] = struct{}{}
|
||||||
|
peer.markTransaction(hash)
|
||||||
}
|
}
|
||||||
requestTracker.Fulfil(peer.id, peer.version, PooledTransactionsMsg, txs.RequestId)
|
requestTracker.Fulfil(peer.id, peer.version, PooledTransactionsMsg, txs.RequestId)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,7 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/big"
|
"math/big"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum"
|
"github.com/ethereum/go-ethereum"
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
|
@ -705,6 +706,39 @@ func (ec *Client) SendTransaction(ctx context.Context, tx *types.Transaction) er
|
||||||
return ec.c.CallContext(ctx, nil, "eth_sendRawTransaction", hexutil.Encode(data))
|
return ec.c.CallContext(ctx, nil, "eth_sendRawTransaction", hexutil.Encode(data))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SendTransactionSync submits a signed tx and waits for a receipt (or until
|
||||||
|
// the optional timeout elapses on the server side). If timeout == 0, the server
|
||||||
|
// uses its default.
|
||||||
|
func (ec *Client) SendTransactionSync(
|
||||||
|
ctx context.Context,
|
||||||
|
tx *types.Transaction,
|
||||||
|
timeout *time.Duration,
|
||||||
|
) (*types.Receipt, error) {
|
||||||
|
raw, err := tx.MarshalBinary()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return ec.SendRawTransactionSync(ctx, raw, timeout)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ec *Client) SendRawTransactionSync(
|
||||||
|
ctx context.Context,
|
||||||
|
rawTx []byte,
|
||||||
|
timeout *time.Duration,
|
||||||
|
) (*types.Receipt, error) {
|
||||||
|
var ms *hexutil.Uint64
|
||||||
|
if timeout != nil {
|
||||||
|
if d := hexutil.Uint64(timeout.Milliseconds()); d > 0 {
|
||||||
|
ms = &d
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var receipt types.Receipt
|
||||||
|
if err := ec.c.CallContext(ctx, &receipt, "eth_sendRawTransactionSync", hexutil.Bytes(rawTx), ms); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &receipt, nil
|
||||||
|
}
|
||||||
|
|
||||||
// RevertErrorData returns the 'revert reason' data of a contract call.
|
// RevertErrorData returns the 'revert reason' data of a contract call.
|
||||||
//
|
//
|
||||||
// This can be used with CallContract and EstimateGas, and only when the server is Geth.
|
// This can be used with CallContract and EstimateGas, and only when the server is Geth.
|
||||||
|
|
@ -828,3 +862,89 @@ func (p *rpcProgress) toSyncProgress() *ethereum.SyncProgress {
|
||||||
StateIndexRemaining: uint64(p.StateIndexRemaining),
|
StateIndexRemaining: uint64(p.StateIndexRemaining),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SimulateOptions represents the options for eth_simulateV1.
|
||||||
|
type SimulateOptions struct {
|
||||||
|
BlockStateCalls []SimulateBlock `json:"blockStateCalls"`
|
||||||
|
TraceTransfers bool `json:"traceTransfers"`
|
||||||
|
Validation bool `json:"validation"`
|
||||||
|
ReturnFullTransactions bool `json:"returnFullTransactions"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// SimulateBlock represents a batch of calls to be simulated.
|
||||||
|
type SimulateBlock struct {
|
||||||
|
BlockOverrides *ethereum.BlockOverrides `json:"blockOverrides,omitempty"`
|
||||||
|
StateOverrides map[common.Address]ethereum.OverrideAccount `json:"stateOverrides,omitempty"`
|
||||||
|
Calls []ethereum.CallMsg `json:"calls"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalJSON implements json.Marshaler for SimulateBlock.
|
||||||
|
func (s SimulateBlock) MarshalJSON() ([]byte, error) {
|
||||||
|
type Alias struct {
|
||||||
|
BlockOverrides *ethereum.BlockOverrides `json:"blockOverrides,omitempty"`
|
||||||
|
StateOverrides map[common.Address]ethereum.OverrideAccount `json:"stateOverrides,omitempty"`
|
||||||
|
Calls []interface{} `json:"calls"`
|
||||||
|
}
|
||||||
|
calls := make([]interface{}, len(s.Calls))
|
||||||
|
for i, call := range s.Calls {
|
||||||
|
calls[i] = toCallArg(call)
|
||||||
|
}
|
||||||
|
return json.Marshal(Alias{
|
||||||
|
BlockOverrides: s.BlockOverrides,
|
||||||
|
StateOverrides: s.StateOverrides,
|
||||||
|
Calls: calls,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
//go:generate go run github.com/fjl/gencodec -type SimulateCallResult -field-override simulateCallResultMarshaling -out gen_simulate_call_result.go
|
||||||
|
|
||||||
|
// SimulateCallResult is the result of a simulated call.
|
||||||
|
type SimulateCallResult struct {
|
||||||
|
ReturnValue []byte `json:"returnData"`
|
||||||
|
Logs []*types.Log `json:"logs"`
|
||||||
|
GasUsed uint64 `json:"gasUsed"`
|
||||||
|
Status uint64 `json:"status"`
|
||||||
|
Error *CallError `json:"error,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type simulateCallResultMarshaling struct {
|
||||||
|
ReturnValue hexutil.Bytes
|
||||||
|
GasUsed hexutil.Uint64
|
||||||
|
Status hexutil.Uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
// CallError represents an error from a simulated call.
|
||||||
|
type CallError struct {
|
||||||
|
Code int `json:"code"`
|
||||||
|
Message string `json:"message"`
|
||||||
|
Data string `json:"data,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
//go:generate go run github.com/fjl/gencodec -type SimulateBlockResult -field-override simulateBlockResultMarshaling -out gen_simulate_block_result.go
|
||||||
|
|
||||||
|
// SimulateBlockResult represents the result of a simulated block.
|
||||||
|
type SimulateBlockResult struct {
|
||||||
|
Number *big.Int `json:"number"`
|
||||||
|
Hash common.Hash `json:"hash"`
|
||||||
|
Timestamp uint64 `json:"timestamp"`
|
||||||
|
GasLimit uint64 `json:"gasLimit"`
|
||||||
|
GasUsed uint64 `json:"gasUsed"`
|
||||||
|
FeeRecipient common.Address `json:"miner"`
|
||||||
|
BaseFeePerGas *big.Int `json:"baseFeePerGas,omitempty"`
|
||||||
|
Calls []SimulateCallResult `json:"calls"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type simulateBlockResultMarshaling struct {
|
||||||
|
Number *hexutil.Big
|
||||||
|
Timestamp hexutil.Uint64
|
||||||
|
GasLimit hexutil.Uint64
|
||||||
|
GasUsed hexutil.Uint64
|
||||||
|
BaseFeePerGas *hexutil.Big
|
||||||
|
}
|
||||||
|
|
||||||
|
// SimulateV1 executes transactions on top of a base state.
|
||||||
|
func (ec *Client) SimulateV1(ctx context.Context, opts SimulateOptions, blockNrOrHash *rpc.BlockNumberOrHash) ([]SimulateBlockResult, error) {
|
||||||
|
var result []SimulateBlockResult
|
||||||
|
err := ec.c.CallContext(ctx, &result, "eth_simulateV1", opts, blockNrOrHash)
|
||||||
|
return result, err
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -754,3 +754,250 @@ func ExampleRevertErrorData() {
|
||||||
// revert: 08c379a00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000a75736572206572726f72
|
// revert: 08c379a00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000a75736572206572726f72
|
||||||
// message: user error
|
// message: user error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSimulateV1(t *testing.T) {
|
||||||
|
backend, _, err := newTestBackend(nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to create test backend: %v", err)
|
||||||
|
}
|
||||||
|
defer backend.Close()
|
||||||
|
|
||||||
|
client := ethclient.NewClient(backend.Attach())
|
||||||
|
defer client.Close()
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
// Get current base fee
|
||||||
|
header, err := client.HeaderByNumber(ctx, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to get header: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Simple test: transfer ETH from one account to another
|
||||||
|
from := testAddr
|
||||||
|
to := common.HexToAddress("0x0000000000000000000000000000000000000001")
|
||||||
|
value := big.NewInt(100)
|
||||||
|
gas := uint64(100000)
|
||||||
|
maxFeePerGas := new(big.Int).Mul(header.BaseFee, big.NewInt(2))
|
||||||
|
|
||||||
|
opts := ethclient.SimulateOptions{
|
||||||
|
BlockStateCalls: []ethclient.SimulateBlock{
|
||||||
|
{
|
||||||
|
Calls: []ethereum.CallMsg{
|
||||||
|
{
|
||||||
|
From: from,
|
||||||
|
To: &to,
|
||||||
|
Value: value,
|
||||||
|
Gas: gas,
|
||||||
|
GasFeeCap: maxFeePerGas,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Validation: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
results, err := client.SimulateV1(ctx, opts, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("SimulateV1 failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(results) != 1 {
|
||||||
|
t.Fatalf("expected 1 block result, got %d", len(results))
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(results[0].Calls) != 1 {
|
||||||
|
t.Fatalf("expected 1 call result, got %d", len(results[0].Calls))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check that the transaction succeeded
|
||||||
|
if results[0].Calls[0].Status != 1 {
|
||||||
|
t.Errorf("expected status 1 (success), got %d", results[0].Calls[0].Status)
|
||||||
|
}
|
||||||
|
|
||||||
|
if results[0].Calls[0].Error != nil {
|
||||||
|
t.Errorf("expected no error, got %v", results[0].Calls[0].Error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSimulateV1WithBlockOverrides(t *testing.T) {
|
||||||
|
backend, _, err := newTestBackend(nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to create test backend: %v", err)
|
||||||
|
}
|
||||||
|
defer backend.Close()
|
||||||
|
|
||||||
|
client := ethclient.NewClient(backend.Attach())
|
||||||
|
defer client.Close()
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
// Get current base fee
|
||||||
|
header, err := client.HeaderByNumber(ctx, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to get header: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
from := testAddr
|
||||||
|
to := common.HexToAddress("0x0000000000000000000000000000000000000001")
|
||||||
|
value := big.NewInt(100)
|
||||||
|
gas := uint64(100000)
|
||||||
|
maxFeePerGas := new(big.Int).Mul(header.BaseFee, big.NewInt(2))
|
||||||
|
|
||||||
|
// Override timestamp only
|
||||||
|
timestamp := uint64(1234567890)
|
||||||
|
|
||||||
|
opts := ethclient.SimulateOptions{
|
||||||
|
BlockStateCalls: []ethclient.SimulateBlock{
|
||||||
|
{
|
||||||
|
BlockOverrides: ð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 (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/big"
|
"math/big"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
|
@ -280,97 +279,8 @@ func toCallArg(msg ethereum.CallMsg) interface{} {
|
||||||
return arg
|
return arg
|
||||||
}
|
}
|
||||||
|
|
||||||
// OverrideAccount specifies the state of an account to be overridden.
|
// OverrideAccount is an alias for ethereum.OverrideAccount.
|
||||||
type OverrideAccount struct {
|
type OverrideAccount = ethereum.OverrideAccount
|
||||||
// Nonce sets nonce of the account. Note: the nonce override will only
|
|
||||||
// be applied when it is set to a non-zero value.
|
|
||||||
Nonce uint64
|
|
||||||
|
|
||||||
// Code sets the contract code. The override will be applied
|
// BlockOverrides is an alias for ethereum.BlockOverrides.
|
||||||
// when the code is non-nil, i.e. setting empty code is possible
|
type BlockOverrides = ethereum.BlockOverrides
|
||||||
// using an empty slice.
|
|
||||||
Code []byte
|
|
||||||
|
|
||||||
// Balance sets the account balance.
|
|
||||||
Balance *big.Int
|
|
||||||
|
|
||||||
// State sets the complete storage. The override will be applied
|
|
||||||
// when the given map is non-nil. Using an empty map wipes the
|
|
||||||
// entire contract storage during the call.
|
|
||||||
State map[common.Hash]common.Hash
|
|
||||||
|
|
||||||
// StateDiff allows overriding individual storage slots.
|
|
||||||
StateDiff map[common.Hash]common.Hash
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a OverrideAccount) MarshalJSON() ([]byte, error) {
|
|
||||||
type acc struct {
|
|
||||||
Nonce hexutil.Uint64 `json:"nonce,omitempty"`
|
|
||||||
Code string `json:"code,omitempty"`
|
|
||||||
Balance *hexutil.Big `json:"balance,omitempty"`
|
|
||||||
State interface{} `json:"state,omitempty"`
|
|
||||||
StateDiff map[common.Hash]common.Hash `json:"stateDiff,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
output := acc{
|
|
||||||
Nonce: hexutil.Uint64(a.Nonce),
|
|
||||||
Balance: (*hexutil.Big)(a.Balance),
|
|
||||||
StateDiff: a.StateDiff,
|
|
||||||
}
|
|
||||||
if a.Code != nil {
|
|
||||||
output.Code = hexutil.Encode(a.Code)
|
|
||||||
}
|
|
||||||
if a.State != nil {
|
|
||||||
output.State = a.State
|
|
||||||
}
|
|
||||||
return json.Marshal(output)
|
|
||||||
}
|
|
||||||
|
|
||||||
// BlockOverrides specifies the set of header fields to override.
|
|
||||||
type BlockOverrides struct {
|
|
||||||
// Number overrides the block number.
|
|
||||||
Number *big.Int
|
|
||||||
// Difficulty overrides the block difficulty.
|
|
||||||
Difficulty *big.Int
|
|
||||||
// Time overrides the block timestamp. Time is applied only when
|
|
||||||
// it is non-zero.
|
|
||||||
Time uint64
|
|
||||||
// GasLimit overrides the block gas limit. GasLimit is applied only when
|
|
||||||
// it is non-zero.
|
|
||||||
GasLimit uint64
|
|
||||||
// Coinbase overrides the block coinbase. Coinbase is applied only when
|
|
||||||
// it is different from the zero address.
|
|
||||||
Coinbase common.Address
|
|
||||||
// Random overrides the block extra data which feeds into the RANDOM opcode.
|
|
||||||
// Random is applied only when it is a non-zero hash.
|
|
||||||
Random common.Hash
|
|
||||||
// BaseFee overrides the block base fee.
|
|
||||||
BaseFee *big.Int
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o BlockOverrides) MarshalJSON() ([]byte, error) {
|
|
||||||
type override struct {
|
|
||||||
Number *hexutil.Big `json:"number,omitempty"`
|
|
||||||
Difficulty *hexutil.Big `json:"difficulty,omitempty"`
|
|
||||||
Time hexutil.Uint64 `json:"time,omitempty"`
|
|
||||||
GasLimit hexutil.Uint64 `json:"gasLimit,omitempty"`
|
|
||||||
Coinbase *common.Address `json:"feeRecipient,omitempty"`
|
|
||||||
Random *common.Hash `json:"prevRandao,omitempty"`
|
|
||||||
BaseFee *hexutil.Big `json:"baseFeePerGas,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
output := override{
|
|
||||||
Number: (*hexutil.Big)(o.Number),
|
|
||||||
Difficulty: (*hexutil.Big)(o.Difficulty),
|
|
||||||
Time: hexutil.Uint64(o.Time),
|
|
||||||
GasLimit: hexutil.Uint64(o.GasLimit),
|
|
||||||
BaseFee: (*hexutil.Big)(o.BaseFee),
|
|
||||||
}
|
|
||||||
if o.Coinbase != (common.Address{}) {
|
|
||||||
output.Coinbase = &o.Coinbase
|
|
||||||
}
|
|
||||||
if o.Random != (common.Hash{}) {
|
|
||||||
output.Random = &o.Random
|
|
||||||
}
|
|
||||||
return json.Marshal(output)
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -121,6 +121,10 @@ type AncientReaderOp interface {
|
||||||
// - if maxBytes is not specified, 'count' items will be returned if they are present
|
// - if maxBytes is not specified, 'count' items will be returned if they are present
|
||||||
AncientRange(kind string, start, count, maxBytes uint64) ([][]byte, error)
|
AncientRange(kind string, start, count, maxBytes uint64) ([][]byte, error)
|
||||||
|
|
||||||
|
// AncientBytes retrieves the value segment of the element specified by the id
|
||||||
|
// and value offsets.
|
||||||
|
AncientBytes(kind string, id, offset, length uint64) ([]byte, error)
|
||||||
|
|
||||||
// Ancients returns the ancient item numbers in the ancient store.
|
// Ancients returns the ancient item numbers in the ancient store.
|
||||||
Ancients() (uint64, error)
|
Ancients() (uint64, error)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -140,6 +140,10 @@ func (db *Database) Close() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (db *Database) AncientBytes(kind string, id, offset, length uint64) ([]byte, error) {
|
||||||
|
panic("not supported")
|
||||||
|
}
|
||||||
|
|
||||||
func New(client *rpc.Client) ethdb.Database {
|
func New(client *rpc.Client) ethdb.Database {
|
||||||
if client == nil {
|
if client == nil {
|
||||||
return nil
|
return nil
|
||||||
|
|
|
||||||
|
|
@ -19,10 +19,12 @@ package ethereum
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"math/big"
|
"math/big"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -293,3 +295,98 @@ type BlockNumberReader interface {
|
||||||
type ChainIDReader interface {
|
type ChainIDReader interface {
|
||||||
ChainID(ctx context.Context) (*big.Int, error)
|
ChainID(ctx context.Context) (*big.Int, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// OverrideAccount specifies the state of an account to be overridden.
|
||||||
|
type OverrideAccount struct {
|
||||||
|
// Nonce sets nonce of the account. Note: the nonce override will only
|
||||||
|
// be applied when it is set to a non-zero value.
|
||||||
|
Nonce uint64
|
||||||
|
|
||||||
|
// Code sets the contract code. The override will be applied
|
||||||
|
// when the code is non-nil, i.e. setting empty code is possible
|
||||||
|
// using an empty slice.
|
||||||
|
Code []byte
|
||||||
|
|
||||||
|
// Balance sets the account balance.
|
||||||
|
Balance *big.Int
|
||||||
|
|
||||||
|
// State sets the complete storage. The override will be applied
|
||||||
|
// when the given map is non-nil. Using an empty map wipes the
|
||||||
|
// entire contract storage during the call.
|
||||||
|
State map[common.Hash]common.Hash
|
||||||
|
|
||||||
|
// StateDiff allows overriding individual storage slots.
|
||||||
|
StateDiff map[common.Hash]common.Hash
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a OverrideAccount) MarshalJSON() ([]byte, error) {
|
||||||
|
type acc struct {
|
||||||
|
Nonce hexutil.Uint64 `json:"nonce,omitempty"`
|
||||||
|
Code string `json:"code,omitempty"`
|
||||||
|
Balance *hexutil.Big `json:"balance,omitempty"`
|
||||||
|
State interface{} `json:"state,omitempty"`
|
||||||
|
StateDiff map[common.Hash]common.Hash `json:"stateDiff,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
output := acc{
|
||||||
|
Nonce: hexutil.Uint64(a.Nonce),
|
||||||
|
Balance: (*hexutil.Big)(a.Balance),
|
||||||
|
StateDiff: a.StateDiff,
|
||||||
|
}
|
||||||
|
if a.Code != nil {
|
||||||
|
output.Code = hexutil.Encode(a.Code)
|
||||||
|
}
|
||||||
|
if a.State != nil {
|
||||||
|
output.State = a.State
|
||||||
|
}
|
||||||
|
return json.Marshal(output)
|
||||||
|
}
|
||||||
|
|
||||||
|
// BlockOverrides specifies the set of header fields to override.
|
||||||
|
type BlockOverrides struct {
|
||||||
|
// Number overrides the block number.
|
||||||
|
Number *big.Int
|
||||||
|
// Difficulty overrides the block difficulty.
|
||||||
|
Difficulty *big.Int
|
||||||
|
// Time overrides the block timestamp. Time is applied only when
|
||||||
|
// it is non-zero.
|
||||||
|
Time uint64
|
||||||
|
// GasLimit overrides the block gas limit. GasLimit is applied only when
|
||||||
|
// it is non-zero.
|
||||||
|
GasLimit uint64
|
||||||
|
// Coinbase overrides the block coinbase. Coinbase is applied only when
|
||||||
|
// it is different from the zero address.
|
||||||
|
Coinbase common.Address
|
||||||
|
// Random overrides the block extra data which feeds into the RANDOM opcode.
|
||||||
|
// Random is applied only when it is a non-zero hash.
|
||||||
|
Random common.Hash
|
||||||
|
// BaseFee overrides the block base fee.
|
||||||
|
BaseFee *big.Int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o BlockOverrides) MarshalJSON() ([]byte, error) {
|
||||||
|
type override struct {
|
||||||
|
Number *hexutil.Big `json:"number,omitempty"`
|
||||||
|
Difficulty *hexutil.Big `json:"difficulty,omitempty"`
|
||||||
|
Time hexutil.Uint64 `json:"time,omitempty"`
|
||||||
|
GasLimit hexutil.Uint64 `json:"gasLimit,omitempty"`
|
||||||
|
Coinbase *common.Address `json:"feeRecipient,omitempty"`
|
||||||
|
Random *common.Hash `json:"prevRandao,omitempty"`
|
||||||
|
BaseFee *hexutil.Big `json:"baseFeePerGas,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
output := override{
|
||||||
|
Number: (*hexutil.Big)(o.Number),
|
||||||
|
Difficulty: (*hexutil.Big)(o.Difficulty),
|
||||||
|
Time: hexutil.Uint64(o.Time),
|
||||||
|
GasLimit: hexutil.Uint64(o.GasLimit),
|
||||||
|
BaseFee: (*hexutil.Big)(o.BaseFee),
|
||||||
|
}
|
||||||
|
if o.Coinbase != (common.Address{}) {
|
||||||
|
output.Coinbase = &o.Coinbase
|
||||||
|
}
|
||||||
|
if o.Random != (common.Hash{}) {
|
||||||
|
output.Random = &o.Random
|
||||||
|
}
|
||||||
|
return json.Marshal(output)
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -55,6 +55,7 @@ import (
|
||||||
const estimateGasErrorRatio = 0.015
|
const estimateGasErrorRatio = 0.015
|
||||||
|
|
||||||
var errBlobTxNotSupported = errors.New("signing blob transactions not supported")
|
var errBlobTxNotSupported = errors.New("signing blob transactions not supported")
|
||||||
|
var errSubClosed = errors.New("chain subscription closed")
|
||||||
|
|
||||||
// EthereumAPI provides an API to access Ethereum related information.
|
// EthereumAPI provides an API to access Ethereum related information.
|
||||||
type EthereumAPI struct {
|
type EthereumAPI struct {
|
||||||
|
|
@ -1619,16 +1620,9 @@ func (api *TransactionAPI) SendTransaction(ctx context.Context, args Transaction
|
||||||
// processing (signing + broadcast).
|
// processing (signing + broadcast).
|
||||||
func (api *TransactionAPI) FillTransaction(ctx context.Context, args TransactionArgs) (*SignTransactionResult, error) {
|
func (api *TransactionAPI) FillTransaction(ctx context.Context, args TransactionArgs) (*SignTransactionResult, error) {
|
||||||
// Set some sanity defaults and terminate on failure
|
// Set some sanity defaults and terminate on failure
|
||||||
sidecarVersion := types.BlobSidecarVersion0
|
|
||||||
if len(args.Blobs) > 0 {
|
|
||||||
h := api.b.CurrentHeader()
|
|
||||||
if api.b.ChainConfig().IsOsaka(h.Number, h.Time) {
|
|
||||||
sidecarVersion = types.BlobSidecarVersion1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
config := sidecarConfig{
|
config := sidecarConfig{
|
||||||
blobSidecarAllowed: true,
|
blobSidecarAllowed: true,
|
||||||
blobSidecarVersion: sidecarVersion,
|
blobSidecarVersion: api.currentBlobSidecarVersion(),
|
||||||
}
|
}
|
||||||
if err := args.setDefaults(ctx, api.b, config); err != nil {
|
if err := args.setDefaults(ctx, api.b, config); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
@ -1642,6 +1636,14 @@ func (api *TransactionAPI) FillTransaction(ctx context.Context, args Transaction
|
||||||
return &SignTransactionResult{data, tx}, nil
|
return &SignTransactionResult{data, tx}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (api *TransactionAPI) currentBlobSidecarVersion() byte {
|
||||||
|
h := api.b.CurrentHeader()
|
||||||
|
if api.b.ChainConfig().IsOsaka(h.Number, h.Time) {
|
||||||
|
return types.BlobSidecarVersion1
|
||||||
|
}
|
||||||
|
return types.BlobSidecarVersion0
|
||||||
|
}
|
||||||
|
|
||||||
// SendRawTransaction will add the signed transaction to the transaction pool.
|
// SendRawTransaction will add the signed transaction to the transaction pool.
|
||||||
// The sender is responsible for signing the transaction and using the correct nonce.
|
// The sender is responsible for signing the transaction and using the correct nonce.
|
||||||
func (api *TransactionAPI) SendRawTransaction(ctx context.Context, input hexutil.Bytes) (common.Hash, error) {
|
func (api *TransactionAPI) SendRawTransaction(ctx context.Context, input hexutil.Bytes) (common.Hash, error) {
|
||||||
|
|
@ -1649,9 +1651,119 @@ func (api *TransactionAPI) SendRawTransaction(ctx context.Context, input hexutil
|
||||||
if err := tx.UnmarshalBinary(input); err != nil {
|
if err := tx.UnmarshalBinary(input); err != nil {
|
||||||
return common.Hash{}, err
|
return common.Hash{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Convert legacy blob transaction proofs.
|
||||||
|
// TODO: remove in go-ethereum v1.17.x
|
||||||
|
if sc := tx.BlobTxSidecar(); sc != nil {
|
||||||
|
exp := api.currentBlobSidecarVersion()
|
||||||
|
if sc.Version == types.BlobSidecarVersion0 && exp == types.BlobSidecarVersion1 {
|
||||||
|
if err := sc.ToV1(); err != nil {
|
||||||
|
return common.Hash{}, fmt.Errorf("blob sidecar conversion failed: %v", err)
|
||||||
|
}
|
||||||
|
tx = tx.WithBlobTxSidecar(sc)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return SubmitTransaction(ctx, api.b, tx)
|
return SubmitTransaction(ctx, api.b, tx)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SendRawTransactionSync will add the signed transaction to the transaction pool
|
||||||
|
// and wait until the transaction has been included in a block and return the receipt, or the timeout.
|
||||||
|
func (api *TransactionAPI) SendRawTransactionSync(ctx context.Context, input hexutil.Bytes, timeoutMs *hexutil.Uint64) (map[string]interface{}, error) {
|
||||||
|
tx := new(types.Transaction)
|
||||||
|
if err := tx.UnmarshalBinary(input); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert legacy blob transaction proofs.
|
||||||
|
// TODO: remove in go-ethereum v1.17.x
|
||||||
|
if sc := tx.BlobTxSidecar(); sc != nil {
|
||||||
|
exp := api.currentBlobSidecarVersion()
|
||||||
|
if sc.Version == types.BlobSidecarVersion0 && exp == types.BlobSidecarVersion1 {
|
||||||
|
if err := sc.ToV1(); err != nil {
|
||||||
|
return nil, fmt.Errorf("blob sidecar conversion failed: %v", err)
|
||||||
|
}
|
||||||
|
tx = tx.WithBlobTxSidecar(sc)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ch := make(chan core.ChainEvent, 128)
|
||||||
|
sub := api.b.SubscribeChainEvent(ch)
|
||||||
|
defer sub.Unsubscribe()
|
||||||
|
|
||||||
|
hash, err := SubmitTransaction(ctx, api.b, tx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
maxTimeout = api.b.RPCTxSyncMaxTimeout()
|
||||||
|
defaultTimeout = api.b.RPCTxSyncDefaultTimeout()
|
||||||
|
timeout = defaultTimeout
|
||||||
|
)
|
||||||
|
if timeoutMs != nil && *timeoutMs > 0 {
|
||||||
|
req := time.Duration(*timeoutMs) * time.Millisecond
|
||||||
|
if req > maxTimeout {
|
||||||
|
timeout = maxTimeout
|
||||||
|
} else {
|
||||||
|
timeout = req
|
||||||
|
}
|
||||||
|
}
|
||||||
|
receiptCtx, cancel := context.WithTimeout(ctx, timeout)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
// Fast path.
|
||||||
|
if r, err := api.GetTransactionReceipt(receiptCtx, hash); err == nil && r != nil {
|
||||||
|
return r, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Monitor the receipts
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-receiptCtx.Done():
|
||||||
|
// If server-side wait window elapsed, return the structured timeout.
|
||||||
|
if errors.Is(receiptCtx.Err(), context.DeadlineExceeded) {
|
||||||
|
return nil, &txSyncTimeoutError{
|
||||||
|
msg: fmt.Sprintf("The transaction was added to the transaction pool but wasn't processed in %v", timeout),
|
||||||
|
hash: hash,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, receiptCtx.Err()
|
||||||
|
|
||||||
|
case err, ok := <-sub.Err():
|
||||||
|
if !ok {
|
||||||
|
return nil, errSubClosed
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
|
||||||
|
case ev, ok := <-ch:
|
||||||
|
if !ok {
|
||||||
|
return nil, errSubClosed
|
||||||
|
}
|
||||||
|
rs, txs := ev.Receipts, ev.Transactions
|
||||||
|
if len(rs) == 0 || len(rs) != len(txs) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for i := range rs {
|
||||||
|
if rs[i].TxHash == hash {
|
||||||
|
if rs[i].BlockNumber != nil && rs[i].BlockHash != (common.Hash{}) {
|
||||||
|
signer := types.LatestSigner(api.b.ChainConfig())
|
||||||
|
return MarshalReceipt(
|
||||||
|
rs[i],
|
||||||
|
rs[i].BlockHash,
|
||||||
|
rs[i].BlockNumber.Uint64(),
|
||||||
|
signer,
|
||||||
|
txs[i],
|
||||||
|
int(rs[i].TransactionIndex),
|
||||||
|
), nil
|
||||||
|
}
|
||||||
|
return api.GetTransactionReceipt(receiptCtx, hash)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Sign calculates an ECDSA signature for:
|
// Sign calculates an ECDSA signature for:
|
||||||
// keccak256("\x19Ethereum Signed Message:\n" + len(message) + message).
|
// keccak256("\x19Ethereum Signed Message:\n" + len(message) + message).
|
||||||
//
|
//
|
||||||
|
|
|
||||||
|
|
@ -440,6 +440,19 @@ type testBackend struct {
|
||||||
|
|
||||||
pending *types.Block
|
pending *types.Block
|
||||||
pendingReceipts types.Receipts
|
pendingReceipts types.Receipts
|
||||||
|
|
||||||
|
chainFeed *event.Feed
|
||||||
|
autoMine bool
|
||||||
|
|
||||||
|
sentTx *types.Transaction
|
||||||
|
sentTxHash common.Hash
|
||||||
|
|
||||||
|
syncDefaultTimeout time.Duration
|
||||||
|
syncMaxTimeout time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
func fakeBlockHash(txh common.Hash) common.Hash {
|
||||||
|
return crypto.Keccak256Hash([]byte("testblock"), txh.Bytes())
|
||||||
}
|
}
|
||||||
|
|
||||||
func newTestBackend(t *testing.T, n int, gspec *core.Genesis, engine consensus.Engine, generator func(i int, b *core.BlockGen)) *testBackend {
|
func newTestBackend(t *testing.T, n int, gspec *core.Genesis, engine consensus.Engine, generator func(i int, b *core.BlockGen)) *testBackend {
|
||||||
|
|
@ -466,6 +479,7 @@ func newTestBackend(t *testing.T, n int, gspec *core.Genesis, engine consensus.E
|
||||||
acc: acc,
|
acc: acc,
|
||||||
pending: blocks[n],
|
pending: blocks[n],
|
||||||
pendingReceipts: receipts[n],
|
pendingReceipts: receipts[n],
|
||||||
|
chainFeed: new(event.Feed),
|
||||||
}
|
}
|
||||||
return backend
|
return backend
|
||||||
}
|
}
|
||||||
|
|
@ -587,19 +601,64 @@ func (b testBackend) GetEVM(ctx context.Context, state *state.StateDB, header *t
|
||||||
return vm.NewEVM(context, state, b.chain.Config(), *vmConfig)
|
return vm.NewEVM(context, state, b.chain.Config(), *vmConfig)
|
||||||
}
|
}
|
||||||
func (b testBackend) SubscribeChainEvent(ch chan<- core.ChainEvent) event.Subscription {
|
func (b testBackend) SubscribeChainEvent(ch chan<- core.ChainEvent) event.Subscription {
|
||||||
panic("implement me")
|
return b.chainFeed.Subscribe(ch)
|
||||||
}
|
}
|
||||||
func (b testBackend) SubscribeChainHeadEvent(ch chan<- core.ChainHeadEvent) event.Subscription {
|
func (b testBackend) SubscribeChainHeadEvent(ch chan<- core.ChainHeadEvent) event.Subscription {
|
||||||
panic("implement me")
|
panic("implement me")
|
||||||
}
|
}
|
||||||
func (b testBackend) SendTx(ctx context.Context, signedTx *types.Transaction) error {
|
func (b *testBackend) SendTx(ctx context.Context, tx *types.Transaction) error {
|
||||||
panic("implement me")
|
b.sentTx = tx
|
||||||
|
b.sentTxHash = tx.Hash()
|
||||||
|
|
||||||
|
if b.autoMine {
|
||||||
|
// Synthesize a "mined" receipt at head+1
|
||||||
|
num := b.chain.CurrentHeader().Number.Uint64() + 1
|
||||||
|
receipt := &types.Receipt{
|
||||||
|
TxHash: tx.Hash(),
|
||||||
|
Status: types.ReceiptStatusSuccessful,
|
||||||
|
BlockHash: fakeBlockHash(tx.Hash()),
|
||||||
|
BlockNumber: new(big.Int).SetUint64(num),
|
||||||
|
TransactionIndex: 0,
|
||||||
|
CumulativeGasUsed: 21000,
|
||||||
|
GasUsed: 21000,
|
||||||
|
}
|
||||||
|
// Broadcast a ChainEvent that includes the receipts and txs
|
||||||
|
b.chainFeed.Send(core.ChainEvent{
|
||||||
|
Header: &types.Header{
|
||||||
|
Number: new(big.Int).SetUint64(num),
|
||||||
|
},
|
||||||
|
Receipts: types.Receipts{receipt},
|
||||||
|
Transactions: types.Transactions{tx},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
func (b testBackend) GetCanonicalTransaction(txHash common.Hash) (bool, *types.Transaction, common.Hash, uint64, uint64) {
|
func (b *testBackend) GetCanonicalTransaction(txHash common.Hash) (bool, *types.Transaction, common.Hash, uint64, uint64) {
|
||||||
|
// Treat the auto-mined tx as canonically placed at head+1.
|
||||||
|
if b.autoMine && txHash == b.sentTxHash {
|
||||||
|
num := b.chain.CurrentHeader().Number.Uint64() + 1
|
||||||
|
return true, b.sentTx, fakeBlockHash(txHash), num, 0
|
||||||
|
}
|
||||||
tx, blockHash, blockNumber, index := rawdb.ReadCanonicalTransaction(b.db, txHash)
|
tx, blockHash, blockNumber, index := rawdb.ReadCanonicalTransaction(b.db, txHash)
|
||||||
return tx != nil, tx, blockHash, blockNumber, index
|
return tx != nil, tx, blockHash, blockNumber, index
|
||||||
}
|
}
|
||||||
func (b testBackend) GetCanonicalReceipt(tx *types.Transaction, blockHash common.Hash, blockNumber, blockIndex uint64) (*types.Receipt, error) {
|
func (b *testBackend) GetCanonicalReceipt(tx *types.Transaction, blockHash common.Hash, blockNumber, blockIndex uint64) (*types.Receipt, error) {
|
||||||
|
if b.autoMine && tx != nil && tx.Hash() == b.sentTxHash &&
|
||||||
|
blockHash == fakeBlockHash(tx.Hash()) &&
|
||||||
|
blockIndex == 0 &&
|
||||||
|
blockNumber == b.chain.CurrentHeader().Number.Uint64()+1 {
|
||||||
|
return &types.Receipt{
|
||||||
|
Type: tx.Type(),
|
||||||
|
Status: types.ReceiptStatusSuccessful,
|
||||||
|
CumulativeGasUsed: 21000,
|
||||||
|
GasUsed: 21000,
|
||||||
|
EffectiveGasPrice: big.NewInt(1),
|
||||||
|
BlockHash: blockHash,
|
||||||
|
BlockNumber: new(big.Int).SetUint64(blockNumber),
|
||||||
|
TransactionIndex: 0,
|
||||||
|
TxHash: tx.Hash(),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
return b.chain.GetCanonicalReceipt(tx, blockHash, blockNumber, blockIndex)
|
return b.chain.GetCanonicalReceipt(tx, blockHash, blockNumber, blockIndex)
|
||||||
}
|
}
|
||||||
func (b testBackend) TxIndexDone() bool {
|
func (b testBackend) TxIndexDone() bool {
|
||||||
|
|
@ -3889,3 +3948,109 @@ func (b configTimeBackend) HeaderByNumber(_ context.Context, n rpc.BlockNumber)
|
||||||
func (b configTimeBackend) CurrentHeader() *types.Header {
|
func (b configTimeBackend) CurrentHeader() *types.Header {
|
||||||
return &types.Header{Time: b.time}
|
return &types.Header{Time: b.time}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (b *testBackend) RPCTxSyncDefaultTimeout() time.Duration {
|
||||||
|
if b.syncDefaultTimeout != 0 {
|
||||||
|
return b.syncDefaultTimeout
|
||||||
|
}
|
||||||
|
return 2 * time.Second
|
||||||
|
}
|
||||||
|
func (b *testBackend) RPCTxSyncMaxTimeout() time.Duration {
|
||||||
|
if b.syncMaxTimeout != 0 {
|
||||||
|
return b.syncMaxTimeout
|
||||||
|
}
|
||||||
|
return 5 * time.Minute
|
||||||
|
}
|
||||||
|
func (b *backendMock) RPCTxSyncDefaultTimeout() time.Duration { return 2 * time.Second }
|
||||||
|
func (b *backendMock) RPCTxSyncMaxTimeout() time.Duration { return 5 * time.Minute }
|
||||||
|
|
||||||
|
func makeSignedRaw(t *testing.T, api *TransactionAPI, from, to common.Address, value *big.Int) (hexutil.Bytes, *types.Transaction) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
fillRes, err := api.FillTransaction(context.Background(), TransactionArgs{
|
||||||
|
From: &from,
|
||||||
|
To: &to,
|
||||||
|
Value: (*hexutil.Big)(value),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("FillTransaction failed: %v", err)
|
||||||
|
}
|
||||||
|
signRes, err := api.SignTransaction(context.Background(), argsFromTransaction(fillRes.Tx, from))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("SignTransaction failed: %v", err)
|
||||||
|
}
|
||||||
|
return signRes.Raw, signRes.Tx
|
||||||
|
}
|
||||||
|
|
||||||
|
// makeSelfSignedRaw is a convenience for a 0-ETH self-transfer.
|
||||||
|
func makeSelfSignedRaw(t *testing.T, api *TransactionAPI, addr common.Address) (hexutil.Bytes, *types.Transaction) {
|
||||||
|
return makeSignedRaw(t, api, addr, addr, big.NewInt(0))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSendRawTransactionSync_Success(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
genesis := &core.Genesis{
|
||||||
|
Config: params.TestChainConfig,
|
||||||
|
Alloc: types.GenesisAlloc{},
|
||||||
|
}
|
||||||
|
b := newTestBackend(t, 0, genesis, ethash.NewFaker(), nil)
|
||||||
|
b.autoMine = true // immediately “mines” the tx in-memory
|
||||||
|
|
||||||
|
api := NewTransactionAPI(b, new(AddrLocker))
|
||||||
|
|
||||||
|
raw, _ := makeSelfSignedRaw(t, api, b.acc.Address)
|
||||||
|
|
||||||
|
receipt, err := api.SendRawTransactionSync(context.Background(), raw, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
if receipt == nil {
|
||||||
|
t.Fatalf("expected non-nil receipt")
|
||||||
|
}
|
||||||
|
if _, ok := receipt["blockNumber"]; !ok {
|
||||||
|
t.Fatalf("expected blockNumber in receipt, got %#v", receipt)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSendRawTransactionSync_Timeout(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
genesis := &core.Genesis{
|
||||||
|
Config: params.TestChainConfig,
|
||||||
|
Alloc: types.GenesisAlloc{},
|
||||||
|
}
|
||||||
|
b := newTestBackend(t, 0, genesis, ethash.NewFaker(), nil)
|
||||||
|
b.autoMine = false // don't mine, should time out
|
||||||
|
|
||||||
|
api := NewTransactionAPI(b, new(AddrLocker))
|
||||||
|
|
||||||
|
raw, _ := makeSelfSignedRaw(t, api, b.acc.Address)
|
||||||
|
|
||||||
|
timeout := hexutil.Uint64(200) // 200ms
|
||||||
|
receipt, err := api.SendRawTransactionSync(context.Background(), raw, &timeout)
|
||||||
|
|
||||||
|
if receipt != nil {
|
||||||
|
t.Fatalf("expected nil receipt, got %#v", receipt)
|
||||||
|
}
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("expected timeout error, got nil")
|
||||||
|
}
|
||||||
|
// assert error shape & data (hash)
|
||||||
|
var de interface {
|
||||||
|
ErrorCode() int
|
||||||
|
ErrorData() interface{}
|
||||||
|
}
|
||||||
|
if !errors.As(err, &de) {
|
||||||
|
t.Fatalf("expected data error with code/data, got %T %v", err, err)
|
||||||
|
}
|
||||||
|
if de.ErrorCode() != errCodeTxSyncTimeout {
|
||||||
|
t.Fatalf("expected code %d, got %d", errCodeTxSyncTimeout, de.ErrorCode())
|
||||||
|
}
|
||||||
|
tx := new(types.Transaction)
|
||||||
|
if e := tx.UnmarshalBinary(raw); e != nil {
|
||||||
|
t.Fatal(e)
|
||||||
|
}
|
||||||
|
if got, want := de.ErrorData(), tx.Hash().Hex(); got != want {
|
||||||
|
t.Fatalf("expected ErrorData=%s, got %v", want, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -53,6 +53,8 @@ type Backend interface {
|
||||||
RPCEVMTimeout() time.Duration // global timeout for eth_call over rpc: DoS protection
|
RPCEVMTimeout() time.Duration // global timeout for eth_call over rpc: DoS protection
|
||||||
RPCTxFeeCap() float64 // global tx fee cap for all transaction related APIs
|
RPCTxFeeCap() float64 // global tx fee cap for all transaction related APIs
|
||||||
UnprotectedAllowed() bool // allows only for EIP155 transactions.
|
UnprotectedAllowed() bool // allows only for EIP155 transactions.
|
||||||
|
RPCTxSyncDefaultTimeout() time.Duration
|
||||||
|
RPCTxSyncMaxTimeout() time.Duration
|
||||||
|
|
||||||
// Blockchain API
|
// Blockchain API
|
||||||
SetHead(number uint64)
|
SetHead(number uint64)
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/accounts/abi"
|
"github.com/ethereum/go-ethereum/accounts/abi"
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||||
"github.com/ethereum/go-ethereum/core"
|
"github.com/ethereum/go-ethereum/core"
|
||||||
"github.com/ethereum/go-ethereum/core/vm"
|
"github.com/ethereum/go-ethereum/core/vm"
|
||||||
|
|
@ -33,6 +34,11 @@ type revertError struct {
|
||||||
reason string // revert reason hex encoded
|
reason string // revert reason hex encoded
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type txSyncTimeoutError struct {
|
||||||
|
msg string
|
||||||
|
hash common.Hash
|
||||||
|
}
|
||||||
|
|
||||||
// ErrorCode returns the JSON error code for a revert.
|
// ErrorCode returns the JSON error code for a revert.
|
||||||
// See: https://ethereum.org/en/developers/docs/apis/json-rpc/#error-codes
|
// See: https://ethereum.org/en/developers/docs/apis/json-rpc/#error-codes
|
||||||
func (e *revertError) ErrorCode() int {
|
func (e *revertError) ErrorCode() int {
|
||||||
|
|
@ -108,6 +114,7 @@ const (
|
||||||
errCodeInvalidParams = -32602
|
errCodeInvalidParams = -32602
|
||||||
errCodeReverted = -32000
|
errCodeReverted = -32000
|
||||||
errCodeVMError = -32015
|
errCodeVMError = -32015
|
||||||
|
errCodeTxSyncTimeout = 4
|
||||||
)
|
)
|
||||||
|
|
||||||
func txValidationError(err error) *invalidTxError {
|
func txValidationError(err error) *invalidTxError {
|
||||||
|
|
@ -168,3 +175,7 @@ type blockGasLimitReachedError struct{ message string }
|
||||||
|
|
||||||
func (e *blockGasLimitReachedError) Error() string { return e.message }
|
func (e *blockGasLimitReachedError) Error() string { return e.message }
|
||||||
func (e *blockGasLimitReachedError) ErrorCode() int { return errCodeBlockGasLimitReached }
|
func (e *blockGasLimitReachedError) ErrorCode() int { return errCodeBlockGasLimitReached }
|
||||||
|
|
||||||
|
func (e *txSyncTimeoutError) Error() string { return e.msg }
|
||||||
|
func (e *txSyncTimeoutError) ErrorCode() int { return errCodeTxSyncTimeout }
|
||||||
|
func (e *txSyncTimeoutError) ErrorData() interface{} { return e.hash.Hex() }
|
||||||
|
|
|
||||||
|
|
@ -500,7 +500,7 @@ func (w *gzipResponseWriter) init() {
|
||||||
hdr := w.resp.Header()
|
hdr := w.resp.Header()
|
||||||
length := hdr.Get("content-length")
|
length := hdr.Get("content-length")
|
||||||
if len(length) > 0 {
|
if len(length) > 0 {
|
||||||
if n, err := strconv.ParseUint(length, 10, 64); err != nil {
|
if n, err := strconv.ParseUint(length, 10, 64); err == nil {
|
||||||
w.hasLength = true
|
w.hasLength = true
|
||||||
w.contentLength = n
|
w.contentLength = n
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -153,6 +153,7 @@ type lookupIterator struct {
|
||||||
cancel func()
|
cancel func()
|
||||||
lookup *lookup
|
lookup *lookup
|
||||||
tabRefreshing <-chan struct{}
|
tabRefreshing <-chan struct{}
|
||||||
|
lastLookup time.Time
|
||||||
}
|
}
|
||||||
|
|
||||||
type lookupFunc func(ctx context.Context) *lookup
|
type lookupFunc func(ctx context.Context) *lookup
|
||||||
|
|
@ -185,6 +186,9 @@ func (it *lookupIterator) Next() bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if it.lookup == nil {
|
if it.lookup == nil {
|
||||||
|
// Ensure enough time has passed between lookup creations.
|
||||||
|
it.slowdown()
|
||||||
|
|
||||||
it.lookup = it.nextLookup(it.ctx)
|
it.lookup = it.nextLookup(it.ctx)
|
||||||
if it.lookup.empty() {
|
if it.lookup.empty() {
|
||||||
// If the lookup is empty right after creation, it means the local table
|
// If the lookup is empty right after creation, it means the local table
|
||||||
|
|
@ -235,6 +239,25 @@ func (it *lookupIterator) lookupFailed(tab *Table, timeout time.Duration) {
|
||||||
tab.waitForNodes(tout, 1)
|
tab.waitForNodes(tout, 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// slowdown applies a delay between creating lookups. This exists to prevent hot-spinning
|
||||||
|
// in some test environments where lookups don't yield any results.
|
||||||
|
func (it *lookupIterator) slowdown() {
|
||||||
|
const minInterval = 1 * time.Second
|
||||||
|
|
||||||
|
now := time.Now()
|
||||||
|
diff := now.Sub(it.lastLookup)
|
||||||
|
it.lastLookup = now
|
||||||
|
if diff > minInterval {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
wait := time.NewTimer(diff)
|
||||||
|
defer wait.Stop()
|
||||||
|
select {
|
||||||
|
case <-wait.C:
|
||||||
|
case <-it.ctx.Done():
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Close ends the iterator.
|
// Close ends the iterator.
|
||||||
func (it *lookupIterator) Close() {
|
func (it *lookupIterator) Close() {
|
||||||
it.cancel()
|
it.cancel()
|
||||||
|
|
|
||||||
|
|
@ -54,7 +54,6 @@ const (
|
||||||
bucketIPLimit, bucketSubnet = 2, 24 // at most 2 addresses from the same /24
|
bucketIPLimit, bucketSubnet = 2, 24 // at most 2 addresses from the same /24
|
||||||
tableIPLimit, tableSubnet = 10, 24
|
tableIPLimit, tableSubnet = 10, 24
|
||||||
|
|
||||||
seedMinTableTime = 5 * time.Minute
|
|
||||||
seedCount = 30
|
seedCount = 30
|
||||||
seedMaxAge = 5 * 24 * time.Hour
|
seedMaxAge = 5 * 24 * time.Hour
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -575,6 +575,13 @@ func startLocalhostV4(t *testing.T, cfg Config) *UDPv4 {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Wait for bootstrap to complete.
|
||||||
|
select {
|
||||||
|
case <-udp.tab.initDone:
|
||||||
|
case <-time.After(5 * time.Second):
|
||||||
|
t.Fatalf("timed out waiting for table initialization")
|
||||||
|
}
|
||||||
return udp
|
return udp
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ package enode
|
||||||
import (
|
import (
|
||||||
"crypto/ecdsa"
|
"crypto/ecdsa"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
|
"encoding/binary"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
@ -358,9 +359,10 @@ func ParseID(in string) (ID, error) {
|
||||||
// Returns -1 if a is closer to target, 1 if b is closer to target
|
// Returns -1 if a is closer to target, 1 if b is closer to target
|
||||||
// and 0 if they are equal.
|
// and 0 if they are equal.
|
||||||
func DistCmp(target, a, b ID) int {
|
func DistCmp(target, a, b ID) int {
|
||||||
for i := range target {
|
for i := 0; i < len(target); i += 8 {
|
||||||
da := a[i] ^ target[i]
|
tn := binary.BigEndian.Uint64(target[i : i+8])
|
||||||
db := b[i] ^ target[i]
|
da := tn ^ binary.BigEndian.Uint64(a[i:i+8])
|
||||||
|
db := tn ^ binary.BigEndian.Uint64(b[i:i+8])
|
||||||
if da > db {
|
if da > db {
|
||||||
return 1
|
return 1
|
||||||
} else if da < db {
|
} else if da < db {
|
||||||
|
|
@ -373,12 +375,14 @@ func DistCmp(target, a, b ID) int {
|
||||||
// LogDist returns the logarithmic distance between a and b, log2(a ^ b).
|
// LogDist returns the logarithmic distance between a and b, log2(a ^ b).
|
||||||
func LogDist(a, b ID) int {
|
func LogDist(a, b ID) int {
|
||||||
lz := 0
|
lz := 0
|
||||||
for i := range a {
|
for i := 0; i < len(a); i += 8 {
|
||||||
x := a[i] ^ b[i]
|
ai := binary.BigEndian.Uint64(a[i : i+8])
|
||||||
|
bi := binary.BigEndian.Uint64(b[i : i+8])
|
||||||
|
x := ai ^ bi
|
||||||
if x == 0 {
|
if x == 0 {
|
||||||
lz += 8
|
lz += 64
|
||||||
} else {
|
} else {
|
||||||
lz += bits.LeadingZeros8(x)
|
lz += bits.LeadingZeros64(x)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -368,6 +368,16 @@ func TestID_distcmpEqual(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func BenchmarkDistCmp(b *testing.B) {
|
||||||
|
base := ID{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}
|
||||||
|
aID := ID{15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0}
|
||||||
|
bID := ID{15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 1}
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
_ = DistCmp(base, aID, bID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestID_logdist(t *testing.T) {
|
func TestID_logdist(t *testing.T) {
|
||||||
logdistBig := func(a, b ID) int {
|
logdistBig := func(a, b ID) int {
|
||||||
abig, bbig := new(big.Int).SetBytes(a[:]), new(big.Int).SetBytes(b[:])
|
abig, bbig := new(big.Int).SetBytes(a[:]), new(big.Int).SetBytes(b[:])
|
||||||
|
|
@ -378,6 +388,28 @@ func TestID_logdist(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func makeIDs() (ID, ID) {
|
||||||
|
var a, b ID
|
||||||
|
size := len(a)
|
||||||
|
// last byte differs
|
||||||
|
for i := 0; i < size-1; i++ {
|
||||||
|
a[i] = 0xAA
|
||||||
|
b[i] = 0xAA
|
||||||
|
}
|
||||||
|
a[size-1] = 0xAA
|
||||||
|
b[size-1] = 0xAB
|
||||||
|
return a, b
|
||||||
|
}
|
||||||
|
|
||||||
|
// Benchmark LogDist
|
||||||
|
func BenchmarkLogDist(b *testing.B) {
|
||||||
|
aID, bID := makeIDs() // 256-bit ID
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
_ = LogDist(aID, bID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// The random tests is likely to miss the case where a and b are equal,
|
// The random tests is likely to miss the case where a and b are equal,
|
||||||
// this test checks it explicitly.
|
// this test checks it explicitly.
|
||||||
func TestID_logdistEqual(t *testing.T) {
|
func TestID_logdistEqual(t *testing.T) {
|
||||||
|
|
|
||||||
|
|
@ -273,7 +273,8 @@ func TestServerWebsocketReadLimit(t *testing.T) {
|
||||||
}
|
}
|
||||||
} else if !errors.Is(err, websocket.ErrReadLimit) &&
|
} else if !errors.Is(err, websocket.ErrReadLimit) &&
|
||||||
!strings.Contains(strings.ToLower(err.Error()), "1009") &&
|
!strings.Contains(strings.ToLower(err.Error()), "1009") &&
|
||||||
!strings.Contains(strings.ToLower(err.Error()), "message too big") {
|
!strings.Contains(strings.ToLower(err.Error()), "message too big") &&
|
||||||
|
!strings.Contains(strings.ToLower(err.Error()), "connection reset by peer") {
|
||||||
// Not the error we expect from exceeding the message size limit.
|
// Not the error we expect from exceeding the message size limit.
|
||||||
t.Fatalf("unexpected error for read limit violation: %v", err)
|
t.Fatalf("unexpected error for read limit violation: %v", err)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -211,7 +211,8 @@ func (t *TransitionTrie) UpdateStem(key []byte, values [][]byte) error {
|
||||||
func (t *TransitionTrie) Copy() *TransitionTrie {
|
func (t *TransitionTrie) Copy() *TransitionTrie {
|
||||||
return &TransitionTrie{
|
return &TransitionTrie{
|
||||||
overlay: t.overlay.Copy(),
|
overlay: t.overlay.Copy(),
|
||||||
base: t.base.Copy(),
|
// base in immutable, so there is no need to copy it
|
||||||
|
base: t.base,
|
||||||
storage: t.storage,
|
storage: t.storage,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -144,25 +144,17 @@ func (r *historyReader) readAccountMetadata(address common.Address, historyID ui
|
||||||
// readStorageMetadata resolves the storage slot metadata within the specified
|
// readStorageMetadata resolves the storage slot metadata within the specified
|
||||||
// state history.
|
// state history.
|
||||||
func (r *historyReader) readStorageMetadata(storageKey common.Hash, storageHash common.Hash, historyID uint64, slotOffset, slotNumber int) ([]byte, error) {
|
func (r *historyReader) readStorageMetadata(storageKey common.Hash, storageHash common.Hash, historyID uint64, slotOffset, slotNumber int) ([]byte, error) {
|
||||||
// TODO(rj493456442) optimize it with partial read
|
data, err := rawdb.ReadStateStorageIndex(r.freezer, historyID, slotIndexSize*slotOffset, slotIndexSize*slotNumber)
|
||||||
blob := rawdb.ReadStateStorageIndex(r.freezer, historyID)
|
if err != nil {
|
||||||
if len(blob) == 0 {
|
msg := fmt.Sprintf("id: %d, slot-offset: %d, slot-length: %d", historyID, slotOffset, slotNumber)
|
||||||
return nil, fmt.Errorf("storage index is truncated, historyID: %d", historyID)
|
return nil, fmt.Errorf("storage indices corrupted, %s, %w", msg, err)
|
||||||
}
|
}
|
||||||
if len(blob)%slotIndexSize != 0 {
|
|
||||||
return nil, fmt.Errorf("storage indices is corrupted, historyID: %d, size: %d", historyID, len(blob))
|
|
||||||
}
|
|
||||||
if slotIndexSize*(slotOffset+slotNumber) > len(blob) {
|
|
||||||
return nil, fmt.Errorf("storage indices is truncated, historyID: %d, size: %d, offset: %d, length: %d", historyID, len(blob), slotOffset, slotNumber)
|
|
||||||
}
|
|
||||||
subSlice := blob[slotIndexSize*slotOffset : slotIndexSize*(slotOffset+slotNumber)]
|
|
||||||
|
|
||||||
// TODO(rj493456442) get rid of the metadata resolution
|
// TODO(rj493456442) get rid of the metadata resolution
|
||||||
var (
|
var (
|
||||||
m meta
|
m meta
|
||||||
target common.Hash
|
target common.Hash
|
||||||
)
|
)
|
||||||
blob = rawdb.ReadStateHistoryMeta(r.freezer, historyID)
|
blob := rawdb.ReadStateHistoryMeta(r.freezer, historyID)
|
||||||
if err := m.decode(blob); err != nil {
|
if err := m.decode(blob); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -172,17 +164,17 @@ func (r *historyReader) readStorageMetadata(storageKey common.Hash, storageHash
|
||||||
target = storageKey
|
target = storageKey
|
||||||
}
|
}
|
||||||
pos := sort.Search(slotNumber, func(i int) bool {
|
pos := sort.Search(slotNumber, func(i int) bool {
|
||||||
slotID := subSlice[slotIndexSize*i : slotIndexSize*i+common.HashLength]
|
slotID := data[slotIndexSize*i : slotIndexSize*i+common.HashLength]
|
||||||
return bytes.Compare(slotID, target.Bytes()) >= 0
|
return bytes.Compare(slotID, target.Bytes()) >= 0
|
||||||
})
|
})
|
||||||
if pos == slotNumber {
|
if pos == slotNumber {
|
||||||
return nil, fmt.Errorf("storage metadata is not found, slot key: %#x, historyID: %d", storageKey, historyID)
|
return nil, fmt.Errorf("storage metadata is not found, slot key: %#x, historyID: %d", storageKey, historyID)
|
||||||
}
|
}
|
||||||
offset := slotIndexSize * pos
|
offset := slotIndexSize * pos
|
||||||
if target != common.BytesToHash(subSlice[offset:offset+common.HashLength]) {
|
if target != common.BytesToHash(data[offset:offset+common.HashLength]) {
|
||||||
return nil, fmt.Errorf("storage metadata is not found, slot key: %#x, historyID: %d", storageKey, historyID)
|
return nil, fmt.Errorf("storage metadata is not found, slot key: %#x, historyID: %d", storageKey, historyID)
|
||||||
}
|
}
|
||||||
return subSlice[offset : slotIndexSize*(pos+1)], nil
|
return data[offset : slotIndexSize*(pos+1)], nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// readAccount retrieves the account data from the specified state history.
|
// readAccount retrieves the account data from the specified state history.
|
||||||
|
|
@ -194,12 +186,11 @@ func (r *historyReader) readAccount(address common.Address, historyID uint64) ([
|
||||||
length := int(metadata[common.AddressLength]) // one byte for account data length
|
length := int(metadata[common.AddressLength]) // one byte for account data length
|
||||||
offset := int(binary.BigEndian.Uint32(metadata[common.AddressLength+1 : common.AddressLength+5])) // four bytes for the account data offset
|
offset := int(binary.BigEndian.Uint32(metadata[common.AddressLength+1 : common.AddressLength+5])) // four bytes for the account data offset
|
||||||
|
|
||||||
// TODO(rj493456442) optimize it with partial read
|
data, err := rawdb.ReadStateAccountHistory(r.freezer, historyID, offset, length)
|
||||||
data := rawdb.ReadStateAccountHistory(r.freezer, historyID)
|
if err != nil {
|
||||||
if len(data) < length+offset {
|
|
||||||
return nil, fmt.Errorf("account data is truncated, address: %#x, historyID: %d, size: %d, offset: %d, len: %d", address, historyID, len(data), offset, length)
|
return nil, fmt.Errorf("account data is truncated, address: %#x, historyID: %d, size: %d, offset: %d, len: %d", address, historyID, len(data), offset, length)
|
||||||
}
|
}
|
||||||
return data[offset : offset+length], nil
|
return data, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// readStorage retrieves the storage slot data from the specified state history.
|
// readStorage retrieves the storage slot data from the specified state history.
|
||||||
|
|
@ -222,12 +213,11 @@ func (r *historyReader) readStorage(address common.Address, storageKey common.Ha
|
||||||
length := int(slotMetadata[common.HashLength]) // one byte for slot data length
|
length := int(slotMetadata[common.HashLength]) // one byte for slot data length
|
||||||
offset := int(binary.BigEndian.Uint32(slotMetadata[common.HashLength+1 : common.HashLength+5])) // four bytes for slot data offset
|
offset := int(binary.BigEndian.Uint32(slotMetadata[common.HashLength+1 : common.HashLength+5])) // four bytes for slot data offset
|
||||||
|
|
||||||
// TODO(rj493456442) optimize it with partial read
|
data, err := rawdb.ReadStateStorageHistory(r.freezer, historyID, offset, length)
|
||||||
data := rawdb.ReadStateStorageHistory(r.freezer, historyID)
|
if err != nil {
|
||||||
if len(data) < offset+length {
|
|
||||||
return nil, fmt.Errorf("storage data is truncated, address: %#x, key: %#x, historyID: %d, size: %d, offset: %d, len: %d", address, storageKey, historyID, len(data), offset, length)
|
return nil, fmt.Errorf("storage data is truncated, address: %#x, key: %#x, historyID: %d, size: %d, offset: %d, len: %d", address, storageKey, historyID, len(data), offset, length)
|
||||||
}
|
}
|
||||||
return data[offset : offset+length], nil
|
return data, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// read retrieves the state element data associated with the stateID.
|
// read retrieves the state element data associated with the stateID.
|
||||||
|
|
|
||||||
|
|
@ -161,7 +161,7 @@ func newTrienodeHistory(root common.Hash, parent common.Hash, block uint64, node
|
||||||
// sharedLen returns the length of the common prefix shared by a and b.
|
// sharedLen returns the length of the common prefix shared by a and b.
|
||||||
func sharedLen(a, b []byte) int {
|
func sharedLen(a, b []byte) int {
|
||||||
n := min(len(a), len(b))
|
n := min(len(a), len(b))
|
||||||
for i := 0; i < n; i++ {
|
for i := range n {
|
||||||
if a[i] != b[i] {
|
if a[i] != b[i] {
|
||||||
return i
|
return i
|
||||||
}
|
}
|
||||||
|
|
@ -295,7 +295,7 @@ func decodeHeader(data []byte) (*trienodeMetadata, []common.Hash, []uint32, []ui
|
||||||
keyOffsets = make([]uint32, 0, count)
|
keyOffsets = make([]uint32, 0, count)
|
||||||
valOffsets = make([]uint32, 0, count)
|
valOffsets = make([]uint32, 0, count)
|
||||||
)
|
)
|
||||||
for i := 0; i < count; i++ {
|
for i := range count {
|
||||||
n := trienodeMetadataSize + trienodeTrieHeaderSize*i
|
n := trienodeMetadataSize + trienodeTrieHeaderSize*i
|
||||||
owner := common.BytesToHash(data[n : n+common.HashLength])
|
owner := common.BytesToHash(data[n : n+common.HashLength])
|
||||||
if i != 0 && bytes.Compare(owner.Bytes(), owners[i-1].Bytes()) <= 0 {
|
if i != 0 && bytes.Compare(owner.Bytes(), owners[i-1].Bytes()) <= 0 {
|
||||||
|
|
@ -348,7 +348,7 @@ func decodeSingle(keySection []byte, onValue func([]byte, int, int) error) ([]st
|
||||||
if len(keySection) < int(8*nRestarts)+4 {
|
if len(keySection) < int(8*nRestarts)+4 {
|
||||||
return nil, fmt.Errorf("key section too short, restarts: %d, size: %d", nRestarts, len(keySection))
|
return nil, fmt.Errorf("key section too short, restarts: %d, size: %d", nRestarts, len(keySection))
|
||||||
}
|
}
|
||||||
for i := 0; i < int(nRestarts); i++ {
|
for i := range int(nRestarts) {
|
||||||
o := len(keySection) - 4 - (int(nRestarts)-i)*8
|
o := len(keySection) - 4 - (int(nRestarts)-i)*8
|
||||||
keyOffset := binary.BigEndian.Uint32(keySection[o : o+4])
|
keyOffset := binary.BigEndian.Uint32(keySection[o : o+4])
|
||||||
if i != 0 && keyOffset <= keyOffsets[i-1] {
|
if i != 0 && keyOffset <= keyOffsets[i-1] {
|
||||||
|
|
@ -469,7 +469,7 @@ func (h *trienodeHistory) decode(header []byte, keySection []byte, valueSection
|
||||||
h.nodeList = make(map[common.Hash][]string)
|
h.nodeList = make(map[common.Hash][]string)
|
||||||
h.nodes = make(map[common.Hash]map[string][]byte)
|
h.nodes = make(map[common.Hash]map[string][]byte)
|
||||||
|
|
||||||
for i := 0; i < len(owners); i++ {
|
for i := range len(owners) {
|
||||||
// Resolve the boundary of key section
|
// Resolve the boundary of key section
|
||||||
keyStart := keyOffsets[i]
|
keyStart := keyOffsets[i]
|
||||||
keyLimit := len(keySection)
|
keyLimit := len(keySection)
|
||||||
|
|
@ -524,7 +524,7 @@ func newSingleTrienodeHistoryReader(id uint64, reader ethdb.AncientReader, keyRa
|
||||||
}
|
}
|
||||||
keyStart := int(keyRange.start)
|
keyStart := int(keyRange.start)
|
||||||
keyLimit := int(keyRange.limit)
|
keyLimit := int(keyRange.limit)
|
||||||
if keyLimit == math.MaxUint32 {
|
if keyRange.limit == math.MaxUint32 {
|
||||||
keyLimit = len(keyData)
|
keyLimit = len(keyData)
|
||||||
}
|
}
|
||||||
if len(keyData) < keyStart || len(keyData) < keyLimit {
|
if len(keyData) < keyStart || len(keyData) < keyLimit {
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,6 @@ package version
|
||||||
const (
|
const (
|
||||||
Major = 1 // Major version component of the current release
|
Major = 1 // Major version component of the current release
|
||||||
Minor = 16 // Minor version component of the current release
|
Minor = 16 // Minor version component of the current release
|
||||||
Patch = 5 // Patch version component of the current release
|
Patch = 6 // Patch version component of the current release
|
||||||
Meta = "unstable" // Version metadata to append to the version string
|
Meta = "unstable" // Version metadata to append to the version string
|
||||||
)
|
)
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue