mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-26 06:36:43 +00:00
Merge tag 'v1.15.9' into release/geth-1.15.10-fh3.0
This commit is contained in:
commit
f63e35ddcc
70 changed files with 1509 additions and 580 deletions
19
.github/CODEOWNERS
vendored
19
.github/CODEOWNERS
vendored
|
|
@ -9,12 +9,11 @@ beacon/light/ @zsfelfoldi
|
||||||
beacon/merkle/ @zsfelfoldi
|
beacon/merkle/ @zsfelfoldi
|
||||||
beacon/types/ @zsfelfoldi @fjl
|
beacon/types/ @zsfelfoldi @fjl
|
||||||
beacon/params/ @zsfelfoldi @fjl
|
beacon/params/ @zsfelfoldi @fjl
|
||||||
cmd/clef/ @holiman
|
cmd/evm/ @MariusVanDerWijden @lightclient
|
||||||
cmd/evm/ @holiman @MariusVanDerWijden @lightclient
|
core/state/ @rjl493456442
|
||||||
core/state/ @rjl493456442 @holiman
|
crypto/ @gballet @jwasinger @fjl
|
||||||
crypto/ @gballet @jwasinger @holiman @fjl
|
core/ @rjl493456442
|
||||||
core/ @holiman @rjl493456442
|
eth/ @rjl493456442
|
||||||
eth/ @holiman @rjl493456442
|
|
||||||
eth/catalyst/ @MariusVanDerWijden @lightclient @fjl @jwasinger
|
eth/catalyst/ @MariusVanDerWijden @lightclient @fjl @jwasinger
|
||||||
eth/tracers/ @s1na
|
eth/tracers/ @s1na
|
||||||
ethclient/ @fjl
|
ethclient/ @fjl
|
||||||
|
|
@ -26,11 +25,9 @@ core/tracing/ @s1na
|
||||||
graphql/ @s1na
|
graphql/ @s1na
|
||||||
internal/ethapi/ @fjl @s1na @lightclient
|
internal/ethapi/ @fjl @s1na @lightclient
|
||||||
internal/era/ @lightclient
|
internal/era/ @lightclient
|
||||||
metrics/ @holiman
|
miner/ @MariusVanDerWijden @fjl @rjl493456442
|
||||||
miner/ @MariusVanDerWijden @holiman @fjl @rjl493456442
|
|
||||||
node/ @fjl
|
node/ @fjl
|
||||||
p2p/ @fjl @zsfelfoldi
|
p2p/ @fjl @zsfelfoldi
|
||||||
rlp/ @fjl
|
rlp/ @fjl
|
||||||
params/ @fjl @holiman @karalabe @gballet @rjl493456442 @zsfelfoldi
|
params/ @fjl @karalabe @gballet @rjl493456442 @zsfelfoldi
|
||||||
rpc/ @fjl @holiman
|
rpc/ @fjl
|
||||||
signer/ @holiman
|
|
||||||
|
|
|
||||||
|
|
@ -939,6 +939,7 @@ var bindTests = []struct {
|
||||||
if _, err := eventer.RaiseSimpleEvent(auth, common.Address{byte(j)}, [32]byte{byte(j)}, true, big.NewInt(int64(10*i+j))); err != nil {
|
if _, err := eventer.RaiseSimpleEvent(auth, common.Address{byte(j)}, [32]byte{byte(j)}, true, big.NewInt(int64(10*i+j))); err != nil {
|
||||||
t.Fatalf("block %d, event %d: raise failed: %v", i, j, err)
|
t.Fatalf("block %d, event %d: raise failed: %v", i, j, err)
|
||||||
}
|
}
|
||||||
|
time.Sleep(time.Millisecond * 200)
|
||||||
}
|
}
|
||||||
sim.Commit()
|
sim.Commit()
|
||||||
}
|
}
|
||||||
|
|
@ -1495,7 +1496,7 @@ var bindTests = []struct {
|
||||||
if n != 3 {
|
if n != 3 {
|
||||||
t.Fatalf("Invalid bar0 event")
|
t.Fatalf("Invalid bar0 event")
|
||||||
}
|
}
|
||||||
case <-time.NewTimer(3 * time.Second).C:
|
case <-time.NewTimer(10 * time.Second).C:
|
||||||
t.Fatalf("Wait bar0 event timeout")
|
t.Fatalf("Wait bar0 event timeout")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1506,7 +1507,7 @@ var bindTests = []struct {
|
||||||
if n != 1 {
|
if n != 1 {
|
||||||
t.Fatalf("Invalid bar event")
|
t.Fatalf("Invalid bar event")
|
||||||
}
|
}
|
||||||
case <-time.NewTimer(3 * time.Second).C:
|
case <-time.NewTimer(10 * time.Second).C:
|
||||||
t.Fatalf("Wait bar event timeout")
|
t.Fatalf("Wait bar event timeout")
|
||||||
}
|
}
|
||||||
close(stopCh)
|
close(stopCh)
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
0xf5606a019f0f1006e9ec2070695045f4334450362a48da4c5965314510e0b4c3
|
0xd60e5310c5d52ced44cfb13be4e9f22a1e6a6dc56964c3cccd429182d26d72d0
|
||||||
|
|
@ -1 +1 @@
|
||||||
0x3c0cb4aa83beded1803d262664ba4392b1023f334d9cca02dc3a6925987ebe91
|
0x02f0bb348b0d45f95a9b7e2bb5705768ad06548876cee03d880a2c9dabb1ff88
|
||||||
|
|
@ -1 +1 @@
|
||||||
0xa8d56457aa414523d93659aef1f7409bbfb72ad75e94d917c8c0b1baa38153ef
|
0xa0dad451a230c01be6f2492980ec5bb412d8cf33351a75e8b172b5b84a5fd03a
|
||||||
|
|
@ -363,9 +363,13 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig,
|
||||||
return nil, nil, nil, NewError(ErrorEVM, fmt.Errorf("could not parse requests logs: %v", err))
|
return nil, nil, nil, NewError(ErrorEVM, fmt.Errorf("could not parse requests logs: %v", err))
|
||||||
}
|
}
|
||||||
// EIP-7002
|
// EIP-7002
|
||||||
core.ProcessWithdrawalQueue(&requests, evm)
|
if err := core.ProcessWithdrawalQueue(&requests, evm); err != nil {
|
||||||
|
return nil, nil, nil, NewError(ErrorEVM, fmt.Errorf("could not process withdrawal requests: %v", err))
|
||||||
|
}
|
||||||
// EIP-7251
|
// EIP-7251
|
||||||
core.ProcessConsolidationQueue(&requests, evm)
|
if err := core.ProcessConsolidationQueue(&requests, evm); err != nil {
|
||||||
|
return nil, nil, nil, NewError(ErrorEVM, fmt.Errorf("could not process consolidation requests: %v", err))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Commit block
|
// Commit block
|
||||||
|
|
|
||||||
|
|
@ -31,11 +31,11 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"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/history"
|
||||||
"github.com/ethereum/go-ethereum/core/rawdb"
|
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||||
"github.com/ethereum/go-ethereum/core/state"
|
"github.com/ethereum/go-ethereum/core/state"
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
"github.com/ethereum/go-ethereum/eth/ethconfig"
|
|
||||||
"github.com/ethereum/go-ethereum/ethdb"
|
"github.com/ethereum/go-ethereum/ethdb"
|
||||||
"github.com/ethereum/go-ethereum/internal/debug"
|
"github.com/ethereum/go-ethereum/internal/debug"
|
||||||
"github.com/ethereum/go-ethereum/internal/era"
|
"github.com/ethereum/go-ethereum/internal/era"
|
||||||
|
|
@ -625,7 +625,7 @@ func pruneHistory(ctx *cli.Context) error {
|
||||||
defer chain.Stop()
|
defer chain.Stop()
|
||||||
|
|
||||||
// Determine the prune point. This will be the first PoS block.
|
// Determine the prune point. This will be the first PoS block.
|
||||||
prunePoint, ok := ethconfig.HistoryPrunePoints[chain.Genesis().Hash()]
|
prunePoint, ok := history.PrunePoints[chain.Genesis().Hash()]
|
||||||
if !ok || prunePoint == nil {
|
if !ok || prunePoint == nil {
|
||||||
return errors.New("prune point not found")
|
return errors.New("prune point not found")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -100,7 +100,6 @@ var (
|
||||||
utils.LightNoSyncServeFlag, // deprecated
|
utils.LightNoSyncServeFlag, // deprecated
|
||||||
utils.EthRequiredBlocksFlag,
|
utils.EthRequiredBlocksFlag,
|
||||||
utils.LegacyWhitelistFlag, // deprecated
|
utils.LegacyWhitelistFlag, // deprecated
|
||||||
utils.BloomFilterSizeFlag,
|
|
||||||
utils.CacheFlag,
|
utils.CacheFlag,
|
||||||
utils.CacheDatabaseFlag,
|
utils.CacheDatabaseFlag,
|
||||||
utils.CacheTrieFlag,
|
utils.CacheTrieFlag,
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"slices"
|
"slices"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/eth/ethconfig"
|
"github.com/ethereum/go-ethereum/core/history"
|
||||||
"github.com/ethereum/go-ethereum/internal/flags"
|
"github.com/ethereum/go-ethereum/internal/flags"
|
||||||
"github.com/ethereum/go-ethereum/internal/utesting"
|
"github.com/ethereum/go-ethereum/internal/utesting"
|
||||||
"github.com/ethereum/go-ethereum/log"
|
"github.com/ethereum/go-ethereum/log"
|
||||||
|
|
@ -124,13 +124,13 @@ func testConfigFromCLI(ctx *cli.Context) (cfg testConfig) {
|
||||||
cfg.filterQueryFile = "queries/filter_queries_mainnet.json"
|
cfg.filterQueryFile = "queries/filter_queries_mainnet.json"
|
||||||
cfg.historyTestFile = "queries/history_mainnet.json"
|
cfg.historyTestFile = "queries/history_mainnet.json"
|
||||||
cfg.historyPruneBlock = new(uint64)
|
cfg.historyPruneBlock = new(uint64)
|
||||||
*cfg.historyPruneBlock = ethconfig.HistoryPrunePoints[params.MainnetGenesisHash].BlockNumber
|
*cfg.historyPruneBlock = history.PrunePoints[params.MainnetGenesisHash].BlockNumber
|
||||||
case ctx.Bool(testSepoliaFlag.Name):
|
case ctx.Bool(testSepoliaFlag.Name):
|
||||||
cfg.fsys = builtinTestFiles
|
cfg.fsys = builtinTestFiles
|
||||||
cfg.filterQueryFile = "queries/filter_queries_sepolia.json"
|
cfg.filterQueryFile = "queries/filter_queries_sepolia.json"
|
||||||
cfg.historyTestFile = "queries/history_sepolia.json"
|
cfg.historyTestFile = "queries/history_sepolia.json"
|
||||||
cfg.historyPruneBlock = new(uint64)
|
cfg.historyPruneBlock = new(uint64)
|
||||||
*cfg.historyPruneBlock = ethconfig.HistoryPrunePoints[params.SepoliaGenesisHash].BlockNumber
|
*cfg.historyPruneBlock = history.PrunePoints[params.SepoliaGenesisHash].BlockNumber
|
||||||
default:
|
default:
|
||||||
cfg.fsys = os.DirFS(".")
|
cfg.fsys = os.DirFS(".")
|
||||||
cfg.filterQueryFile = ctx.String(filterQueryFileFlag.Name)
|
cfg.filterQueryFile = ctx.String(filterQueryFileFlag.Name)
|
||||||
|
|
|
||||||
|
|
@ -50,8 +50,11 @@ func testHeaderVerification(t *testing.T, scheme string) {
|
||||||
headers[i] = block.Header()
|
headers[i] = block.Header()
|
||||||
}
|
}
|
||||||
// Run the header checker for blocks one-by-one, checking for both valid and invalid nonces
|
// Run the header checker for blocks one-by-one, checking for both valid and invalid nonces
|
||||||
chain, _ := NewBlockChain(rawdb.NewMemoryDatabase(), DefaultCacheConfigWithScheme(scheme), gspec, nil, ethash.NewFaker(), vm.Config{}, nil)
|
chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), DefaultCacheConfigWithScheme(scheme), gspec, nil, ethash.NewFaker(), vm.Config{}, nil)
|
||||||
defer chain.Stop()
|
defer chain.Stop()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
for i := 0; i < len(blocks); i++ {
|
for i := 0; i < len(blocks); i++ {
|
||||||
for j, valid := range []bool{true, false} {
|
for j, valid := range []bool{true, false} {
|
||||||
|
|
@ -163,8 +166,11 @@ func testHeaderVerificationForMerging(t *testing.T, isClique bool) {
|
||||||
postHeaders[i] = block.Header()
|
postHeaders[i] = block.Header()
|
||||||
}
|
}
|
||||||
// Run the header checker for blocks one-by-one, checking for both valid and invalid nonces
|
// Run the header checker for blocks one-by-one, checking for both valid and invalid nonces
|
||||||
chain, _ := NewBlockChain(rawdb.NewMemoryDatabase(), nil, gspec, nil, engine, vm.Config{}, nil)
|
chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), nil, gspec, nil, engine, vm.Config{}, nil)
|
||||||
defer chain.Stop()
|
defer chain.Stop()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
// Verify the blocks before the merging
|
// Verify the blocks before the merging
|
||||||
for i := 0; i < len(preBlocks); i++ {
|
for i := 0; i < len(preBlocks); i++ {
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"math"
|
||||||
"math/big"
|
"math/big"
|
||||||
"runtime"
|
"runtime"
|
||||||
"slices"
|
"slices"
|
||||||
|
|
@ -36,6 +37,7 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/common/prque"
|
"github.com/ethereum/go-ethereum/common/prque"
|
||||||
"github.com/ethereum/go-ethereum/consensus"
|
"github.com/ethereum/go-ethereum/consensus"
|
||||||
"github.com/ethereum/go-ethereum/consensus/misc/eip4844"
|
"github.com/ethereum/go-ethereum/consensus/misc/eip4844"
|
||||||
|
"github.com/ethereum/go-ethereum/core/history"
|
||||||
"github.com/ethereum/go-ethereum/core/rawdb"
|
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||||
"github.com/ethereum/go-ethereum/core/state"
|
"github.com/ethereum/go-ethereum/core/state"
|
||||||
"github.com/ethereum/go-ethereum/core/state/snapshot"
|
"github.com/ethereum/go-ethereum/core/state/snapshot"
|
||||||
|
|
@ -99,6 +101,10 @@ var (
|
||||||
errInvalidNewChain = errors.New("invalid new chain")
|
errInvalidNewChain = errors.New("invalid new chain")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
forkReadyInterval = 3 * time.Minute
|
||||||
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
bodyCacheLimit = 256
|
bodyCacheLimit = 256
|
||||||
blockCacheLimit = 256
|
blockCacheLimit = 256
|
||||||
|
|
@ -158,8 +164,7 @@ type CacheConfig struct {
|
||||||
|
|
||||||
// This defines the cutoff block for history expiry.
|
// This defines the cutoff block for history expiry.
|
||||||
// Blocks before this number may be unavailable in the chain database.
|
// Blocks before this number may be unavailable in the chain database.
|
||||||
HistoryPruningCutoffNumber uint64
|
ChainHistoryMode history.HistoryMode
|
||||||
HistoryPruningCutoffHash common.Hash
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// triedbConfig derives the configures for trie database.
|
// triedbConfig derives the configures for trie database.
|
||||||
|
|
@ -255,6 +260,7 @@ type BlockChain struct {
|
||||||
currentSnapBlock atomic.Pointer[types.Header] // Current head of snap-sync
|
currentSnapBlock atomic.Pointer[types.Header] // Current head of snap-sync
|
||||||
currentFinalBlock atomic.Pointer[types.Header] // Latest (consensus) finalized block
|
currentFinalBlock atomic.Pointer[types.Header] // Latest (consensus) finalized block
|
||||||
currentSafeBlock atomic.Pointer[types.Header] // Latest (consensus) safe block
|
currentSafeBlock atomic.Pointer[types.Header] // Latest (consensus) safe block
|
||||||
|
historyPrunePoint atomic.Pointer[history.PrunePoint]
|
||||||
|
|
||||||
bodyCache *lru.Cache[common.Hash, *types.Body]
|
bodyCache *lru.Cache[common.Hash, *types.Body]
|
||||||
bodyRLPCache *lru.Cache[common.Hash, rlp.RawValue]
|
bodyRLPCache *lru.Cache[common.Hash, rlp.RawValue]
|
||||||
|
|
@ -274,6 +280,8 @@ type BlockChain struct {
|
||||||
processor Processor // Block transaction processor interface
|
processor Processor // Block transaction processor interface
|
||||||
vmConfig vm.Config
|
vmConfig vm.Config
|
||||||
logger *tracing.Hooks
|
logger *tracing.Hooks
|
||||||
|
|
||||||
|
lastForkReadyAlert time.Time // Last time there was a fork readiness print out
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewBlockChain returns a fully initialised block chain using information
|
// NewBlockChain returns a fully initialised block chain using information
|
||||||
|
|
@ -513,19 +521,33 @@ func (bc *BlockChain) loadLastState() error {
|
||||||
log.Warn("Empty database, resetting chain")
|
log.Warn("Empty database, resetting chain")
|
||||||
return bc.Reset()
|
return bc.Reset()
|
||||||
}
|
}
|
||||||
// Make sure the entire head block is available
|
headHeader := bc.GetHeaderByHash(head)
|
||||||
headBlock := bc.GetBlockByHash(head)
|
if headHeader == nil {
|
||||||
|
// Corrupt or empty database, init from scratch
|
||||||
|
log.Warn("Head header missing, resetting chain", "hash", head)
|
||||||
|
return bc.Reset()
|
||||||
|
}
|
||||||
|
|
||||||
|
var headBlock *types.Block
|
||||||
|
if cmp := headHeader.Number.Cmp(new(big.Int)); cmp == 1 {
|
||||||
|
// Make sure the entire head block is available.
|
||||||
|
headBlock = bc.GetBlockByHash(head)
|
||||||
|
} else if cmp == 0 {
|
||||||
|
// On a pruned node the block body might not be available. But a pruned
|
||||||
|
// block should never be the head block. The only exception is when, as
|
||||||
|
// a last resort, chain is reset to genesis.
|
||||||
|
headBlock = bc.genesisBlock
|
||||||
|
}
|
||||||
if headBlock == nil {
|
if headBlock == nil {
|
||||||
// Corrupt or empty database, init from scratch
|
// Corrupt or empty database, init from scratch
|
||||||
log.Warn("Head block missing, resetting chain", "hash", head)
|
log.Warn("Head block missing, resetting chain", "hash", head)
|
||||||
return bc.Reset()
|
return bc.Reset()
|
||||||
}
|
}
|
||||||
// Everything seems to be fine, set as the head block
|
// Everything seems to be fine, set as the head block
|
||||||
bc.currentBlock.Store(headBlock.Header())
|
bc.currentBlock.Store(headHeader)
|
||||||
headBlockGauge.Update(int64(headBlock.NumberU64()))
|
headBlockGauge.Update(int64(headBlock.NumberU64()))
|
||||||
|
|
||||||
// Restore the last known head header
|
// Restore the last known head header
|
||||||
headHeader := headBlock.Header()
|
|
||||||
if head := rawdb.ReadHeadHeaderHash(bc.db); head != (common.Hash{}) {
|
if head := rawdb.ReadHeadHeaderHash(bc.db); head != (common.Hash{}) {
|
||||||
if header := bc.GetHeaderByHash(head); header != nil {
|
if header := bc.GetHeaderByHash(head); header != nil {
|
||||||
headHeader = header
|
headHeader = header
|
||||||
|
|
@ -533,6 +555,12 @@ func (bc *BlockChain) loadLastState() error {
|
||||||
}
|
}
|
||||||
bc.hc.SetCurrentHeader(headHeader)
|
bc.hc.SetCurrentHeader(headHeader)
|
||||||
|
|
||||||
|
// Initialize history pruning.
|
||||||
|
latest := max(headBlock.NumberU64(), headHeader.Number.Uint64())
|
||||||
|
if err := bc.initializeHistoryPruning(latest); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
// Restore the last known head snap block
|
// Restore the last known head snap block
|
||||||
bc.currentSnapBlock.Store(headBlock.Header())
|
bc.currentSnapBlock.Store(headBlock.Header())
|
||||||
headFastBlockGauge.Update(int64(headBlock.NumberU64()))
|
headFastBlockGauge.Update(int64(headBlock.NumberU64()))
|
||||||
|
|
@ -555,6 +583,7 @@ func (bc *BlockChain) loadLastState() error {
|
||||||
headSafeBlockGauge.Update(int64(block.NumberU64()))
|
headSafeBlockGauge.Update(int64(block.NumberU64()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Issue a status log for the user
|
// Issue a status log for the user
|
||||||
var (
|
var (
|
||||||
currentSnapBlock = bc.CurrentSnapBlock()
|
currentSnapBlock = bc.CurrentSnapBlock()
|
||||||
|
|
@ -573,9 +602,57 @@ func (bc *BlockChain) loadLastState() error {
|
||||||
if pivot := rawdb.ReadLastPivotNumber(bc.db); pivot != nil {
|
if pivot := rawdb.ReadLastPivotNumber(bc.db); pivot != nil {
|
||||||
log.Info("Loaded last snap-sync pivot marker", "number", *pivot)
|
log.Info("Loaded last snap-sync pivot marker", "number", *pivot)
|
||||||
}
|
}
|
||||||
|
if pruning := bc.historyPrunePoint.Load(); pruning != nil {
|
||||||
|
log.Info("Chain history is pruned", "earliest", pruning.BlockNumber, "hash", pruning.BlockHash)
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// initializeHistoryPruning sets bc.historyPrunePoint.
|
||||||
|
func (bc *BlockChain) initializeHistoryPruning(latest uint64) error {
|
||||||
|
freezerTail, _ := bc.db.Tail()
|
||||||
|
|
||||||
|
switch bc.cacheConfig.ChainHistoryMode {
|
||||||
|
case history.KeepAll:
|
||||||
|
if freezerTail == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
// The database was pruned somehow, so we need to figure out if it's a known
|
||||||
|
// configuration or an error.
|
||||||
|
predefinedPoint := history.PrunePoints[bc.genesisBlock.Hash()]
|
||||||
|
if predefinedPoint == nil || freezerTail != predefinedPoint.BlockNumber {
|
||||||
|
log.Error("Chain history database is pruned with unknown configuration", "tail", freezerTail)
|
||||||
|
return fmt.Errorf("unexpected database tail")
|
||||||
|
}
|
||||||
|
bc.historyPrunePoint.Store(predefinedPoint)
|
||||||
|
return nil
|
||||||
|
|
||||||
|
case history.KeepPostMerge:
|
||||||
|
if freezerTail == 0 && latest != 0 {
|
||||||
|
// This is the case where a user is trying to run with --history.chain
|
||||||
|
// postmerge directly on an existing DB. We could just trigger the pruning
|
||||||
|
// here, but it'd be a bit dangerous since they may not have intended this
|
||||||
|
// action to happen. So just tell them how to do it.
|
||||||
|
log.Error(fmt.Sprintf("Chain history mode is configured as %q, but database is not pruned.", bc.cacheConfig.ChainHistoryMode.String()))
|
||||||
|
log.Error(fmt.Sprintf("Run 'geth prune-history' to prune pre-merge history."))
|
||||||
|
return fmt.Errorf("history pruning requested via configuration")
|
||||||
|
}
|
||||||
|
predefinedPoint := history.PrunePoints[bc.genesisBlock.Hash()]
|
||||||
|
if predefinedPoint == nil {
|
||||||
|
log.Error("Chain history pruning is not supported for this network", "genesis", bc.genesisBlock.Hash())
|
||||||
|
return fmt.Errorf("history pruning requested for unknown network")
|
||||||
|
} else if freezerTail > 0 && freezerTail != predefinedPoint.BlockNumber {
|
||||||
|
log.Error("Chain history database is pruned to unknown block", "tail", freezerTail)
|
||||||
|
return fmt.Errorf("unexpected database tail")
|
||||||
|
}
|
||||||
|
bc.historyPrunePoint.Store(predefinedPoint)
|
||||||
|
return nil
|
||||||
|
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("invalid history mode: %d", bc.cacheConfig.ChainHistoryMode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// SetHead rewinds the local chain to a new head. Depending on whether the node
|
// SetHead rewinds the local chain to a new head. Depending on whether the node
|
||||||
// was snap synced or full synced and in which state, the method will try to
|
// was snap synced or full synced and in which state, the method will try to
|
||||||
// delete minimal data from disk whilst retaining chain consistency.
|
// delete minimal data from disk whilst retaining chain consistency.
|
||||||
|
|
@ -586,12 +663,16 @@ func (bc *BlockChain) SetHead(head uint64) error {
|
||||||
// Send chain head event to update the transaction pool
|
// Send chain head event to update the transaction pool
|
||||||
header := bc.CurrentBlock()
|
header := bc.CurrentBlock()
|
||||||
if block := bc.GetBlock(header.Hash(), header.Number.Uint64()); block == nil {
|
if block := bc.GetBlock(header.Hash(), header.Number.Uint64()); block == nil {
|
||||||
|
// In a pruned node the genesis block will not exist in the freezer.
|
||||||
|
// It should not happen that we set head to any other pruned block.
|
||||||
|
if header.Number.Uint64() > 0 {
|
||||||
// This should never happen. In practice, previously currentBlock
|
// This should never happen. In practice, previously currentBlock
|
||||||
// contained the entire block whereas now only a "marker", so there
|
// contained the entire block whereas now only a "marker", so there
|
||||||
// is an ever so slight chance for a race we should handle.
|
// is an ever so slight chance for a race we should handle.
|
||||||
log.Error("Current block not found in database", "block", header.Number, "hash", header.Hash())
|
log.Error("Current block not found in database", "block", header.Number, "hash", header.Hash())
|
||||||
return fmt.Errorf("current block missing: #%d [%x..]", header.Number, header.Hash().Bytes()[:4])
|
return fmt.Errorf("current block missing: #%d [%x..]", header.Number, header.Hash().Bytes()[:4])
|
||||||
}
|
}
|
||||||
|
}
|
||||||
bc.chainHeadFeed.Send(ChainHeadEvent{Header: header})
|
bc.chainHeadFeed.Send(ChainHeadEvent{Header: header})
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
@ -607,12 +688,16 @@ func (bc *BlockChain) SetHeadWithTimestamp(timestamp uint64) error {
|
||||||
// Send chain head event to update the transaction pool
|
// Send chain head event to update the transaction pool
|
||||||
header := bc.CurrentBlock()
|
header := bc.CurrentBlock()
|
||||||
if block := bc.GetBlock(header.Hash(), header.Number.Uint64()); block == nil {
|
if block := bc.GetBlock(header.Hash(), header.Number.Uint64()); block == nil {
|
||||||
|
// In a pruned node the genesis block will not exist in the freezer.
|
||||||
|
// It should not happen that we set head to any other pruned block.
|
||||||
|
if header.Number.Uint64() > 0 {
|
||||||
// This should never happen. In practice, previously currentBlock
|
// This should never happen. In practice, previously currentBlock
|
||||||
// contained the entire block whereas now only a "marker", so there
|
// contained the entire block whereas now only a "marker", so there
|
||||||
// is an ever so slight chance for a race we should handle.
|
// is an ever so slight chance for a race we should handle.
|
||||||
log.Error("Current block not found in database", "block", header.Number, "hash", header.Hash())
|
log.Error("Current block not found in database", "block", header.Number, "hash", header.Hash())
|
||||||
return fmt.Errorf("current block missing: #%d [%x..]", header.Number, header.Hash().Bytes()[:4])
|
return fmt.Errorf("current block missing: #%d [%x..]", header.Number, header.Hash().Bytes()[:4])
|
||||||
}
|
}
|
||||||
|
}
|
||||||
bc.chainHeadFeed.Send(ChainHeadEvent{Header: header})
|
bc.chainHeadFeed.Send(ChainHeadEvent{Header: header})
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
@ -1014,7 +1099,9 @@ func (bc *BlockChain) ResetWithGenesisBlock(genesis *types.Block) error {
|
||||||
bc.hc.SetCurrentHeader(bc.genesisBlock.Header())
|
bc.hc.SetCurrentHeader(bc.genesisBlock.Header())
|
||||||
bc.currentSnapBlock.Store(bc.genesisBlock.Header())
|
bc.currentSnapBlock.Store(bc.genesisBlock.Header())
|
||||||
headFastBlockGauge.Update(int64(bc.genesisBlock.NumberU64()))
|
headFastBlockGauge.Update(int64(bc.genesisBlock.NumberU64()))
|
||||||
return nil
|
|
||||||
|
// Reset history pruning status.
|
||||||
|
return bc.initializeHistoryPruning(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Export writes the active chain to the given writer.
|
// Export writes the active chain to the given writer.
|
||||||
|
|
@ -1804,6 +1891,9 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool, makeWitness
|
||||||
trieDiffNodes, trieBufNodes, _ := bc.triedb.Size()
|
trieDiffNodes, trieBufNodes, _ := bc.triedb.Size()
|
||||||
stats.report(chain, it.index, snapDiffItems, snapBufItems, trieDiffNodes, trieBufNodes, setHead)
|
stats.report(chain, it.index, snapDiffItems, snapBufItems, trieDiffNodes, trieBufNodes, setHead)
|
||||||
|
|
||||||
|
// Print confirmation that a future fork is scheduled, but not yet active.
|
||||||
|
bc.logForkReadiness(block)
|
||||||
|
|
||||||
if !setHead {
|
if !setHead {
|
||||||
// After merge we expect few side chains. Simply count
|
// After merge we expect few side chains. Simply count
|
||||||
// all blocks the CL gives us for GC processing time
|
// all blocks the CL gives us for GC processing time
|
||||||
|
|
@ -1837,6 +1927,7 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool, makeWitness
|
||||||
"root", block.Root())
|
"root", block.Root())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
stats.ignored += it.remaining()
|
stats.ignored += it.remaining()
|
||||||
return witness, it.index, err
|
return witness, it.index, err
|
||||||
}
|
}
|
||||||
|
|
@ -2434,6 +2525,23 @@ func (bc *BlockChain) reportBlock(block *types.Block, res *ProcessResult, err er
|
||||||
log.Error(summarizeBadBlock(block, receipts, bc.Config(), err))
|
log.Error(summarizeBadBlock(block, receipts, bc.Config(), err))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// logForkReadiness will write a log when a future fork is scheduled, but not
|
||||||
|
// active. This is useful so operators know their client is ready for the fork.
|
||||||
|
func (bc *BlockChain) logForkReadiness(block *types.Block) {
|
||||||
|
c := bc.Config()
|
||||||
|
current, last := c.LatestFork(block.Time()), c.LatestFork(math.MaxUint64)
|
||||||
|
t := c.Timestamp(last)
|
||||||
|
if t == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
at := time.Unix(int64(*t), 0)
|
||||||
|
if current < last && time.Now().After(bc.lastForkReadyAlert.Add(forkReadyInterval)) {
|
||||||
|
log.Info("Ready for fork activation", "fork", last, "date", at.Format(time.RFC822),
|
||||||
|
"remaining", time.Until(at).Round(time.Second), "timestamp", at.Unix())
|
||||||
|
bc.lastForkReadyAlert = time.Now()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// summarizeBadBlock returns a string summarizing the bad block and other
|
// summarizeBadBlock returns a string summarizing the bad block and other
|
||||||
// relevant information.
|
// relevant information.
|
||||||
func summarizeBadBlock(block *types.Block, receipts []*types.Receipt, config *params.ChainConfig, err error) string {
|
func summarizeBadBlock(block *types.Block, receipts []*types.Receipt, config *params.ChainConfig, err error) string {
|
||||||
|
|
|
||||||
|
|
@ -410,7 +410,11 @@ func (bc *BlockChain) TxIndexProgress() (TxIndexProgress, error) {
|
||||||
// HistoryPruningCutoff returns the configured history pruning point.
|
// HistoryPruningCutoff returns the configured history pruning point.
|
||||||
// Blocks before this might not be available in the database.
|
// Blocks before this might not be available in the database.
|
||||||
func (bc *BlockChain) HistoryPruningCutoff() (uint64, common.Hash) {
|
func (bc *BlockChain) HistoryPruningCutoff() (uint64, common.Hash) {
|
||||||
return bc.cacheConfig.HistoryPruningCutoffNumber, bc.cacheConfig.HistoryPruningCutoffHash
|
pt := bc.historyPrunePoint.Load()
|
||||||
|
if pt == nil {
|
||||||
|
return 0, bc.genesisBlock.Hash()
|
||||||
|
}
|
||||||
|
return pt.BlockNumber, pt.BlockHash
|
||||||
}
|
}
|
||||||
|
|
||||||
// TrieDB retrieves the low level trie database used for data storage.
|
// TrieDB retrieves the low level trie database used for data storage.
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,7 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/consensus"
|
"github.com/ethereum/go-ethereum/consensus"
|
||||||
"github.com/ethereum/go-ethereum/consensus/beacon"
|
"github.com/ethereum/go-ethereum/consensus/beacon"
|
||||||
"github.com/ethereum/go-ethereum/consensus/ethash"
|
"github.com/ethereum/go-ethereum/consensus/ethash"
|
||||||
|
"github.com/ethereum/go-ethereum/core/history"
|
||||||
"github.com/ethereum/go-ethereum/core/rawdb"
|
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||||
"github.com/ethereum/go-ethereum/core/state"
|
"github.com/ethereum/go-ethereum/core/state"
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
|
|
@ -4257,13 +4258,7 @@ func testChainReorgSnapSync(t *testing.T, ancientLimit uint64) {
|
||||||
// be persisted without the receipts and bodies; chain after should be persisted
|
// be persisted without the receipts and bodies; chain after should be persisted
|
||||||
// normally.
|
// normally.
|
||||||
func TestInsertChainWithCutoff(t *testing.T) {
|
func TestInsertChainWithCutoff(t *testing.T) {
|
||||||
testInsertChainWithCutoff(t, 32, 32) // cutoff = 32, ancientLimit = 32
|
const chainLength = 64
|
||||||
testInsertChainWithCutoff(t, 32, 64) // cutoff = 32, ancientLimit = 64 (entire chain in ancient)
|
|
||||||
testInsertChainWithCutoff(t, 32, 65) // cutoff = 32, ancientLimit = 65 (64 blocks in ancient, 1 block in live)
|
|
||||||
}
|
|
||||||
|
|
||||||
func testInsertChainWithCutoff(t *testing.T, cutoff uint64, ancientLimit uint64) {
|
|
||||||
// log.SetDefault(log.NewLogger(log.NewTerminalHandlerWithLevel(os.Stderr, log.LevelDebug, true)))
|
|
||||||
|
|
||||||
// Configure and generate a sample block chain
|
// Configure and generate a sample block chain
|
||||||
var (
|
var (
|
||||||
|
|
@ -4278,24 +4273,51 @@ func testInsertChainWithCutoff(t *testing.T, cutoff uint64, ancientLimit uint64)
|
||||||
signer = types.LatestSigner(gspec.Config)
|
signer = types.LatestSigner(gspec.Config)
|
||||||
engine = beacon.New(ethash.NewFaker())
|
engine = beacon.New(ethash.NewFaker())
|
||||||
)
|
)
|
||||||
_, blocks, receipts := GenerateChainWithGenesis(gspec, engine, int(2*cutoff), func(i int, block *BlockGen) {
|
_, blocks, receipts := GenerateChainWithGenesis(gspec, engine, chainLength, func(i int, block *BlockGen) {
|
||||||
block.SetCoinbase(common.Address{0x00})
|
block.SetCoinbase(common.Address{0x00})
|
||||||
|
|
||||||
tx, err := types.SignTx(types.NewTransaction(block.TxNonce(address), common.Address{0x00}, big.NewInt(1000), params.TxGas, block.header.BaseFee, nil), signer, key)
|
tx, err := types.SignTx(types.NewTransaction(block.TxNonce(address), common.Address{0x00}, big.NewInt(1000), params.TxGas, block.header.BaseFee, nil), signer, key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
block.AddTx(tx)
|
block.AddTx(tx)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Run the actual tests.
|
||||||
|
t.Run("cutoff-32/ancientLimit-32", func(t *testing.T) {
|
||||||
|
// cutoff = 32, ancientLimit = 32
|
||||||
|
testInsertChainWithCutoff(t, 32, 32, gspec, blocks, receipts)
|
||||||
|
})
|
||||||
|
t.Run("cutoff-32/ancientLimit-64", func(t *testing.T) {
|
||||||
|
// cutoff = 32, ancientLimit = 64 (entire chain in ancient)
|
||||||
|
testInsertChainWithCutoff(t, 32, 64, gspec, blocks, receipts)
|
||||||
|
})
|
||||||
|
t.Run("cutoff-32/ancientLimit-64", func(t *testing.T) {
|
||||||
|
// cutoff = 32, ancientLimit = 65 (64 blocks in ancient, 1 block in live)
|
||||||
|
testInsertChainWithCutoff(t, 32, 65, gspec, blocks, receipts)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func testInsertChainWithCutoff(t *testing.T, cutoff uint64, ancientLimit uint64, genesis *Genesis, blocks []*types.Block, receipts []types.Receipts) {
|
||||||
|
// log.SetDefault(log.NewLogger(log.NewTerminalHandlerWithLevel(os.Stderr, log.LevelDebug, true)))
|
||||||
|
|
||||||
|
// Add a known pruning point for the duration of the test.
|
||||||
|
ghash := genesis.ToBlock().Hash()
|
||||||
|
cutoffBlock := blocks[cutoff-1]
|
||||||
|
history.PrunePoints[ghash] = &history.PrunePoint{
|
||||||
|
BlockNumber: cutoffBlock.NumberU64(),
|
||||||
|
BlockHash: cutoffBlock.Hash(),
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
delete(history.PrunePoints, ghash)
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Enable pruning in cache config.
|
||||||
|
config := DefaultCacheConfigWithScheme(rawdb.PathScheme)
|
||||||
|
config.ChainHistoryMode = history.KeepPostMerge
|
||||||
|
|
||||||
db, _ := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), "", "", false)
|
db, _ := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), "", "", false)
|
||||||
defer db.Close()
|
defer db.Close()
|
||||||
|
chain, _ := NewBlockChain(db, DefaultCacheConfigWithScheme(rawdb.PathScheme), genesis, nil, beacon.New(ethash.NewFaker()), vm.Config{}, nil)
|
||||||
cutoffBlock := blocks[cutoff-1]
|
|
||||||
config := DefaultCacheConfigWithScheme(rawdb.PathScheme)
|
|
||||||
config.HistoryPruningCutoffNumber = cutoffBlock.NumberU64()
|
|
||||||
config.HistoryPruningCutoffHash = cutoffBlock.Hash()
|
|
||||||
|
|
||||||
chain, _ := NewBlockChain(db, DefaultCacheConfigWithScheme(rawdb.PathScheme), gspec, nil, beacon.New(ethash.NewFaker()), vm.Config{}, nil)
|
|
||||||
defer chain.Stop()
|
defer chain.Stop()
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|
@ -4326,8 +4348,8 @@ func testInsertChainWithCutoff(t *testing.T, cutoff uint64, ancientLimit uint64)
|
||||||
t.Errorf("head header #%d: header mismatch: want: %v, got: %v", headHeader.Number, blocks[len(blocks)-1].Hash(), headHeader.Hash())
|
t.Errorf("head header #%d: header mismatch: want: %v, got: %v", headHeader.Number, blocks[len(blocks)-1].Hash(), headHeader.Hash())
|
||||||
}
|
}
|
||||||
headBlock := chain.CurrentBlock()
|
headBlock := chain.CurrentBlock()
|
||||||
if headBlock.Hash() != gspec.ToBlock().Hash() {
|
if headBlock.Hash() != ghash {
|
||||||
t.Errorf("head block #%d: header mismatch: want: %v, got: %v", headBlock.Number, gspec.ToBlock().Hash(), headBlock.Hash())
|
t.Errorf("head block #%d: header mismatch: want: %v, got: %v", headBlock.Number, ghash, headBlock.Hash())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Iterate over all chain data components, and cross reference
|
// Iterate over all chain data components, and cross reference
|
||||||
|
|
|
||||||
|
|
@ -328,9 +328,13 @@ func (b *BlockGen) collectRequests(readonly bool) (requests [][]byte) {
|
||||||
blockContext := NewEVMBlockContext(b.header, b.cm, &b.header.Coinbase)
|
blockContext := NewEVMBlockContext(b.header, b.cm, &b.header.Coinbase)
|
||||||
evm := vm.NewEVM(blockContext, statedb, b.cm.config, vm.Config{})
|
evm := vm.NewEVM(blockContext, statedb, b.cm.config, vm.Config{})
|
||||||
// EIP-7002
|
// EIP-7002
|
||||||
ProcessWithdrawalQueue(&requests, evm)
|
if err := ProcessWithdrawalQueue(&requests, evm); err != nil {
|
||||||
|
panic(fmt.Sprintf("could not process withdrawal requests: %v", err))
|
||||||
|
}
|
||||||
// EIP-7251
|
// EIP-7251
|
||||||
ProcessConsolidationQueue(&requests, evm)
|
if err := ProcessConsolidationQueue(&requests, evm); err != nil {
|
||||||
|
panic(fmt.Sprintf("could not process consolidation requests: %v", err))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return requests
|
return requests
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,8 @@
|
||||||
package filtermaps
|
package filtermaps
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"sync"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
"github.com/ethereum/go-ethereum/log"
|
"github.com/ethereum/go-ethereum/log"
|
||||||
|
|
@ -39,6 +41,7 @@ type blockchain interface {
|
||||||
// of the underlying blockchain, it should only possess the block headers
|
// of the underlying blockchain, it should only possess the block headers
|
||||||
// and receipts up until the expected chain view head.
|
// and receipts up until the expected chain view head.
|
||||||
type ChainView struct {
|
type ChainView struct {
|
||||||
|
lock sync.Mutex
|
||||||
chain blockchain
|
chain blockchain
|
||||||
headNumber uint64
|
headNumber uint64
|
||||||
hashes []common.Hash // block hashes starting backwards from headNumber until first canonical hash
|
hashes []common.Hash // block hashes starting backwards from headNumber until first canonical hash
|
||||||
|
|
@ -55,47 +58,75 @@ func NewChainView(chain blockchain, number uint64, hash common.Hash) *ChainView
|
||||||
return cv
|
return cv
|
||||||
}
|
}
|
||||||
|
|
||||||
// getBlockHash returns the block hash belonging to the given block number.
|
// HeadNumber returns the head block number of the chain view.
|
||||||
|
func (cv *ChainView) HeadNumber() uint64 {
|
||||||
|
return cv.headNumber
|
||||||
|
}
|
||||||
|
|
||||||
|
// BlockHash returns the block hash belonging to the given block number.
|
||||||
// Note that the hash of the head block is not returned because ChainView might
|
// Note that the hash of the head block is not returned because ChainView might
|
||||||
// represent a view where the head block is currently being created.
|
// represent a view where the head block is currently being created.
|
||||||
func (cv *ChainView) getBlockHash(number uint64) common.Hash {
|
func (cv *ChainView) BlockHash(number uint64) common.Hash {
|
||||||
if number >= cv.headNumber {
|
cv.lock.Lock()
|
||||||
|
defer cv.lock.Unlock()
|
||||||
|
|
||||||
|
if number > cv.headNumber {
|
||||||
panic("invalid block number")
|
panic("invalid block number")
|
||||||
}
|
}
|
||||||
return cv.blockHash(number)
|
return cv.blockHash(number)
|
||||||
}
|
}
|
||||||
|
|
||||||
// getBlockId returns the unique block id belonging to the given block number.
|
// BlockId returns the unique block id belonging to the given block number.
|
||||||
// Note that it is currently equal to the block hash. In the future it might
|
// Note that it is currently equal to the block hash. In the future it might
|
||||||
// be a different id for future blocks if the log index root becomes part of
|
// be a different id for future blocks if the log index root becomes part of
|
||||||
// consensus and therefore rendering the index with the new head will happen
|
// consensus and therefore rendering the index with the new head will happen
|
||||||
// before the hash of that new head is available.
|
// before the hash of that new head is available.
|
||||||
func (cv *ChainView) getBlockId(number uint64) common.Hash {
|
func (cv *ChainView) BlockId(number uint64) common.Hash {
|
||||||
|
cv.lock.Lock()
|
||||||
|
defer cv.lock.Unlock()
|
||||||
|
|
||||||
if number > cv.headNumber {
|
if number > cv.headNumber {
|
||||||
panic("invalid block number")
|
panic("invalid block number")
|
||||||
}
|
}
|
||||||
return cv.blockHash(number)
|
return cv.blockHash(number)
|
||||||
}
|
}
|
||||||
|
|
||||||
// getReceipts returns the set of receipts belonging to the block at the given
|
// Header returns the block header at the given block number.
|
||||||
// block number.
|
func (cv *ChainView) Header(number uint64) *types.Header {
|
||||||
func (cv *ChainView) getReceipts(number uint64) types.Receipts {
|
return cv.chain.GetHeader(cv.BlockHash(number), number)
|
||||||
if number > cv.headNumber {
|
|
||||||
panic("invalid block number")
|
|
||||||
}
|
}
|
||||||
blockHash := cv.blockHash(number)
|
|
||||||
|
// Receipts returns the set of receipts belonging to the block at the given
|
||||||
|
// block number.
|
||||||
|
func (cv *ChainView) Receipts(number uint64) types.Receipts {
|
||||||
|
blockHash := cv.BlockHash(number)
|
||||||
if blockHash == (common.Hash{}) {
|
if blockHash == (common.Hash{}) {
|
||||||
log.Error("Chain view: block hash unavailable", "number", number, "head", cv.headNumber)
|
log.Error("Chain view: block hash unavailable", "number", number, "head", cv.headNumber)
|
||||||
}
|
}
|
||||||
return cv.chain.GetReceiptsByHash(blockHash)
|
return cv.chain.GetReceiptsByHash(blockHash)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SharedRange returns the block range shared by two chain views.
|
||||||
|
func (cv *ChainView) SharedRange(cv2 *ChainView) common.Range[uint64] {
|
||||||
|
cv.lock.Lock()
|
||||||
|
defer cv.lock.Unlock()
|
||||||
|
|
||||||
|
if cv == nil || cv2 == nil || !cv.extendNonCanonical() || !cv2.extendNonCanonical() {
|
||||||
|
return common.Range[uint64]{}
|
||||||
|
}
|
||||||
|
var sharedLen uint64
|
||||||
|
for n := min(cv.headNumber+1-uint64(len(cv.hashes)), cv2.headNumber+1-uint64(len(cv2.hashes))); n <= cv.headNumber && n <= cv2.headNumber && cv.blockHash(n) == cv2.blockHash(n); n++ {
|
||||||
|
sharedLen = n + 1
|
||||||
|
}
|
||||||
|
return common.NewRange(0, sharedLen)
|
||||||
|
}
|
||||||
|
|
||||||
// limitedView returns a new chain view that is a truncated version of the parent view.
|
// limitedView returns a new chain view that is a truncated version of the parent view.
|
||||||
func (cv *ChainView) limitedView(newHead uint64) *ChainView {
|
func (cv *ChainView) limitedView(newHead uint64) *ChainView {
|
||||||
if newHead >= cv.headNumber {
|
if newHead >= cv.headNumber {
|
||||||
return cv
|
return cv
|
||||||
}
|
}
|
||||||
return NewChainView(cv.chain, newHead, cv.blockHash(newHead))
|
return NewChainView(cv.chain, newHead, cv.BlockHash(newHead))
|
||||||
}
|
}
|
||||||
|
|
||||||
// equalViews returns true if the two chain views are equivalent.
|
// equalViews returns true if the two chain views are equivalent.
|
||||||
|
|
@ -103,7 +134,7 @@ func equalViews(cv1, cv2 *ChainView) bool {
|
||||||
if cv1 == nil || cv2 == nil {
|
if cv1 == nil || cv2 == nil {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return cv1.headNumber == cv2.headNumber && cv1.getBlockId(cv1.headNumber) == cv2.getBlockId(cv2.headNumber)
|
return cv1.headNumber == cv2.headNumber && cv1.BlockId(cv1.headNumber) == cv2.BlockId(cv2.headNumber)
|
||||||
}
|
}
|
||||||
|
|
||||||
// matchViews returns true if the two chain views are equivalent up until the
|
// matchViews returns true if the two chain views are equivalent up until the
|
||||||
|
|
@ -117,9 +148,9 @@ func matchViews(cv1, cv2 *ChainView, number uint64) bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if number == cv1.headNumber || number == cv2.headNumber {
|
if number == cv1.headNumber || number == cv2.headNumber {
|
||||||
return cv1.getBlockId(number) == cv2.getBlockId(number)
|
return cv1.BlockId(number) == cv2.BlockId(number)
|
||||||
}
|
}
|
||||||
return cv1.getBlockHash(number) == cv2.getBlockHash(number)
|
return cv1.BlockHash(number) == cv2.BlockHash(number)
|
||||||
}
|
}
|
||||||
|
|
||||||
// extendNonCanonical checks whether the previously known reverse list of head
|
// extendNonCanonical checks whether the previously known reverse list of head
|
||||||
|
|
|
||||||
|
|
@ -17,5 +17,6 @@
|
||||||
{"blockNumber": 3101669, "blockId": "0xa6e66a18fed64d33178c7088f273c404be597662c34f6e6884cf2e24f3ea4ace", "firstIndex": 1073741353},
|
{"blockNumber": 3101669, "blockId": "0xa6e66a18fed64d33178c7088f273c404be597662c34f6e6884cf2e24f3ea4ace", "firstIndex": 1073741353},
|
||||||
{"blockNumber": 3221725, "blockId": "0xe771f897dece48b1583cc1d1d10de8015da57407eb1fdf239fdbe46eaab85143", "firstIndex": 1140850137},
|
{"blockNumber": 3221725, "blockId": "0xe771f897dece48b1583cc1d1d10de8015da57407eb1fdf239fdbe46eaab85143", "firstIndex": 1140850137},
|
||||||
{"blockNumber": 3357164, "blockId": "0x6252d0aa54c79623b0680069c88d7b5c47983f0d5c4845b6c811b8d9b5e8ff3c", "firstIndex": 1207959453},
|
{"blockNumber": 3357164, "blockId": "0x6252d0aa54c79623b0680069c88d7b5c47983f0d5c4845b6c811b8d9b5e8ff3c", "firstIndex": 1207959453},
|
||||||
{"blockNumber": 3447019, "blockId": "0xeb7d585e1e063f3cc05ed399fbf6c2df63c271f62f030acb804e9fb1e74b6dc1", "firstIndex": 1275067542}
|
{"blockNumber": 3447019, "blockId": "0xeb7d585e1e063f3cc05ed399fbf6c2df63c271f62f030acb804e9fb1e74b6dc1", "firstIndex": 1275067542},
|
||||||
|
{"blockNumber": 3546397, "blockId": "0xdabdef7defa4281180a57c5af121877b82274f15ccf074ea0096146f4c246df2", "firstIndex": 1342176778}
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -260,5 +260,12 @@
|
||||||
{"blockNumber": 21959188, "blockId": "0x6b9c482cc4822af2ad62a359b923062556ab6a046d1a39a8243b764848690601", "firstIndex": 17381193886},
|
{"blockNumber": 21959188, "blockId": "0x6b9c482cc4822af2ad62a359b923062556ab6a046d1a39a8243b764848690601", "firstIndex": 17381193886},
|
||||||
{"blockNumber": 21994911, "blockId": "0x0d99ef9dd42dd1c62fc5249eb4f618e7672ab93fa0ba7545c77360371cf972e5", "firstIndex": 17448302795},
|
{"blockNumber": 21994911, "blockId": "0x0d99ef9dd42dd1c62fc5249eb4f618e7672ab93fa0ba7545c77360371cf972e5", "firstIndex": 17448302795},
|
||||||
{"blockNumber": 22026007, "blockId": "0xf7cdc7f6694f2c2ed31fa8a607f65cfa59d0dd7d7424ab5c017f959ae2982c71", "firstIndex": 17515413036},
|
{"blockNumber": 22026007, "blockId": "0xf7cdc7f6694f2c2ed31fa8a607f65cfa59d0dd7d7424ab5c017f959ae2982c71", "firstIndex": 17515413036},
|
||||||
{"blockNumber": 22059890, "blockId": "0x82b768a0dddefda2eefd3a33596ea2be075312f1dd4b01f6b0d377faca2af98b", "firstIndex": 17582521768}
|
{"blockNumber": 22059890, "blockId": "0x82b768a0dddefda2eefd3a33596ea2be075312f1dd4b01f6b0d377faca2af98b", "firstIndex": 17582521768},
|
||||||
|
{"blockNumber": 22090784, "blockId": "0xf97c2eaf9a550360ac24000c0ff17ffa388a2bdd6f73f2f36718e332edfa107a", "firstIndex": 17649630983},
|
||||||
|
{"blockNumber": 22121157, "blockId": "0xa790025235db782e899f23d8b09663ec2d74ec149e4125d62989f98829b08e2d", "firstIndex": 17716724973},
|
||||||
|
{"blockNumber": 22148056, "blockId": "0xbe25ac4f1bdd89a7db5782bf1157e6c4378d80220d67a029397853ef16cd1a4c", "firstIndex": 17783838366},
|
||||||
|
{"blockNumber": 22168652, "blockId": "0x6ae43618c915e636794e2cc2d75dde9992766881c7405fe6479c045ed4bee57e", "firstIndex": 17850956277},
|
||||||
|
{"blockNumber": 22190975, "blockId": "0x9437121647899a4b7b84d67fbea7cc6ff967481c2eab4328ccd86e2cefe19420", "firstIndex": 17918066140},
|
||||||
|
{"blockNumber": 22234357, "blockId": "0x036030830134f9224160d5a0b62da35ec7813dc8855d554bd22e9d38545243ed", "firstIndex": 17985175075},
|
||||||
|
{"blockNumber": 22276736, "blockId": "0x5ceb96d98aa1b4c1c2f2fa253ae9cdb1b04e0420c11bf795400e8762c0a1635c", "firstIndex": 18052284344}
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -64,5 +64,9 @@
|
||||||
{"blockNumber": 7730079, "blockId": "0x08e0d511a93e2a9c004b3456d77d687ae86247417ada1cdd1f85332a62dfed1d", "firstIndex": 4227846795},
|
{"blockNumber": 7730079, "blockId": "0x08e0d511a93e2a9c004b3456d77d687ae86247417ada1cdd1f85332a62dfed1d", "firstIndex": 4227846795},
|
||||||
{"blockNumber": 7777515, "blockId": "0xeba8faed2e12bb9ed5e7fad19dd82662f15f6f4f512a0d232eedf1e676712428", "firstIndex": 4294966082},
|
{"blockNumber": 7777515, "blockId": "0xeba8faed2e12bb9ed5e7fad19dd82662f15f6f4f512a0d232eedf1e676712428", "firstIndex": 4294966082},
|
||||||
{"blockNumber": 7828860, "blockId": "0x62c054bd24be351dc4777460ee922a75fdcea5c9830455cbac6fa29552719c85", "firstIndex": 4362076087},
|
{"blockNumber": 7828860, "blockId": "0x62c054bd24be351dc4777460ee922a75fdcea5c9830455cbac6fa29552719c85", "firstIndex": 4362076087},
|
||||||
{"blockNumber": 7869890, "blockId": "0x5664bcfa6151f798781e22bea4f9a2c796d097402350cb131e4e3c78e13e461a", "firstIndex": 4429183267}
|
{"blockNumber": 7869890, "blockId": "0x5664bcfa6151f798781e22bea4f9a2c796d097402350cb131e4e3c78e13e461a", "firstIndex": 4429183267},
|
||||||
|
{"blockNumber": 7911722, "blockId": "0x9a85e48e3135c97c51fc1786f2af0596c802e021b6c53cfca65a129cafcd23ed", "firstIndex": 4496287265},
|
||||||
|
{"blockNumber": 7960147, "blockId": "0xc9359cc76d7090e1c8a031108f0ab7a8935d971efd4325fe53612a1d99562f6f", "firstIndex": 4563402388},
|
||||||
|
{"blockNumber": 8030418, "blockId": "0x21867e68cd8327aed2da2601399d60f7f9e41dca4a4f2f9be982e5a2b9304a88", "firstIndex": 4630511616},
|
||||||
|
{"blockNumber": 8087701, "blockId": "0x0fa8c8d7549cc9a8d308262706fe248efe759f8b63511efb1e7f3926e9af2dcb", "firstIndex": 4697614758}
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -50,6 +50,7 @@ var (
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
databaseVersion = 1 // reindexed if database version does not match
|
||||||
cachedLastBlocks = 1000 // last block of map pointers
|
cachedLastBlocks = 1000 // last block of map pointers
|
||||||
cachedLvPointers = 1000 // first log value pointer of block pointers
|
cachedLvPointers = 1000 // first log value pointer of block pointers
|
||||||
cachedBaseRows = 100 // groups of base layer filter row data
|
cachedBaseRows = 100 // groups of base layer filter row data
|
||||||
|
|
@ -127,6 +128,7 @@ type FilterMaps struct {
|
||||||
|
|
||||||
// test hooks
|
// test hooks
|
||||||
testDisableSnapshots, testSnapshotUsed bool
|
testDisableSnapshots, testSnapshotUsed bool
|
||||||
|
testProcessEventsHook func()
|
||||||
}
|
}
|
||||||
|
|
||||||
// filterMap is a full or partial in-memory representation of a filter map where
|
// filterMap is a full or partial in-memory representation of a filter map where
|
||||||
|
|
@ -138,13 +140,25 @@ type FilterMaps struct {
|
||||||
// as transparent (uncached/unchanged).
|
// as transparent (uncached/unchanged).
|
||||||
type filterMap []FilterRow
|
type filterMap []FilterRow
|
||||||
|
|
||||||
// copy returns a copy of the given filter map. Note that the row slices are
|
// fastCopy returns a copy of the given filter map. Note that the row slices are
|
||||||
// copied but their contents are not. This permits extending the rows further
|
// copied but their contents are not. This permits appending to the rows further
|
||||||
// (which happens during map rendering) without affecting the validity of
|
// (which happens during map rendering) without affecting the validity of
|
||||||
// copies made for snapshots during rendering.
|
// copies made for snapshots during rendering.
|
||||||
func (fm filterMap) copy() filterMap {
|
// Appending to the rows of both the original map and the fast copy, or two fast
|
||||||
|
// copies of the same map would result in data corruption, therefore a fast copy
|
||||||
|
// should always be used in a read only way.
|
||||||
|
func (fm filterMap) fastCopy() filterMap {
|
||||||
|
return slices.Clone(fm)
|
||||||
|
}
|
||||||
|
|
||||||
|
// fullCopy returns a copy of the given filter map, also making a copy of each
|
||||||
|
// individual filter row, ensuring that a modification to either one will never
|
||||||
|
// affect the other.
|
||||||
|
func (fm filterMap) fullCopy() filterMap {
|
||||||
c := make(filterMap, len(fm))
|
c := make(filterMap, len(fm))
|
||||||
copy(c, fm)
|
for i, row := range fm {
|
||||||
|
c[i] = slices.Clone(row)
|
||||||
|
}
|
||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -207,8 +221,9 @@ type Config struct {
|
||||||
// NewFilterMaps creates a new FilterMaps and starts the indexer.
|
// NewFilterMaps creates a new FilterMaps and starts the indexer.
|
||||||
func NewFilterMaps(db ethdb.KeyValueStore, initView *ChainView, historyCutoff, finalBlock uint64, params Params, config Config) *FilterMaps {
|
func NewFilterMaps(db ethdb.KeyValueStore, initView *ChainView, historyCutoff, finalBlock uint64, params Params, config Config) *FilterMaps {
|
||||||
rs, initialized, err := rawdb.ReadFilterMapsRange(db)
|
rs, initialized, err := rawdb.ReadFilterMapsRange(db)
|
||||||
if err != nil {
|
if err != nil || rs.Version != databaseVersion {
|
||||||
log.Error("Error reading log index range", "error", err)
|
rs, initialized = rawdb.FilterMapsRange{}, false
|
||||||
|
log.Warn("Invalid log index database version; resetting log index")
|
||||||
}
|
}
|
||||||
params.deriveFields()
|
params.deriveFields()
|
||||||
f := &FilterMaps{
|
f := &FilterMaps{
|
||||||
|
|
@ -248,7 +263,7 @@ func NewFilterMaps(db ethdb.KeyValueStore, initView *ChainView, historyCutoff, f
|
||||||
f.targetView = initView
|
f.targetView = initView
|
||||||
if f.indexedRange.initialized {
|
if f.indexedRange.initialized {
|
||||||
f.indexedView = f.initChainView(f.targetView)
|
f.indexedView = f.initChainView(f.targetView)
|
||||||
f.indexedRange.headIndexed = f.indexedRange.blocks.AfterLast() == f.indexedView.headNumber+1
|
f.indexedRange.headIndexed = f.indexedRange.blocks.AfterLast() == f.indexedView.HeadNumber()+1
|
||||||
if !f.indexedRange.headIndexed {
|
if !f.indexedRange.headIndexed {
|
||||||
f.indexedRange.headDelimiter = 0
|
f.indexedRange.headDelimiter = 0
|
||||||
}
|
}
|
||||||
|
|
@ -299,7 +314,7 @@ func (f *FilterMaps) initChainView(chainView *ChainView) *ChainView {
|
||||||
log.Error("Could not initialize indexed chain view", "error", err)
|
log.Error("Could not initialize indexed chain view", "error", err)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
if lastBlockNumber <= chainView.headNumber && chainView.getBlockId(lastBlockNumber) == lastBlockId {
|
if lastBlockNumber <= chainView.HeadNumber() && chainView.BlockId(lastBlockNumber) == lastBlockId {
|
||||||
return chainView.limitedView(lastBlockNumber)
|
return chainView.limitedView(lastBlockNumber)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -356,7 +371,7 @@ func (f *FilterMaps) init() error {
|
||||||
for min < max {
|
for min < max {
|
||||||
mid := (min + max + 1) / 2
|
mid := (min + max + 1) / 2
|
||||||
cp := checkpointList[mid-1]
|
cp := checkpointList[mid-1]
|
||||||
if cp.BlockNumber <= f.targetView.headNumber && f.targetView.getBlockId(cp.BlockNumber) == cp.BlockId {
|
if cp.BlockNumber <= f.targetView.HeadNumber() && f.targetView.BlockId(cp.BlockNumber) == cp.BlockId {
|
||||||
min = mid
|
min = mid
|
||||||
} else {
|
} else {
|
||||||
max = mid - 1
|
max = mid - 1
|
||||||
|
|
@ -437,6 +452,7 @@ func (f *FilterMaps) setRange(batch ethdb.KeyValueWriter, newView *ChainView, ne
|
||||||
f.updateMatchersValidRange()
|
f.updateMatchersValidRange()
|
||||||
if newRange.initialized {
|
if newRange.initialized {
|
||||||
rs := rawdb.FilterMapsRange{
|
rs := rawdb.FilterMapsRange{
|
||||||
|
Version: databaseVersion,
|
||||||
HeadIndexed: newRange.headIndexed,
|
HeadIndexed: newRange.headIndexed,
|
||||||
HeadDelimiter: newRange.headDelimiter,
|
HeadDelimiter: newRange.headDelimiter,
|
||||||
BlocksFirst: newRange.blocks.First(),
|
BlocksFirst: newRange.blocks.First(),
|
||||||
|
|
@ -497,7 +513,7 @@ func (f *FilterMaps) getLogByLvIndex(lvIndex uint64) (*types.Log, error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// get block receipts
|
// get block receipts
|
||||||
receipts := f.indexedView.getReceipts(firstBlockNumber)
|
receipts := f.indexedView.Receipts(firstBlockNumber)
|
||||||
if receipts == nil {
|
if receipts == nil {
|
||||||
return nil, fmt.Errorf("failed to retrieve receipts for block %d containing searched log value index %d: %v", firstBlockNumber, lvIndex, err)
|
return nil, fmt.Errorf("failed to retrieve receipts for block %d containing searched log value index %d: %v", firstBlockNumber, lvIndex, err)
|
||||||
}
|
}
|
||||||
|
|
@ -558,7 +574,7 @@ func (f *FilterMaps) getFilterMapRow(mapIndex, rowIndex uint32, baseLayerOnly bo
|
||||||
}
|
}
|
||||||
f.baseRowsCache.Add(baseMapRowIndex, baseRows)
|
f.baseRowsCache.Add(baseMapRowIndex, baseRows)
|
||||||
}
|
}
|
||||||
baseRow := baseRows[mapIndex&(f.baseRowGroupLength-1)]
|
baseRow := slices.Clone(baseRows[mapIndex&(f.baseRowGroupLength-1)])
|
||||||
if baseLayerOnly {
|
if baseLayerOnly {
|
||||||
return baseRow, nil
|
return baseRow, nil
|
||||||
}
|
}
|
||||||
|
|
@ -595,7 +611,9 @@ func (f *FilterMaps) storeFilterMapRowsOfGroup(batch ethdb.Batch, mapIndices []u
|
||||||
if uint32(len(mapIndices)) != f.baseRowGroupLength { // skip base rows read if all rows are replaced
|
if uint32(len(mapIndices)) != f.baseRowGroupLength { // skip base rows read if all rows are replaced
|
||||||
var ok bool
|
var ok bool
|
||||||
baseRows, ok = f.baseRowsCache.Get(baseMapRowIndex)
|
baseRows, ok = f.baseRowsCache.Get(baseMapRowIndex)
|
||||||
if !ok {
|
if ok {
|
||||||
|
baseRows = slices.Clone(baseRows)
|
||||||
|
} else {
|
||||||
var err error
|
var err error
|
||||||
baseRows, err = rawdb.ReadFilterMapBaseRows(f.db, baseMapRowIndex, f.baseRowGroupLength, f.logMapWidth)
|
baseRows, err = rawdb.ReadFilterMapBaseRows(f.db, baseMapRowIndex, f.baseRowGroupLength, f.logMapWidth)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -641,7 +659,7 @@ func (f *FilterMaps) mapRowIndex(mapIndex, rowIndex uint32) uint64 {
|
||||||
// called from outside the indexerLoop goroutine.
|
// called from outside the indexerLoop goroutine.
|
||||||
func (f *FilterMaps) getBlockLvPointer(blockNumber uint64) (uint64, error) {
|
func (f *FilterMaps) getBlockLvPointer(blockNumber uint64) (uint64, error) {
|
||||||
if blockNumber >= f.indexedRange.blocks.AfterLast() && f.indexedRange.headIndexed {
|
if blockNumber >= f.indexedRange.blocks.AfterLast() && f.indexedRange.headIndexed {
|
||||||
return f.indexedRange.headDelimiter, nil
|
return f.indexedRange.headDelimiter + 1, nil
|
||||||
}
|
}
|
||||||
if lvPointer, ok := f.lvPointerCache.Get(blockNumber); ok {
|
if lvPointer, ok := f.lvPointerCache.Get(blockNumber); ok {
|
||||||
return lvPointer, nil
|
return lvPointer, nil
|
||||||
|
|
|
||||||
|
|
@ -44,7 +44,7 @@ func (f *FilterMaps) indexerLoop() {
|
||||||
|
|
||||||
for !f.stop {
|
for !f.stop {
|
||||||
if !f.indexedRange.initialized {
|
if !f.indexedRange.initialized {
|
||||||
if f.targetView.headNumber == 0 {
|
if f.targetView.HeadNumber() == 0 {
|
||||||
// initialize when chain head is available
|
// initialize when chain head is available
|
||||||
f.processSingleEvent(true)
|
f.processSingleEvent(true)
|
||||||
continue
|
continue
|
||||||
|
|
@ -165,6 +165,9 @@ func (f *FilterMaps) waitForNewHead() {
|
||||||
// processEvents processes all events, blocking only if a block processing is
|
// processEvents processes all events, blocking only if a block processing is
|
||||||
// happening and indexing should be suspended.
|
// happening and indexing should be suspended.
|
||||||
func (f *FilterMaps) processEvents() {
|
func (f *FilterMaps) processEvents() {
|
||||||
|
if f.testProcessEventsHook != nil {
|
||||||
|
f.testProcessEventsHook()
|
||||||
|
}
|
||||||
for f.processSingleEvent(f.blockProcessing) {
|
for f.processSingleEvent(f.blockProcessing) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -249,7 +252,7 @@ func (f *FilterMaps) tryIndexHead() error {
|
||||||
log.Info("Log index head rendering in progress",
|
log.Info("Log index head rendering in progress",
|
||||||
"first block", f.indexedRange.blocks.First(), "last block", f.indexedRange.blocks.Last(),
|
"first block", f.indexedRange.blocks.First(), "last block", f.indexedRange.blocks.Last(),
|
||||||
"processed", f.indexedRange.blocks.AfterLast()-f.ptrHeadIndex,
|
"processed", f.indexedRange.blocks.AfterLast()-f.ptrHeadIndex,
|
||||||
"remaining", f.indexedView.headNumber-f.indexedRange.blocks.Last(),
|
"remaining", f.indexedView.HeadNumber()-f.indexedRange.blocks.Last(),
|
||||||
"elapsed", common.PrettyDuration(time.Since(f.startedHeadIndexAt)))
|
"elapsed", common.PrettyDuration(time.Since(f.startedHeadIndexAt)))
|
||||||
f.loggedHeadIndex = true
|
f.loggedHeadIndex = true
|
||||||
f.lastLogHeadIndex = time.Now()
|
f.lastLogHeadIndex = time.Now()
|
||||||
|
|
@ -418,10 +421,10 @@ func (f *FilterMaps) needTailEpoch(epoch uint32) bool {
|
||||||
// tailTargetBlock returns the target value for the tail block number according
|
// tailTargetBlock returns the target value for the tail block number according
|
||||||
// to the log history parameter and the current index head.
|
// to the log history parameter and the current index head.
|
||||||
func (f *FilterMaps) tailTargetBlock() uint64 {
|
func (f *FilterMaps) tailTargetBlock() uint64 {
|
||||||
if f.history == 0 || f.indexedView.headNumber < f.history {
|
if f.history == 0 || f.indexedView.HeadNumber() < f.history {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
return f.indexedView.headNumber + 1 - f.history
|
return f.indexedView.HeadNumber() + 1 - f.history
|
||||||
}
|
}
|
||||||
|
|
||||||
// tailPartialBlocks returns the number of rendered blocks in the partially
|
// tailPartialBlocks returns the number of rendered blocks in the partially
|
||||||
|
|
|
||||||
|
|
@ -17,8 +17,10 @@
|
||||||
package filtermaps
|
package filtermaps
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
crand "crypto/rand"
|
crand "crypto/rand"
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
|
"encoding/binary"
|
||||||
"math/big"
|
"math/big"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
@ -31,6 +33,7 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
"github.com/ethereum/go-ethereum/ethdb"
|
"github.com/ethereum/go-ethereum/ethdb"
|
||||||
"github.com/ethereum/go-ethereum/params"
|
"github.com/ethereum/go-ethereum/params"
|
||||||
|
"github.com/ethereum/go-ethereum/rlp"
|
||||||
)
|
)
|
||||||
|
|
||||||
var testParams = Params{
|
var testParams = Params{
|
||||||
|
|
@ -104,6 +107,7 @@ func TestIndexerRandomRange(t *testing.T) {
|
||||||
fork, head = rand.Intn(len(forks)), rand.Intn(1001)
|
fork, head = rand.Intn(len(forks)), rand.Intn(1001)
|
||||||
ts.chain.setCanonicalChain(forks[fork][:head+1])
|
ts.chain.setCanonicalChain(forks[fork][:head+1])
|
||||||
case 2:
|
case 2:
|
||||||
|
checkSnapshot = false
|
||||||
if head < 1000 {
|
if head < 1000 {
|
||||||
checkSnapshot = !noHistory && head != 0 // no snapshot generated for block 0
|
checkSnapshot = !noHistory && head != 0 // no snapshot generated for block 0
|
||||||
// add blocks after the current head
|
// add blocks after the current head
|
||||||
|
|
@ -158,6 +162,115 @@ func TestIndexerRandomRange(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestIndexerMatcherView(t *testing.T) {
|
||||||
|
testIndexerMatcherView(t, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIndexerMatcherViewWithConcurrentRead(t *testing.T) {
|
||||||
|
testIndexerMatcherView(t, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testIndexerMatcherView(t *testing.T, concurrentRead bool) {
|
||||||
|
ts := newTestSetup(t)
|
||||||
|
defer ts.close()
|
||||||
|
|
||||||
|
forks := make([][]common.Hash, 20)
|
||||||
|
hashes := make([]common.Hash, 20)
|
||||||
|
ts.chain.addBlocks(100, 5, 2, 4, true)
|
||||||
|
ts.setHistory(0, false)
|
||||||
|
for i := range forks {
|
||||||
|
if i != 0 {
|
||||||
|
ts.chain.setHead(100 - i)
|
||||||
|
ts.chain.addBlocks(i, 5, 2, 4, true)
|
||||||
|
}
|
||||||
|
ts.fm.WaitIdle()
|
||||||
|
forks[i] = ts.chain.getCanonicalChain()
|
||||||
|
hashes[i] = ts.matcherViewHash()
|
||||||
|
}
|
||||||
|
fork := len(forks) - 1
|
||||||
|
for i := 0; i < 5000; i++ {
|
||||||
|
oldFork := fork
|
||||||
|
fork = rand.Intn(len(forks))
|
||||||
|
stopCh := make(chan chan struct{})
|
||||||
|
if concurrentRead {
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
ts.matcherViewHash()
|
||||||
|
select {
|
||||||
|
case ch := <-stopCh:
|
||||||
|
close(ch)
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
ts.chain.setCanonicalChain(forks[fork])
|
||||||
|
ts.fm.WaitIdle()
|
||||||
|
if concurrentRead {
|
||||||
|
ch := make(chan struct{})
|
||||||
|
stopCh <- ch
|
||||||
|
<-ch
|
||||||
|
}
|
||||||
|
hash := ts.matcherViewHash()
|
||||||
|
if hash != hashes[fork] {
|
||||||
|
t.Fatalf("Matcher view hash mismatch when switching from for %d to %d", oldFork, fork)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLogsByIndex(t *testing.T) {
|
||||||
|
ts := newTestSetup(t)
|
||||||
|
defer func() {
|
||||||
|
ts.fm.testProcessEventsHook = nil
|
||||||
|
ts.close()
|
||||||
|
}()
|
||||||
|
|
||||||
|
ts.chain.addBlocks(1000, 10, 3, 4, true)
|
||||||
|
ts.setHistory(0, false)
|
||||||
|
ts.fm.WaitIdle()
|
||||||
|
firstLog := make([]uint64, 1001) // first valid log position per block
|
||||||
|
lastLog := make([]uint64, 1001) // last valid log position per block
|
||||||
|
for i := uint64(0); i <= ts.fm.indexedRange.headDelimiter; i++ {
|
||||||
|
log, err := ts.fm.getLogByLvIndex(i)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Error getting log by index %d: %v", i, err)
|
||||||
|
}
|
||||||
|
if log != nil {
|
||||||
|
if firstLog[log.BlockNumber] == 0 {
|
||||||
|
firstLog[log.BlockNumber] = i
|
||||||
|
}
|
||||||
|
lastLog[log.BlockNumber] = i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var failed bool
|
||||||
|
ts.fm.testProcessEventsHook = func() {
|
||||||
|
if ts.fm.indexedRange.blocks.IsEmpty() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if lvi := firstLog[ts.fm.indexedRange.blocks.First()]; lvi != 0 {
|
||||||
|
log, err := ts.fm.getLogByLvIndex(lvi)
|
||||||
|
if log == nil || err != nil {
|
||||||
|
t.Errorf("Error getting first log of indexed block range: %v", err)
|
||||||
|
failed = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if lvi := lastLog[ts.fm.indexedRange.blocks.Last()]; lvi != 0 {
|
||||||
|
log, err := ts.fm.getLogByLvIndex(lvi)
|
||||||
|
if log == nil || err != nil {
|
||||||
|
t.Errorf("Error getting last log of indexed block range: %v", err)
|
||||||
|
failed = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
chain := ts.chain.getCanonicalChain()
|
||||||
|
for i := 0; i < 1000 && !failed; i++ {
|
||||||
|
head := rand.Intn(len(chain))
|
||||||
|
ts.chain.setCanonicalChain(chain[:head+1])
|
||||||
|
ts.fm.WaitIdle()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestIndexerCompareDb(t *testing.T) {
|
func TestIndexerCompareDb(t *testing.T) {
|
||||||
ts := newTestSetup(t)
|
ts := newTestSetup(t)
|
||||||
defer ts.close()
|
defer ts.close()
|
||||||
|
|
@ -291,6 +404,55 @@ func (ts *testSetup) fmDbHash() common.Hash {
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ts *testSetup) matcherViewHash() common.Hash {
|
||||||
|
mb := ts.fm.NewMatcherBackend()
|
||||||
|
defer mb.Close()
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
params := mb.GetParams()
|
||||||
|
hasher := sha256.New()
|
||||||
|
var headPtr uint64
|
||||||
|
for b := uint64(0); ; b++ {
|
||||||
|
lvptr, err := mb.GetBlockLvPointer(ctx, b)
|
||||||
|
if err != nil || (b > 0 && lvptr == headPtr) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
var enc [8]byte
|
||||||
|
binary.LittleEndian.PutUint64(enc[:], lvptr)
|
||||||
|
hasher.Write(enc[:])
|
||||||
|
headPtr = lvptr
|
||||||
|
}
|
||||||
|
headMap := uint32(headPtr >> params.logValuesPerMap)
|
||||||
|
var enc [12]byte
|
||||||
|
for r := uint32(0); r < params.mapHeight; r++ {
|
||||||
|
binary.LittleEndian.PutUint32(enc[:4], r)
|
||||||
|
for m := uint32(0); m <= headMap; m++ {
|
||||||
|
binary.LittleEndian.PutUint32(enc[4:8], m)
|
||||||
|
row, _ := mb.GetFilterMapRow(ctx, m, r, false)
|
||||||
|
for _, v := range row {
|
||||||
|
binary.LittleEndian.PutUint32(enc[8:], v)
|
||||||
|
hasher.Write(enc[:])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var hash common.Hash
|
||||||
|
hasher.Sum(hash[:0])
|
||||||
|
for i := 0; i < 50; i++ {
|
||||||
|
hasher.Reset()
|
||||||
|
hasher.Write(hash[:])
|
||||||
|
lvptr := binary.LittleEndian.Uint64(hash[:8]) % headPtr
|
||||||
|
if log, _ := mb.GetLogByLvIndex(ctx, lvptr); log != nil {
|
||||||
|
enc, err := rlp.EncodeToBytes(log)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
hasher.Write(enc)
|
||||||
|
}
|
||||||
|
hasher.Sum(hash[:0])
|
||||||
|
}
|
||||||
|
return hash
|
||||||
|
}
|
||||||
|
|
||||||
func (ts *testSetup) close() {
|
func (ts *testSetup) close() {
|
||||||
if ts.fm != nil {
|
if ts.fm != nil {
|
||||||
ts.fm.Stop()
|
ts.fm.Stop()
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"math"
|
"math"
|
||||||
|
"slices"
|
||||||
"sort"
|
"sort"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
|
@ -84,7 +85,7 @@ func (f *FilterMaps) renderMapsBefore(renderBefore uint32) (*mapRenderer, error)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if snapshot := f.lastCanonicalSnapshotBefore(renderBefore); snapshot != nil && snapshot.mapIndex >= nextMap {
|
if snapshot := f.lastCanonicalSnapshotOfMap(nextMap); snapshot != nil {
|
||||||
return f.renderMapsFromSnapshot(snapshot)
|
return f.renderMapsFromSnapshot(snapshot)
|
||||||
}
|
}
|
||||||
if nextMap >= renderBefore {
|
if nextMap >= renderBefore {
|
||||||
|
|
@ -97,17 +98,17 @@ func (f *FilterMaps) renderMapsBefore(renderBefore uint32) (*mapRenderer, error)
|
||||||
// snapshot made at a block boundary.
|
// snapshot made at a block boundary.
|
||||||
func (f *FilterMaps) renderMapsFromSnapshot(cp *renderedMap) (*mapRenderer, error) {
|
func (f *FilterMaps) renderMapsFromSnapshot(cp *renderedMap) (*mapRenderer, error) {
|
||||||
f.testSnapshotUsed = true
|
f.testSnapshotUsed = true
|
||||||
iter, err := f.newLogIteratorFromBlockDelimiter(cp.lastBlock)
|
iter, err := f.newLogIteratorFromBlockDelimiter(cp.lastBlock, cp.headDelimiter)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to create log iterator from block delimiter %d: %v", cp.lastBlock, err)
|
return nil, fmt.Errorf("failed to create log iterator from block delimiter %d: %v", cp.lastBlock, err)
|
||||||
}
|
}
|
||||||
return &mapRenderer{
|
return &mapRenderer{
|
||||||
f: f,
|
f: f,
|
||||||
currentMap: &renderedMap{
|
currentMap: &renderedMap{
|
||||||
filterMap: cp.filterMap.copy(),
|
filterMap: cp.filterMap.fullCopy(),
|
||||||
mapIndex: cp.mapIndex,
|
mapIndex: cp.mapIndex,
|
||||||
lastBlock: cp.lastBlock,
|
lastBlock: cp.lastBlock,
|
||||||
blockLvPtrs: cp.blockLvPtrs,
|
blockLvPtrs: slices.Clone(cp.blockLvPtrs),
|
||||||
},
|
},
|
||||||
finishedMaps: make(map[uint32]*renderedMap),
|
finishedMaps: make(map[uint32]*renderedMap),
|
||||||
finished: common.NewRange(cp.mapIndex, 0),
|
finished: common.NewRange(cp.mapIndex, 0),
|
||||||
|
|
@ -137,14 +138,15 @@ func (f *FilterMaps) renderMapsFromMapBoundary(firstMap, renderBefore uint32, st
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// lastCanonicalSnapshotBefore returns the latest cached snapshot that matches
|
// lastCanonicalSnapshotOfMap returns the latest cached snapshot of the given map
|
||||||
// the current targetView.
|
// that is also consistent with the current targetView.
|
||||||
func (f *FilterMaps) lastCanonicalSnapshotBefore(renderBefore uint32) *renderedMap {
|
func (f *FilterMaps) lastCanonicalSnapshotOfMap(mapIndex uint32) *renderedMap {
|
||||||
var best *renderedMap
|
var best *renderedMap
|
||||||
for _, blockNumber := range f.renderSnapshots.Keys() {
|
for _, blockNumber := range f.renderSnapshots.Keys() {
|
||||||
if cp, _ := f.renderSnapshots.Get(blockNumber); cp != nil && blockNumber < f.indexedRange.blocks.AfterLast() &&
|
if cp, _ := f.renderSnapshots.Get(blockNumber); cp != nil && blockNumber < f.indexedRange.blocks.AfterLast() &&
|
||||||
blockNumber <= f.targetView.headNumber && f.targetView.getBlockId(blockNumber) == cp.lastBlockId &&
|
blockNumber <= f.indexedView.HeadNumber() && f.indexedView.BlockId(blockNumber) == cp.lastBlockId &&
|
||||||
cp.mapIndex < renderBefore && (best == nil || blockNumber > best.lastBlock) {
|
blockNumber <= f.targetView.HeadNumber() && f.targetView.BlockId(blockNumber) == cp.lastBlockId &&
|
||||||
|
cp.mapIndex == mapIndex && (best == nil || blockNumber > best.lastBlock) {
|
||||||
best = cp
|
best = cp
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -171,10 +173,9 @@ func (f *FilterMaps) lastCanonicalMapBoundaryBefore(renderBefore uint32) (nextMa
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, 0, 0, fmt.Errorf("failed to retrieve last block of reverse iterated map %d: %v", mapIndex, err)
|
return 0, 0, 0, fmt.Errorf("failed to retrieve last block of reverse iterated map %d: %v", mapIndex, err)
|
||||||
}
|
}
|
||||||
if lastBlock >= f.indexedView.headNumber || lastBlock >= f.targetView.headNumber ||
|
if (f.indexedRange.headIndexed && mapIndex >= f.indexedRange.maps.Last()) ||
|
||||||
lastBlockId != f.targetView.getBlockId(lastBlock) {
|
lastBlock >= f.targetView.HeadNumber() || lastBlockId != f.targetView.BlockId(lastBlock) {
|
||||||
// map is not full or inconsistent with targetView; roll back
|
continue // map is not full or inconsistent with targetView; roll back
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
lvPtr, err := f.getBlockLvPointer(lastBlock)
|
lvPtr, err := f.getBlockLvPointer(lastBlock)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -244,10 +245,10 @@ func (f *FilterMaps) loadHeadSnapshot() error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
f.renderSnapshots.Add(f.indexedRange.blocks.Last(), &renderedMap{
|
f.renderSnapshots.Add(f.indexedRange.blocks.Last(), &renderedMap{
|
||||||
filterMap: fm,
|
filterMap: fm.fullCopy(),
|
||||||
mapIndex: f.indexedRange.maps.Last(),
|
mapIndex: f.indexedRange.maps.Last(),
|
||||||
lastBlock: f.indexedRange.blocks.Last(),
|
lastBlock: f.indexedRange.blocks.Last(),
|
||||||
lastBlockId: f.indexedView.getBlockId(f.indexedRange.blocks.Last()),
|
lastBlockId: f.indexedView.BlockId(f.indexedRange.blocks.Last()),
|
||||||
blockLvPtrs: lvPtrs,
|
blockLvPtrs: lvPtrs,
|
||||||
finished: true,
|
finished: true,
|
||||||
headDelimiter: f.indexedRange.headDelimiter,
|
headDelimiter: f.indexedRange.headDelimiter,
|
||||||
|
|
@ -257,11 +258,14 @@ func (f *FilterMaps) loadHeadSnapshot() error {
|
||||||
|
|
||||||
// makeSnapshot creates a snapshot of the current state of the rendered map.
|
// makeSnapshot creates a snapshot of the current state of the rendered map.
|
||||||
func (r *mapRenderer) makeSnapshot() {
|
func (r *mapRenderer) makeSnapshot() {
|
||||||
r.f.renderSnapshots.Add(r.iterator.blockNumber, &renderedMap{
|
if r.iterator.blockNumber != r.currentMap.lastBlock || r.iterator.chainView != r.f.targetView {
|
||||||
filterMap: r.currentMap.filterMap.copy(),
|
panic("iterator state inconsistent with current rendered map")
|
||||||
|
}
|
||||||
|
r.f.renderSnapshots.Add(r.currentMap.lastBlock, &renderedMap{
|
||||||
|
filterMap: r.currentMap.filterMap.fastCopy(),
|
||||||
mapIndex: r.currentMap.mapIndex,
|
mapIndex: r.currentMap.mapIndex,
|
||||||
lastBlock: r.iterator.blockNumber,
|
lastBlock: r.currentMap.lastBlock,
|
||||||
lastBlockId: r.f.targetView.getBlockId(r.currentMap.lastBlock),
|
lastBlockId: r.iterator.chainView.BlockId(r.currentMap.lastBlock),
|
||||||
blockLvPtrs: r.currentMap.blockLvPtrs,
|
blockLvPtrs: r.currentMap.blockLvPtrs,
|
||||||
finished: true,
|
finished: true,
|
||||||
headDelimiter: r.iterator.lvIndex,
|
headDelimiter: r.iterator.lvIndex,
|
||||||
|
|
@ -367,7 +371,7 @@ func (r *mapRenderer) renderCurrentMap(stopCb func() bool) (bool, error) {
|
||||||
r.currentMap.finished = true
|
r.currentMap.finished = true
|
||||||
r.currentMap.headDelimiter = r.iterator.lvIndex
|
r.currentMap.headDelimiter = r.iterator.lvIndex
|
||||||
}
|
}
|
||||||
r.currentMap.lastBlockId = r.f.targetView.getBlockId(r.currentMap.lastBlock)
|
r.currentMap.lastBlockId = r.f.targetView.BlockId(r.currentMap.lastBlock)
|
||||||
totalTime += time.Since(start)
|
totalTime += time.Since(start)
|
||||||
mapRenderTimer.Update(totalTime)
|
mapRenderTimer.Update(totalTime)
|
||||||
mapLogValueMeter.Mark(logValuesProcessed)
|
mapLogValueMeter.Mark(logValuesProcessed)
|
||||||
|
|
@ -533,6 +537,7 @@ func (r *mapRenderer) getTempRange() (filterMapsRange, error) {
|
||||||
} else {
|
} else {
|
||||||
tempRange.blocks.SetAfterLast(0)
|
tempRange.blocks.SetAfterLast(0)
|
||||||
}
|
}
|
||||||
|
tempRange.headIndexed = false
|
||||||
tempRange.headDelimiter = 0
|
tempRange.headDelimiter = 0
|
||||||
}
|
}
|
||||||
return tempRange, nil
|
return tempRange, nil
|
||||||
|
|
@ -563,8 +568,8 @@ func (r *mapRenderer) getUpdatedRange() (filterMapsRange, error) {
|
||||||
lm := r.finishedMaps[r.finished.Last()]
|
lm := r.finishedMaps[r.finished.Last()]
|
||||||
newRange.headIndexed = lm.finished
|
newRange.headIndexed = lm.finished
|
||||||
if lm.finished {
|
if lm.finished {
|
||||||
newRange.blocks.SetLast(r.f.targetView.headNumber)
|
newRange.blocks.SetLast(r.f.targetView.HeadNumber())
|
||||||
if lm.lastBlock != r.f.targetView.headNumber {
|
if lm.lastBlock != r.f.targetView.HeadNumber() {
|
||||||
panic("map rendering finished but last block != head block")
|
panic("map rendering finished but last block != head block")
|
||||||
}
|
}
|
||||||
newRange.headDelimiter = lm.headDelimiter
|
newRange.headDelimiter = lm.headDelimiter
|
||||||
|
|
@ -661,25 +666,14 @@ var errUnindexedRange = errors.New("unindexed range")
|
||||||
// newLogIteratorFromBlockDelimiter creates a logIterator starting at the
|
// newLogIteratorFromBlockDelimiter creates a logIterator starting at the
|
||||||
// given block's first log value entry (the block delimiter), according to the
|
// given block's first log value entry (the block delimiter), according to the
|
||||||
// current targetView.
|
// current targetView.
|
||||||
func (f *FilterMaps) newLogIteratorFromBlockDelimiter(blockNumber uint64) (*logIterator, error) {
|
func (f *FilterMaps) newLogIteratorFromBlockDelimiter(blockNumber, lvIndex uint64) (*logIterator, error) {
|
||||||
if blockNumber > f.targetView.headNumber {
|
if blockNumber > f.targetView.HeadNumber() {
|
||||||
return nil, fmt.Errorf("iterator entry point %d after target chain head block %d", blockNumber, f.targetView.headNumber)
|
return nil, fmt.Errorf("iterator entry point %d after target chain head block %d", blockNumber, f.targetView.HeadNumber())
|
||||||
}
|
}
|
||||||
if !f.indexedRange.blocks.Includes(blockNumber) {
|
if !f.indexedRange.blocks.Includes(blockNumber) {
|
||||||
return nil, errUnindexedRange
|
return nil, errUnindexedRange
|
||||||
}
|
}
|
||||||
var lvIndex uint64
|
finished := blockNumber == f.targetView.HeadNumber()
|
||||||
if f.indexedRange.headIndexed && blockNumber+1 == f.indexedRange.blocks.AfterLast() {
|
|
||||||
lvIndex = f.indexedRange.headDelimiter
|
|
||||||
} else {
|
|
||||||
var err error
|
|
||||||
lvIndex, err = f.getBlockLvPointer(blockNumber + 1)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to retrieve log value pointer of block %d after delimiter: %v", blockNumber+1, err)
|
|
||||||
}
|
|
||||||
lvIndex--
|
|
||||||
}
|
|
||||||
finished := blockNumber == f.targetView.headNumber
|
|
||||||
l := &logIterator{
|
l := &logIterator{
|
||||||
chainView: f.targetView,
|
chainView: f.targetView,
|
||||||
params: &f.Params,
|
params: &f.Params,
|
||||||
|
|
@ -695,11 +689,11 @@ func (f *FilterMaps) newLogIteratorFromBlockDelimiter(blockNumber uint64) (*logI
|
||||||
// newLogIteratorFromMapBoundary creates a logIterator starting at the given
|
// newLogIteratorFromMapBoundary creates a logIterator starting at the given
|
||||||
// map boundary, according to the current targetView.
|
// map boundary, according to the current targetView.
|
||||||
func (f *FilterMaps) newLogIteratorFromMapBoundary(mapIndex uint32, startBlock, startLvPtr uint64) (*logIterator, error) {
|
func (f *FilterMaps) newLogIteratorFromMapBoundary(mapIndex uint32, startBlock, startLvPtr uint64) (*logIterator, error) {
|
||||||
if startBlock > f.targetView.headNumber {
|
if startBlock > f.targetView.HeadNumber() {
|
||||||
return nil, fmt.Errorf("iterator entry point %d after target chain head block %d", startBlock, f.targetView.headNumber)
|
return nil, fmt.Errorf("iterator entry point %d after target chain head block %d", startBlock, f.targetView.HeadNumber())
|
||||||
}
|
}
|
||||||
// get block receipts
|
// get block receipts
|
||||||
receipts := f.targetView.getReceipts(startBlock)
|
receipts := f.targetView.Receipts(startBlock)
|
||||||
if receipts == nil {
|
if receipts == nil {
|
||||||
return nil, fmt.Errorf("receipts not found for start block %d", startBlock)
|
return nil, fmt.Errorf("receipts not found for start block %d", startBlock)
|
||||||
}
|
}
|
||||||
|
|
@ -766,7 +760,7 @@ func (l *logIterator) next() error {
|
||||||
if l.delimiter {
|
if l.delimiter {
|
||||||
l.delimiter = false
|
l.delimiter = false
|
||||||
l.blockNumber++
|
l.blockNumber++
|
||||||
l.receipts = l.chainView.getReceipts(l.blockNumber)
|
l.receipts = l.chainView.Receipts(l.blockNumber)
|
||||||
if l.receipts == nil {
|
if l.receipts == nil {
|
||||||
return fmt.Errorf("receipts not found for block %d", l.blockNumber)
|
return fmt.Errorf("receipts not found for block %d", l.blockNumber)
|
||||||
}
|
}
|
||||||
|
|
@ -803,7 +797,7 @@ func (l *logIterator) enforceValidState() {
|
||||||
}
|
}
|
||||||
l.logIndex = 0
|
l.logIndex = 0
|
||||||
}
|
}
|
||||||
if l.blockNumber == l.chainView.headNumber {
|
if l.blockNumber == l.chainView.HeadNumber() {
|
||||||
l.finished = true
|
l.finished = true
|
||||||
} else {
|
} else {
|
||||||
l.delimiter = true
|
l.delimiter = true
|
||||||
|
|
|
||||||
|
|
@ -57,7 +57,7 @@ type MatcherBackend interface {
|
||||||
// all states of the chain since the previous SyncLogIndex or the creation of
|
// all states of the chain since the previous SyncLogIndex or the creation of
|
||||||
// the matcher backend.
|
// the matcher backend.
|
||||||
type SyncRange struct {
|
type SyncRange struct {
|
||||||
HeadNumber uint64
|
IndexedView *ChainView
|
||||||
// block range where the index has not changed since the last matcher sync
|
// block range where the index has not changed since the last matcher sync
|
||||||
// and therefore the set of matches found in this region is guaranteed to
|
// and therefore the set of matches found in this region is guaranteed to
|
||||||
// be valid and complete.
|
// be valid and complete.
|
||||||
|
|
|
||||||
|
|
@ -75,6 +75,9 @@ func (fm *FilterMapsMatcherBackend) Close() {
|
||||||
// on write.
|
// on write.
|
||||||
// GetFilterMapRow implements MatcherBackend.
|
// GetFilterMapRow implements MatcherBackend.
|
||||||
func (fm *FilterMapsMatcherBackend) GetFilterMapRow(ctx context.Context, mapIndex, rowIndex uint32, baseLayerOnly bool) (FilterRow, error) {
|
func (fm *FilterMapsMatcherBackend) GetFilterMapRow(ctx context.Context, mapIndex, rowIndex uint32, baseLayerOnly bool) (FilterRow, error) {
|
||||||
|
fm.f.indexLock.RLock()
|
||||||
|
defer fm.f.indexLock.RUnlock()
|
||||||
|
|
||||||
return fm.f.getFilterMapRow(mapIndex, rowIndex, baseLayerOnly)
|
return fm.f.getFilterMapRow(mapIndex, rowIndex, baseLayerOnly)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -125,7 +128,7 @@ func (fm *FilterMapsMatcherBackend) synced() {
|
||||||
indexedBlocks.SetAfterLast(indexedBlocks.Last()) // remove partially indexed last block
|
indexedBlocks.SetAfterLast(indexedBlocks.Last()) // remove partially indexed last block
|
||||||
}
|
}
|
||||||
fm.syncCh <- SyncRange{
|
fm.syncCh <- SyncRange{
|
||||||
HeadNumber: fm.f.targetView.headNumber,
|
IndexedView: fm.f.indexedView,
|
||||||
ValidBlocks: fm.validBlocks,
|
ValidBlocks: fm.validBlocks,
|
||||||
IndexedBlocks: indexedBlocks,
|
IndexedBlocks: indexedBlocks,
|
||||||
}
|
}
|
||||||
|
|
@ -151,7 +154,7 @@ func (fm *FilterMapsMatcherBackend) SyncLogIndex(ctx context.Context) (SyncRange
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
return SyncRange{}, ctx.Err()
|
return SyncRange{}, ctx.Err()
|
||||||
case <-fm.f.disabledCh:
|
case <-fm.f.disabledCh:
|
||||||
return SyncRange{HeadNumber: fm.f.targetView.headNumber}, nil
|
return SyncRange{IndexedView: fm.f.indexedView}, nil
|
||||||
}
|
}
|
||||||
select {
|
select {
|
||||||
case vr := <-syncCh:
|
case vr := <-syncCh:
|
||||||
|
|
@ -159,7 +162,7 @@ func (fm *FilterMapsMatcherBackend) SyncLogIndex(ctx context.Context) (SyncRange
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
return SyncRange{}, ctx.Err()
|
return SyncRange{}, ctx.Err()
|
||||||
case <-fm.f.disabledCh:
|
case <-fm.f.disabledCh:
|
||||||
return SyncRange{HeadNumber: fm.f.targetView.headNumber}, nil
|
return SyncRange{IndexedView: fm.f.indexedView}, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -76,8 +76,10 @@ func TestCreation(t *testing.T) {
|
||||||
{20000000, 1681338454, ID{Hash: checksumToBytes(0xf0afd0e3), Next: 1681338455}}, // Last Gray Glacier block
|
{20000000, 1681338454, ID{Hash: checksumToBytes(0xf0afd0e3), Next: 1681338455}}, // Last Gray Glacier block
|
||||||
{20000000, 1681338455, ID{Hash: checksumToBytes(0xdce96c2d), Next: 1710338135}}, // First Shanghai block
|
{20000000, 1681338455, ID{Hash: checksumToBytes(0xdce96c2d), Next: 1710338135}}, // First Shanghai block
|
||||||
{30000000, 1710338134, ID{Hash: checksumToBytes(0xdce96c2d), Next: 1710338135}}, // Last Shanghai block
|
{30000000, 1710338134, ID{Hash: checksumToBytes(0xdce96c2d), Next: 1710338135}}, // Last Shanghai block
|
||||||
{40000000, 1710338135, ID{Hash: checksumToBytes(0x9f3d2254), Next: 0}}, // First Cancun block
|
{40000000, 1710338135, ID{Hash: checksumToBytes(0x9f3d2254), Next: 1746612311}}, // First Cancun block
|
||||||
{50000000, 2000000000, ID{Hash: checksumToBytes(0x9f3d2254), Next: 0}}, // Future Cancun block
|
{30000000, 1746022486, ID{Hash: checksumToBytes(0x9f3d2254), Next: 1746612311}}, // Last Cancun block
|
||||||
|
{30000000, 1746612311, ID{Hash: checksumToBytes(0xc376cf8b), Next: 0}}, // First Prague block
|
||||||
|
{50000000, 2000000000, ID{Hash: checksumToBytes(0xc376cf8b), Next: 0}}, // Future Prague block
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
// Sepolia test cases
|
// Sepolia test cases
|
||||||
|
|
@ -137,9 +139,11 @@ func TestCreation(t *testing.T) {
|
||||||
// fork ID.
|
// fork ID.
|
||||||
func TestValidation(t *testing.T) {
|
func TestValidation(t *testing.T) {
|
||||||
// Config that has not timestamp enabled
|
// Config that has not timestamp enabled
|
||||||
|
// TODO(lightclient): this always needs to be updated when a mainnet timestamp is set.
|
||||||
legacyConfig := *params.MainnetChainConfig
|
legacyConfig := *params.MainnetChainConfig
|
||||||
legacyConfig.ShanghaiTime = nil
|
legacyConfig.ShanghaiTime = nil
|
||||||
legacyConfig.CancunTime = nil
|
legacyConfig.CancunTime = nil
|
||||||
|
legacyConfig.PragueTime = nil
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
config *params.ChainConfig
|
config *params.ChainConfig
|
||||||
|
|
@ -314,9 +318,7 @@ func TestValidation(t *testing.T) {
|
||||||
|
|
||||||
// Local is mainnet Prague, remote announces Shanghai + knowledge about Cancun. Remote
|
// Local is mainnet Prague, remote announces Shanghai + knowledge about Cancun. Remote
|
||||||
// is definitely out of sync. It may or may not need the Prague update, we don't know yet.
|
// is definitely out of sync. It may or may not need the Prague update, we don't know yet.
|
||||||
//
|
{params.MainnetChainConfig, 0, 0, ID{Hash: checksumToBytes(0x3edd5b10), Next: 1710338135}, nil},
|
||||||
// TODO(karalabe): Enable this when Cancun **and** Prague is specced, update all the numbers
|
|
||||||
//{params.MainnetChainConfig, 0, 0, ID{Hash: checksumToBytes(0x3edd5b10), Next: 4370000}, nil},
|
|
||||||
|
|
||||||
// Local is mainnet Shanghai, remote announces Cancun. Local is out of sync, accept.
|
// Local is mainnet Shanghai, remote announces Cancun. Local is out of sync, accept.
|
||||||
{params.MainnetChainConfig, 21000000, 1700000000, ID{Hash: checksumToBytes(0x9f3d2254), Next: 0}, nil},
|
{params.MainnetChainConfig, 21000000, 1700000000, ID{Hash: checksumToBytes(0x9f3d2254), Next: 0}, nil},
|
||||||
|
|
@ -324,8 +326,7 @@ func TestValidation(t *testing.T) {
|
||||||
// Local is mainnet Shanghai, remote announces Cancun, but is not aware of Prague. Local
|
// Local is mainnet Shanghai, remote announces Cancun, but is not aware of Prague. Local
|
||||||
// out of sync. Local also knows about a future fork, but that is uncertain yet.
|
// out of sync. Local also knows about a future fork, but that is uncertain yet.
|
||||||
//
|
//
|
||||||
// TODO(karalabe): Enable this when Cancun **and** Prague is specced, update remote checksum
|
{params.MainnetChainConfig, 21000000, 1678000000, ID{Hash: checksumToBytes(0xc376cf8b), Next: 0}, nil},
|
||||||
//{params.MainnetChainConfig, 21000000, 1678000000, ID{Hash: checksumToBytes(0x00000000), Next: 0}, nil},
|
|
||||||
|
|
||||||
// Local is mainnet Cancun. remote announces Shanghai but is not aware of further forks.
|
// Local is mainnet Cancun. remote announces Shanghai but is not aware of further forks.
|
||||||
// Remote needs software update.
|
// Remote needs software update.
|
||||||
|
|
@ -342,11 +343,11 @@ func TestValidation(t *testing.T) {
|
||||||
// Local is mainnet Shanghai, remote is random Shanghai.
|
// Local is mainnet Shanghai, remote is random Shanghai.
|
||||||
{params.MainnetChainConfig, 20000000, 1681338455, ID{Hash: checksumToBytes(0x12345678), Next: 0}, ErrLocalIncompatibleOrStale},
|
{params.MainnetChainConfig, 20000000, 1681338455, ID{Hash: checksumToBytes(0x12345678), Next: 0}, ErrLocalIncompatibleOrStale},
|
||||||
|
|
||||||
// Local is mainnet Cancun, far in the future. Remote announces Gopherium (non existing fork)
|
// Local is mainnet Prague, far in the future. Remote announces Gopherium (non existing fork)
|
||||||
// at some future timestamp 8888888888, for itself, but past block for local. Local is incompatible.
|
// at some future timestamp 8888888888, for itself, but past block for local. Local is incompatible.
|
||||||
//
|
//
|
||||||
// This case detects non-upgraded nodes with majority hash power (typical Ropsten mess).
|
// This case detects non-upgraded nodes with majority hash power (typical Ropsten mess).
|
||||||
{params.MainnetChainConfig, 88888888, 8888888888, ID{Hash: checksumToBytes(0x9f3d2254), Next: 8888888888}, ErrLocalIncompatibleOrStale},
|
{params.MainnetChainConfig, 88888888, 8888888888, ID{Hash: checksumToBytes(0xc376cf8b), Next: 8888888888}, ErrLocalIncompatibleOrStale},
|
||||||
|
|
||||||
// Local is mainnet Shanghai. Remote is also in Shanghai, but announces Gopherium (non existing
|
// Local is mainnet Shanghai. Remote is also in Shanghai, but announces Gopherium (non existing
|
||||||
// fork) at timestamp 1668000000, before Cancun. Local is incompatible.
|
// fork) at timestamp 1668000000, before Cancun. Local is incompatible.
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@
|
||||||
// You should have received a copy of the GNU Lesser General Public License
|
// 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/>.
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
package ethconfig
|
package history
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
@ -27,22 +27,22 @@ import (
|
||||||
type HistoryMode uint32
|
type HistoryMode uint32
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// AllHistory (default) means that all chain history down to genesis block will be kept.
|
// KeepAll (default) means that all chain history down to genesis block will be kept.
|
||||||
AllHistory HistoryMode = iota
|
KeepAll HistoryMode = iota
|
||||||
|
|
||||||
// PostMergeHistory sets the history pruning point to the merge activation block.
|
// KeepPostMerge sets the history pruning point to the merge activation block.
|
||||||
PostMergeHistory
|
KeepPostMerge
|
||||||
)
|
)
|
||||||
|
|
||||||
func (m HistoryMode) IsValid() bool {
|
func (m HistoryMode) IsValid() bool {
|
||||||
return m <= PostMergeHistory
|
return m <= KeepPostMerge
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m HistoryMode) String() string {
|
func (m HistoryMode) String() string {
|
||||||
switch m {
|
switch m {
|
||||||
case AllHistory:
|
case KeepAll:
|
||||||
return "all"
|
return "all"
|
||||||
case PostMergeHistory:
|
case KeepPostMerge:
|
||||||
return "postmerge"
|
return "postmerge"
|
||||||
default:
|
default:
|
||||||
return fmt.Sprintf("invalid HistoryMode(%d)", m)
|
return fmt.Sprintf("invalid HistoryMode(%d)", m)
|
||||||
|
|
@ -61,24 +61,24 @@ func (m HistoryMode) MarshalText() ([]byte, error) {
|
||||||
func (m *HistoryMode) UnmarshalText(text []byte) error {
|
func (m *HistoryMode) UnmarshalText(text []byte) error {
|
||||||
switch string(text) {
|
switch string(text) {
|
||||||
case "all":
|
case "all":
|
||||||
*m = AllHistory
|
*m = KeepAll
|
||||||
case "postmerge":
|
case "postmerge":
|
||||||
*m = PostMergeHistory
|
*m = KeepPostMerge
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf(`unknown sync mode %q, want "all" or "postmerge"`, text)
|
return fmt.Errorf(`unknown sync mode %q, want "all" or "postmerge"`, text)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type HistoryPrunePoint struct {
|
type PrunePoint struct {
|
||||||
BlockNumber uint64
|
BlockNumber uint64
|
||||||
BlockHash common.Hash
|
BlockHash common.Hash
|
||||||
}
|
}
|
||||||
|
|
||||||
// HistoryPrunePoints contains the pre-defined history pruning cutoff blocks for known networks.
|
// PrunePoints the pre-defined history pruning cutoff blocks for known networks.
|
||||||
// They point to the first post-merge block. Any pruning should truncate *up to* but excluding
|
// They point to the first post-merge block. Any pruning should truncate *up to* but excluding
|
||||||
// given block.
|
// given block.
|
||||||
var HistoryPrunePoints = map[common.Hash]*HistoryPrunePoint{
|
var PrunePoints = map[common.Hash]*PrunePoint{
|
||||||
// mainnet
|
// mainnet
|
||||||
params.MainnetGenesisHash: {
|
params.MainnetGenesisHash: {
|
||||||
BlockNumber: 15537393,
|
BlockNumber: 15537393,
|
||||||
|
|
@ -91,7 +91,7 @@ var HistoryPrunePoints = map[common.Hash]*HistoryPrunePoint{
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// PrunedHistoryError is returned when the requested history is pruned.
|
// PrunedHistoryError is returned by APIs when the requested history is pruned.
|
||||||
type PrunedHistoryError struct{}
|
type PrunedHistoryError struct{}
|
||||||
|
|
||||||
func (e *PrunedHistoryError) Error() string { return "pruned history unavailable" }
|
func (e *PrunedHistoryError) Error() string { return "pruned history unavailable" }
|
||||||
|
|
@ -434,6 +434,7 @@ func DeleteBlockLvPointers(db ethdb.KeyValueStore, blocks common.Range[uint64],
|
||||||
// FilterMapsRange is a storage representation of the block range covered by the
|
// FilterMapsRange is a storage representation of the block range covered by the
|
||||||
// filter maps structure and the corresponting log value index range.
|
// filter maps structure and the corresponting log value index range.
|
||||||
type FilterMapsRange struct {
|
type FilterMapsRange struct {
|
||||||
|
Version uint32
|
||||||
HeadIndexed bool
|
HeadIndexed bool
|
||||||
HeadDelimiter uint64
|
HeadDelimiter uint64
|
||||||
BlocksFirst, BlocksAfterLast uint64
|
BlocksFirst, BlocksAfterLast uint64
|
||||||
|
|
|
||||||
|
|
@ -188,7 +188,7 @@ func (s *StateDB) DumpToCollector(c DumpCollector, conf *DumpConfig) (nextKey []
|
||||||
c.OnAccount(address, account)
|
c.OnAccount(address, account)
|
||||||
accounts++
|
accounts++
|
||||||
if time.Since(logged) > 8*time.Second {
|
if time.Since(logged) > 8*time.Second {
|
||||||
log.Info("Trie dumping in progress", "at", it.Key, "accounts", accounts,
|
log.Info("Trie dumping in progress", "at", common.Bytes2Hex(it.Key), "accounts", accounts,
|
||||||
"elapsed", common.PrettyDuration(time.Since(start)))
|
"elapsed", common.PrettyDuration(time.Since(start)))
|
||||||
logged = time.Now()
|
logged = time.Now()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -113,9 +113,13 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
// EIP-7002
|
// EIP-7002
|
||||||
ProcessWithdrawalQueue(&requests, evm)
|
if err := ProcessWithdrawalQueue(&requests, evm); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
// EIP-7251
|
// EIP-7251
|
||||||
ProcessConsolidationQueue(&requests, evm)
|
if err := ProcessConsolidationQueue(&requests, evm); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Finalize the block, applying any consensus engine specific extras (e.g. block rewards)
|
// Finalize the block, applying any consensus engine specific extras (e.g. block rewards)
|
||||||
|
|
@ -265,17 +269,17 @@ func ProcessParentBlockHash(prevHash common.Hash, evm *vm.EVM) {
|
||||||
|
|
||||||
// ProcessWithdrawalQueue calls the EIP-7002 withdrawal queue contract.
|
// ProcessWithdrawalQueue calls the EIP-7002 withdrawal queue contract.
|
||||||
// It returns the opaque request data returned by the contract.
|
// It returns the opaque request data returned by the contract.
|
||||||
func ProcessWithdrawalQueue(requests *[][]byte, evm *vm.EVM) {
|
func ProcessWithdrawalQueue(requests *[][]byte, evm *vm.EVM) error {
|
||||||
processRequestsSystemCall(requests, evm, 0x01, params.WithdrawalQueueAddress)
|
return processRequestsSystemCall(requests, evm, 0x01, params.WithdrawalQueueAddress)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ProcessConsolidationQueue calls the EIP-7251 consolidation queue contract.
|
// ProcessConsolidationQueue calls the EIP-7251 consolidation queue contract.
|
||||||
// It returns the opaque request data returned by the contract.
|
// It returns the opaque request data returned by the contract.
|
||||||
func ProcessConsolidationQueue(requests *[][]byte, evm *vm.EVM) {
|
func ProcessConsolidationQueue(requests *[][]byte, evm *vm.EVM) error {
|
||||||
processRequestsSystemCall(requests, evm, 0x02, params.ConsolidationQueueAddress)
|
return processRequestsSystemCall(requests, evm, 0x02, params.ConsolidationQueueAddress)
|
||||||
}
|
}
|
||||||
|
|
||||||
func processRequestsSystemCall(requests *[][]byte, evm *vm.EVM, requestType byte, addr common.Address) {
|
func processRequestsSystemCall(requests *[][]byte, evm *vm.EVM, requestType byte, addr common.Address) error {
|
||||||
if tracer := evm.Config.Tracer; tracer != nil {
|
if tracer := evm.Config.Tracer; tracer != nil {
|
||||||
onSystemCallStart(tracer, evm.GetVMContext())
|
onSystemCallStart(tracer, evm.GetVMContext())
|
||||||
if tracer.OnSystemCallEnd != nil {
|
if tracer.OnSystemCallEnd != nil {
|
||||||
|
|
@ -292,17 +296,20 @@ func processRequestsSystemCall(requests *[][]byte, evm *vm.EVM, requestType byte
|
||||||
}
|
}
|
||||||
evm.SetTxContext(NewEVMTxContext(msg))
|
evm.SetTxContext(NewEVMTxContext(msg))
|
||||||
evm.StateDB.AddAddressToAccessList(addr)
|
evm.StateDB.AddAddressToAccessList(addr)
|
||||||
ret, _, _ := evm.Call(msg.From, *msg.To, msg.Data, 30_000_000, common.U2560)
|
ret, _, err := evm.Call(msg.From, *msg.To, msg.Data, 30_000_000, common.U2560)
|
||||||
evm.StateDB.Finalise(true)
|
evm.StateDB.Finalise(true)
|
||||||
if len(ret) == 0 {
|
if err != nil {
|
||||||
return // skip empty output
|
return fmt.Errorf("system call failed to execute: %v", err)
|
||||||
|
}
|
||||||
|
if len(ret) == 0 {
|
||||||
|
return nil // skip empty output
|
||||||
}
|
}
|
||||||
|
|
||||||
// Append prefixed requestsData to the requests list.
|
// Append prefixed requestsData to the requests list.
|
||||||
requestsData := make([]byte, len(ret)+1)
|
requestsData := make([]byte, len(ret)+1)
|
||||||
requestsData[0] = requestType
|
requestsData[0] = requestType
|
||||||
copy(requestsData[1:], ret)
|
copy(requestsData[1:], ret)
|
||||||
*requests = append(*requests, requestsData)
|
*requests = append(*requests, requestsData)
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var depositTopic = common.HexToHash("0x649bbc62d0e31342afea4e5cd82d4049e7e1ee912fc0889aa790803be39038c5")
|
var depositTopic = common.HexToHash("0x649bbc62d0e31342afea4e5cd82d4049e7e1ee912fc0889aa790803be39038c5")
|
||||||
|
|
|
||||||
|
|
@ -196,6 +196,19 @@ func (indexer *txIndexer) repair(head uint64) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// resolveHead resolves the block number of the current chain head.
|
||||||
|
func (indexer *txIndexer) resolveHead() uint64 {
|
||||||
|
headBlockHash := rawdb.ReadHeadBlockHash(indexer.db)
|
||||||
|
if headBlockHash == (common.Hash{}) {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
headBlockNumber := rawdb.ReadHeaderNumber(indexer.db, headBlockHash)
|
||||||
|
if headBlockNumber == nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return *headBlockNumber
|
||||||
|
}
|
||||||
|
|
||||||
// loop is the scheduler of the indexer, assigning indexing/unindexing tasks depending
|
// loop is the scheduler of the indexer, assigning indexing/unindexing tasks depending
|
||||||
// on the received chain event.
|
// on the received chain event.
|
||||||
func (indexer *txIndexer) loop(chain *BlockChain) {
|
func (indexer *txIndexer) loop(chain *BlockChain) {
|
||||||
|
|
@ -205,7 +218,7 @@ func (indexer *txIndexer) loop(chain *BlockChain) {
|
||||||
var (
|
var (
|
||||||
stop chan struct{} // Non-nil if background routine is active
|
stop chan struct{} // Non-nil if background routine is active
|
||||||
done chan struct{} // Non-nil if background routine is active
|
done chan struct{} // Non-nil if background routine is active
|
||||||
head = rawdb.ReadHeadBlock(indexer.db).NumberU64() // The latest announced chain head
|
head = indexer.resolveHead() // The latest announced chain head
|
||||||
|
|
||||||
headCh = make(chan ChainHeadEvent)
|
headCh = make(chan ChainHeadEvent)
|
||||||
sub = chain.SubscribeChainHeadEvent(headCh)
|
sub = chain.SubscribeChainHeadEvent(headCh)
|
||||||
|
|
|
||||||
|
|
@ -1391,6 +1391,8 @@ func (p *BlobPool) add(tx *types.Transaction) (err error) {
|
||||||
switch {
|
switch {
|
||||||
case errors.Is(err, txpool.ErrUnderpriced):
|
case errors.Is(err, txpool.ErrUnderpriced):
|
||||||
addUnderpricedMeter.Mark(1)
|
addUnderpricedMeter.Mark(1)
|
||||||
|
case errors.Is(err, txpool.ErrTxGasPriceTooLow):
|
||||||
|
addUnderpricedMeter.Mark(1)
|
||||||
case errors.Is(err, core.ErrNonceTooLow):
|
case errors.Is(err, core.ErrNonceTooLow):
|
||||||
addStaleMeter.Mark(1)
|
addStaleMeter.Mark(1)
|
||||||
case errors.Is(err, core.ErrNonceTooHigh):
|
case errors.Is(err, core.ErrNonceTooHigh):
|
||||||
|
|
|
||||||
|
|
@ -1484,7 +1484,7 @@ func TestAdd(t *testing.T) {
|
||||||
{ // New account, no previous txs, nonce 0, but blob fee cap too low
|
{ // New account, no previous txs, nonce 0, but blob fee cap too low
|
||||||
from: "alice",
|
from: "alice",
|
||||||
tx: makeUnsignedTx(0, 1, 1, 0),
|
tx: makeUnsignedTx(0, 1, 1, 0),
|
||||||
err: txpool.ErrUnderpriced,
|
err: txpool.ErrTxGasPriceTooLow,
|
||||||
},
|
},
|
||||||
{ // Same as above but blob fee cap equals minimum, should be accepted
|
{ // Same as above but blob fee cap equals minimum, should be accepted
|
||||||
from: "alice",
|
from: "alice",
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,9 @@
|
||||||
|
|
||||||
package txpool
|
package txpool
|
||||||
|
|
||||||
import "errors"
|
import (
|
||||||
|
"errors"
|
||||||
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
// ErrAlreadyKnown is returned if the transactions is already contained
|
// ErrAlreadyKnown is returned if the transactions is already contained
|
||||||
|
|
@ -26,14 +28,19 @@ var (
|
||||||
// ErrInvalidSender is returned if the transaction contains an invalid signature.
|
// ErrInvalidSender is returned if the transaction contains an invalid signature.
|
||||||
ErrInvalidSender = errors.New("invalid sender")
|
ErrInvalidSender = errors.New("invalid sender")
|
||||||
|
|
||||||
// ErrUnderpriced is returned if a transaction's gas price is below the minimum
|
// ErrUnderpriced is returned if a transaction's gas price is too low to be
|
||||||
// configured for the transaction pool.
|
// included in the pool. If the gas price is lower than the minimum configured
|
||||||
|
// one for the transaction pool, use ErrTxGasPriceTooLow instead.
|
||||||
ErrUnderpriced = errors.New("transaction underpriced")
|
ErrUnderpriced = errors.New("transaction underpriced")
|
||||||
|
|
||||||
// ErrReplaceUnderpriced is returned if a transaction is attempted to be replaced
|
// ErrReplaceUnderpriced is returned if a transaction is attempted to be replaced
|
||||||
// with a different one without the required price bump.
|
// with a different one without the required price bump.
|
||||||
ErrReplaceUnderpriced = errors.New("replacement transaction underpriced")
|
ErrReplaceUnderpriced = errors.New("replacement transaction underpriced")
|
||||||
|
|
||||||
|
// ErrTxGasPriceTooLow is returned if a transaction's gas price is below the
|
||||||
|
// minimum configured for the transaction pool.
|
||||||
|
ErrTxGasPriceTooLow = errors.New("transaction gas price below minimum")
|
||||||
|
|
||||||
// ErrAccountLimitExceeded is returned if a transaction would exceed the number
|
// ErrAccountLimitExceeded is returned if a transaction would exceed the number
|
||||||
// allowed by a pool for a single account.
|
// allowed by a pool for a single account.
|
||||||
ErrAccountLimitExceeded = errors.New("account limit exceeded")
|
ErrAccountLimitExceeded = errors.New("account limit exceeded")
|
||||||
|
|
|
||||||
|
|
@ -1827,6 +1827,16 @@ func (t *lookup) Remove(hash common.Hash) {
|
||||||
delete(t.txs, hash)
|
delete(t.txs, hash)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Clear resets the lookup structure, removing all stored entries.
|
||||||
|
func (t *lookup) Clear() {
|
||||||
|
t.lock.Lock()
|
||||||
|
defer t.lock.Unlock()
|
||||||
|
|
||||||
|
t.slots = 0
|
||||||
|
t.txs = make(map[common.Hash]*types.Transaction)
|
||||||
|
t.auths = make(map[common.Address][]common.Hash)
|
||||||
|
}
|
||||||
|
|
||||||
// TxsBelowTip finds all remote transactions below the given tip threshold.
|
// TxsBelowTip finds all remote transactions below the given tip threshold.
|
||||||
func (t *lookup) TxsBelowTip(threshold *big.Int) types.Transactions {
|
func (t *lookup) TxsBelowTip(threshold *big.Int) types.Transactions {
|
||||||
found := make(types.Transactions, 0, 128)
|
found := make(types.Transactions, 0, 128)
|
||||||
|
|
@ -1923,7 +1933,7 @@ func (pool *LegacyPool) Clear() {
|
||||||
for addr := range pool.queue {
|
for addr := range pool.queue {
|
||||||
pool.reserver.Release(addr)
|
pool.reserver.Release(addr)
|
||||||
}
|
}
|
||||||
pool.all = newLookup()
|
pool.all.Clear()
|
||||||
pool.priced = newPricedList(pool.all)
|
pool.priced = newPricedList(pool.all)
|
||||||
pool.pending = make(map[common.Address]*list)
|
pool.pending = make(map[common.Address]*list)
|
||||||
pool.queue = make(map[common.Address]*list)
|
pool.queue = make(map[common.Address]*list)
|
||||||
|
|
|
||||||
|
|
@ -82,12 +82,14 @@ func TestTransactionFutureAttack(t *testing.T) {
|
||||||
// Create the pool to test the limit enforcement with
|
// Create the pool to test the limit enforcement with
|
||||||
statedb, _ := state.New(types.EmptyRootHash, state.NewDatabaseForTesting())
|
statedb, _ := state.New(types.EmptyRootHash, state.NewDatabaseForTesting())
|
||||||
blockchain := newTestBlockChain(eip1559Config, 1000000, statedb, new(event.Feed))
|
blockchain := newTestBlockChain(eip1559Config, 1000000, statedb, new(event.Feed))
|
||||||
|
|
||||||
config := testTxPoolConfig
|
config := testTxPoolConfig
|
||||||
config.GlobalQueue = 100
|
config.GlobalQueue = 100
|
||||||
config.GlobalSlots = 100
|
config.GlobalSlots = 100
|
||||||
pool := New(config, blockchain)
|
pool := New(config, blockchain)
|
||||||
pool.Init(config.PriceLimit, blockchain.CurrentBlock(), newReserver())
|
pool.Init(config.PriceLimit, blockchain.CurrentBlock(), newReserver())
|
||||||
defer pool.Close()
|
defer pool.Close()
|
||||||
|
|
||||||
fillPool(t, pool)
|
fillPool(t, pool)
|
||||||
pending, _ := pool.Stats()
|
pending, _ := pool.Stats()
|
||||||
// Now, future transaction attack starts, let's add a bunch of expensive non-executables, and see if the pending-count drops
|
// Now, future transaction attack starts, let's add a bunch of expensive non-executables, and see if the pending-count drops
|
||||||
|
|
@ -180,7 +182,9 @@ func TestTransactionZAttack(t *testing.T) {
|
||||||
ivPending := countInvalidPending()
|
ivPending := countInvalidPending()
|
||||||
t.Logf("invalid pending: %d\n", ivPending)
|
t.Logf("invalid pending: %d\n", ivPending)
|
||||||
|
|
||||||
// Now, DETER-Z attack starts, let's add a bunch of expensive non-executables (from N accounts) along with balance-overdraft txs (from one account), and see if the pending-count drops
|
// Now, DETER-Z attack starts, let's add a bunch of expensive non-executables
|
||||||
|
// (from N accounts) along with balance-overdraft txs (from one account), and
|
||||||
|
// see if the pending-count drops
|
||||||
for j := 0; j < int(pool.config.GlobalQueue); j++ {
|
for j := 0; j < int(pool.config.GlobalQueue); j++ {
|
||||||
futureTxs := types.Transactions{}
|
futureTxs := types.Transactions{}
|
||||||
key, _ := crypto.GenerateKey()
|
key, _ := crypto.GenerateKey()
|
||||||
|
|
|
||||||
|
|
@ -413,7 +413,7 @@ func TestInvalidTransactions(t *testing.T) {
|
||||||
|
|
||||||
tx = transaction(1, 100000, key)
|
tx = transaction(1, 100000, key)
|
||||||
pool.gasTip.Store(uint256.NewInt(1000))
|
pool.gasTip.Store(uint256.NewInt(1000))
|
||||||
if err, want := pool.addRemote(tx), txpool.ErrUnderpriced; !errors.Is(err, want) {
|
if err, want := pool.addRemote(tx), txpool.ErrTxGasPriceTooLow; !errors.Is(err, want) {
|
||||||
t.Errorf("want %v have %v", want, err)
|
t.Errorf("want %v have %v", want, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -484,7 +484,7 @@ func TestNegativeValue(t *testing.T) {
|
||||||
tx, _ := types.SignTx(types.NewTransaction(0, common.Address{}, big.NewInt(-1), 100, big.NewInt(1), nil), types.HomesteadSigner{}, key)
|
tx, _ := types.SignTx(types.NewTransaction(0, common.Address{}, big.NewInt(-1), 100, big.NewInt(1), nil), types.HomesteadSigner{}, key)
|
||||||
from, _ := deriveSender(tx)
|
from, _ := deriveSender(tx)
|
||||||
testAddBalance(pool, from, big.NewInt(1))
|
testAddBalance(pool, from, big.NewInt(1))
|
||||||
if err := pool.addRemote(tx); err != txpool.ErrNegativeValue {
|
if err := pool.addRemote(tx); !errors.Is(err, txpool.ErrNegativeValue) {
|
||||||
t.Error("expected", txpool.ErrNegativeValue, "got", err)
|
t.Error("expected", txpool.ErrNegativeValue, "got", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -497,7 +497,7 @@ func TestTipAboveFeeCap(t *testing.T) {
|
||||||
|
|
||||||
tx := dynamicFeeTx(0, 100, big.NewInt(1), big.NewInt(2), key)
|
tx := dynamicFeeTx(0, 100, big.NewInt(1), big.NewInt(2), key)
|
||||||
|
|
||||||
if err := pool.addRemote(tx); err != core.ErrTipAboveFeeCap {
|
if err := pool.addRemote(tx); !errors.Is(err, core.ErrTipAboveFeeCap) {
|
||||||
t.Error("expected", core.ErrTipAboveFeeCap, "got", err)
|
t.Error("expected", core.ErrTipAboveFeeCap, "got", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -512,12 +512,12 @@ func TestVeryHighValues(t *testing.T) {
|
||||||
veryBigNumber.Lsh(veryBigNumber, 300)
|
veryBigNumber.Lsh(veryBigNumber, 300)
|
||||||
|
|
||||||
tx := dynamicFeeTx(0, 100, big.NewInt(1), veryBigNumber, key)
|
tx := dynamicFeeTx(0, 100, big.NewInt(1), veryBigNumber, key)
|
||||||
if err := pool.addRemote(tx); err != core.ErrTipVeryHigh {
|
if err := pool.addRemote(tx); !errors.Is(err, core.ErrTipVeryHigh) {
|
||||||
t.Error("expected", core.ErrTipVeryHigh, "got", err)
|
t.Error("expected", core.ErrTipVeryHigh, "got", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
tx2 := dynamicFeeTx(0, 100, veryBigNumber, big.NewInt(1), key)
|
tx2 := dynamicFeeTx(0, 100, veryBigNumber, big.NewInt(1), key)
|
||||||
if err := pool.addRemote(tx2); err != core.ErrFeeCapVeryHigh {
|
if err := pool.addRemote(tx2); !errors.Is(err, core.ErrFeeCapVeryHigh) {
|
||||||
t.Error("expected", core.ErrFeeCapVeryHigh, "got", err)
|
t.Error("expected", core.ErrFeeCapVeryHigh, "got", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1424,14 +1424,14 @@ func TestRepricing(t *testing.T) {
|
||||||
t.Fatalf("pool internal state corrupted: %v", err)
|
t.Fatalf("pool internal state corrupted: %v", err)
|
||||||
}
|
}
|
||||||
// Check that we can't add the old transactions back
|
// Check that we can't add the old transactions back
|
||||||
if err := pool.addRemote(pricedTransaction(1, 100000, big.NewInt(1), keys[0])); !errors.Is(err, txpool.ErrUnderpriced) {
|
if err := pool.addRemote(pricedTransaction(1, 100000, big.NewInt(1), keys[0])); !errors.Is(err, txpool.ErrTxGasPriceTooLow) {
|
||||||
t.Fatalf("adding underpriced pending transaction error mismatch: have %v, want %v", err, txpool.ErrUnderpriced)
|
t.Fatalf("adding underpriced pending transaction error mismatch: have %v, want %v", err, txpool.ErrTxGasPriceTooLow)
|
||||||
}
|
}
|
||||||
if err := pool.addRemote(pricedTransaction(0, 100000, big.NewInt(1), keys[1])); !errors.Is(err, txpool.ErrUnderpriced) {
|
if err := pool.addRemote(pricedTransaction(0, 100000, big.NewInt(1), keys[1])); !errors.Is(err, txpool.ErrTxGasPriceTooLow) {
|
||||||
t.Fatalf("adding underpriced pending transaction error mismatch: have %v, want %v", err, txpool.ErrUnderpriced)
|
t.Fatalf("adding underpriced pending transaction error mismatch: have %v, want %v", err, txpool.ErrTxGasPriceTooLow)
|
||||||
}
|
}
|
||||||
if err := pool.addRemote(pricedTransaction(2, 100000, big.NewInt(1), keys[2])); !errors.Is(err, txpool.ErrUnderpriced) {
|
if err := pool.addRemote(pricedTransaction(2, 100000, big.NewInt(1), keys[2])); !errors.Is(err, txpool.ErrTxGasPriceTooLow) {
|
||||||
t.Fatalf("adding underpriced queued transaction error mismatch: have %v, want %v", err, txpool.ErrUnderpriced)
|
t.Fatalf("adding underpriced queued transaction error mismatch: have %v, want %v", err, txpool.ErrTxGasPriceTooLow)
|
||||||
}
|
}
|
||||||
if err := validateEvents(events, 0); err != nil {
|
if err := validateEvents(events, 0); err != nil {
|
||||||
t.Fatalf("post-reprice event firing failed: %v", err)
|
t.Fatalf("post-reprice event firing failed: %v", err)
|
||||||
|
|
@ -1476,14 +1476,14 @@ func TestMinGasPriceEnforced(t *testing.T) {
|
||||||
tx := pricedTransaction(0, 100000, big.NewInt(2), key)
|
tx := pricedTransaction(0, 100000, big.NewInt(2), key)
|
||||||
pool.SetGasTip(big.NewInt(tx.GasPrice().Int64() + 1))
|
pool.SetGasTip(big.NewInt(tx.GasPrice().Int64() + 1))
|
||||||
|
|
||||||
if err := pool.Add([]*types.Transaction{tx}, true)[0]; !errors.Is(err, txpool.ErrUnderpriced) {
|
if err := pool.Add([]*types.Transaction{tx}, true)[0]; !errors.Is(err, txpool.ErrTxGasPriceTooLow) {
|
||||||
t.Fatalf("Min tip not enforced")
|
t.Fatalf("Min tip not enforced")
|
||||||
}
|
}
|
||||||
|
|
||||||
tx = dynamicFeeTx(0, 100000, big.NewInt(3), big.NewInt(2), key)
|
tx = dynamicFeeTx(0, 100000, big.NewInt(3), big.NewInt(2), key)
|
||||||
pool.SetGasTip(big.NewInt(tx.GasTipCap().Int64() + 1))
|
pool.SetGasTip(big.NewInt(tx.GasTipCap().Int64() + 1))
|
||||||
|
|
||||||
if err := pool.Add([]*types.Transaction{tx}, true)[0]; !errors.Is(err, txpool.ErrUnderpriced) {
|
if err := pool.Add([]*types.Transaction{tx}, true)[0]; !errors.Is(err, txpool.ErrTxGasPriceTooLow) {
|
||||||
t.Fatalf("Min tip not enforced")
|
t.Fatalf("Min tip not enforced")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1560,16 +1560,16 @@ func TestRepricingDynamicFee(t *testing.T) {
|
||||||
}
|
}
|
||||||
// Check that we can't add the old transactions back
|
// Check that we can't add the old transactions back
|
||||||
tx := pricedTransaction(1, 100000, big.NewInt(1), keys[0])
|
tx := pricedTransaction(1, 100000, big.NewInt(1), keys[0])
|
||||||
if err := pool.addRemote(tx); !errors.Is(err, txpool.ErrUnderpriced) {
|
if err := pool.addRemote(tx); !errors.Is(err, txpool.ErrTxGasPriceTooLow) {
|
||||||
t.Fatalf("adding underpriced pending transaction error mismatch: have %v, want %v", err, txpool.ErrUnderpriced)
|
t.Fatalf("adding underpriced pending transaction error mismatch: have %v, want %v", err, txpool.ErrTxGasPriceTooLow)
|
||||||
}
|
}
|
||||||
tx = dynamicFeeTx(0, 100000, big.NewInt(2), big.NewInt(1), keys[1])
|
tx = dynamicFeeTx(0, 100000, big.NewInt(2), big.NewInt(1), keys[1])
|
||||||
if err := pool.addRemote(tx); !errors.Is(err, txpool.ErrUnderpriced) {
|
if err := pool.addRemote(tx); !errors.Is(err, txpool.ErrTxGasPriceTooLow) {
|
||||||
t.Fatalf("adding underpriced pending transaction error mismatch: have %v, want %v", err, txpool.ErrUnderpriced)
|
t.Fatalf("adding underpriced pending transaction error mismatch: have %v, want %v", err, txpool.ErrTxGasPriceTooLow)
|
||||||
}
|
}
|
||||||
tx = dynamicFeeTx(2, 100000, big.NewInt(1), big.NewInt(1), keys[2])
|
tx = dynamicFeeTx(2, 100000, big.NewInt(1), big.NewInt(1), keys[2])
|
||||||
if err := pool.addRemote(tx); !errors.Is(err, txpool.ErrUnderpriced) {
|
if err := pool.addRemote(tx); !errors.Is(err, txpool.ErrTxGasPriceTooLow) {
|
||||||
t.Fatalf("adding underpriced queued transaction error mismatch: have %v, want %v", err, txpool.ErrUnderpriced)
|
t.Fatalf("adding underpriced queued transaction error mismatch: have %v, want %v", err, txpool.ErrTxGasPriceTooLow)
|
||||||
}
|
}
|
||||||
if err := validateEvents(events, 0); err != nil {
|
if err := validateEvents(events, 0); err != nil {
|
||||||
t.Fatalf("post-reprice event firing failed: %v", err)
|
t.Fatalf("post-reprice event firing failed: %v", err)
|
||||||
|
|
@ -1673,7 +1673,7 @@ func TestUnderpricing(t *testing.T) {
|
||||||
t.Fatalf("failed to add well priced transaction: %v", err)
|
t.Fatalf("failed to add well priced transaction: %v", err)
|
||||||
}
|
}
|
||||||
// Ensure that replacing a pending transaction with a future transaction fails
|
// Ensure that replacing a pending transaction with a future transaction fails
|
||||||
if err := pool.addRemoteSync(pricedTransaction(5, 100000, big.NewInt(6), keys[1])); err != ErrFutureReplacePending {
|
if err := pool.addRemoteSync(pricedTransaction(5, 100000, big.NewInt(6), keys[1])); !errors.Is(err, ErrFutureReplacePending) {
|
||||||
t.Fatalf("adding future replace transaction error mismatch: have %v, want %v", err, ErrFutureReplacePending)
|
t.Fatalf("adding future replace transaction error mismatch: have %v, want %v", err, ErrFutureReplacePending)
|
||||||
}
|
}
|
||||||
pending, queued = pool.Stats()
|
pending, queued = pool.Stats()
|
||||||
|
|
@ -1995,7 +1995,7 @@ func TestReplacement(t *testing.T) {
|
||||||
if err := pool.addRemoteSync(pricedTransaction(0, 100000, big.NewInt(1), key)); err != nil {
|
if err := pool.addRemoteSync(pricedTransaction(0, 100000, big.NewInt(1), key)); err != nil {
|
||||||
t.Fatalf("failed to add original cheap pending transaction: %v", err)
|
t.Fatalf("failed to add original cheap pending transaction: %v", err)
|
||||||
}
|
}
|
||||||
if err := pool.addRemote(pricedTransaction(0, 100001, big.NewInt(1), key)); err != txpool.ErrReplaceUnderpriced {
|
if err := pool.addRemote(pricedTransaction(0, 100001, big.NewInt(1), key)); !errors.Is(err, txpool.ErrReplaceUnderpriced) {
|
||||||
t.Fatalf("original cheap pending transaction replacement error mismatch: have %v, want %v", err, txpool.ErrReplaceUnderpriced)
|
t.Fatalf("original cheap pending transaction replacement error mismatch: have %v, want %v", err, txpool.ErrReplaceUnderpriced)
|
||||||
}
|
}
|
||||||
if err := pool.addRemote(pricedTransaction(0, 100000, big.NewInt(2), key)); err != nil {
|
if err := pool.addRemote(pricedTransaction(0, 100000, big.NewInt(2), key)); err != nil {
|
||||||
|
|
@ -2008,7 +2008,7 @@ func TestReplacement(t *testing.T) {
|
||||||
if err := pool.addRemoteSync(pricedTransaction(0, 100000, big.NewInt(price), key)); err != nil {
|
if err := pool.addRemoteSync(pricedTransaction(0, 100000, big.NewInt(price), key)); err != nil {
|
||||||
t.Fatalf("failed to add original proper pending transaction: %v", err)
|
t.Fatalf("failed to add original proper pending transaction: %v", err)
|
||||||
}
|
}
|
||||||
if err := pool.addRemote(pricedTransaction(0, 100001, big.NewInt(threshold-1), key)); err != txpool.ErrReplaceUnderpriced {
|
if err := pool.addRemote(pricedTransaction(0, 100001, big.NewInt(threshold-1), key)); !errors.Is(err, txpool.ErrReplaceUnderpriced) {
|
||||||
t.Fatalf("original proper pending transaction replacement error mismatch: have %v, want %v", err, txpool.ErrReplaceUnderpriced)
|
t.Fatalf("original proper pending transaction replacement error mismatch: have %v, want %v", err, txpool.ErrReplaceUnderpriced)
|
||||||
}
|
}
|
||||||
if err := pool.addRemote(pricedTransaction(0, 100000, big.NewInt(threshold), key)); err != nil {
|
if err := pool.addRemote(pricedTransaction(0, 100000, big.NewInt(threshold), key)); err != nil {
|
||||||
|
|
@ -2022,7 +2022,7 @@ func TestReplacement(t *testing.T) {
|
||||||
if err := pool.addRemote(pricedTransaction(2, 100000, big.NewInt(1), key)); err != nil {
|
if err := pool.addRemote(pricedTransaction(2, 100000, big.NewInt(1), key)); err != nil {
|
||||||
t.Fatalf("failed to add original cheap queued transaction: %v", err)
|
t.Fatalf("failed to add original cheap queued transaction: %v", err)
|
||||||
}
|
}
|
||||||
if err := pool.addRemote(pricedTransaction(2, 100001, big.NewInt(1), key)); err != txpool.ErrReplaceUnderpriced {
|
if err := pool.addRemote(pricedTransaction(2, 100001, big.NewInt(1), key)); !errors.Is(err, txpool.ErrReplaceUnderpriced) {
|
||||||
t.Fatalf("original cheap queued transaction replacement error mismatch: have %v, want %v", err, txpool.ErrReplaceUnderpriced)
|
t.Fatalf("original cheap queued transaction replacement error mismatch: have %v, want %v", err, txpool.ErrReplaceUnderpriced)
|
||||||
}
|
}
|
||||||
if err := pool.addRemote(pricedTransaction(2, 100000, big.NewInt(2), key)); err != nil {
|
if err := pool.addRemote(pricedTransaction(2, 100000, big.NewInt(2), key)); err != nil {
|
||||||
|
|
@ -2032,7 +2032,7 @@ func TestReplacement(t *testing.T) {
|
||||||
if err := pool.addRemote(pricedTransaction(2, 100000, big.NewInt(price), key)); err != nil {
|
if err := pool.addRemote(pricedTransaction(2, 100000, big.NewInt(price), key)); err != nil {
|
||||||
t.Fatalf("failed to add original proper queued transaction: %v", err)
|
t.Fatalf("failed to add original proper queued transaction: %v", err)
|
||||||
}
|
}
|
||||||
if err := pool.addRemote(pricedTransaction(2, 100001, big.NewInt(threshold-1), key)); err != txpool.ErrReplaceUnderpriced {
|
if err := pool.addRemote(pricedTransaction(2, 100001, big.NewInt(threshold-1), key)); !errors.Is(err, txpool.ErrReplaceUnderpriced) {
|
||||||
t.Fatalf("original proper queued transaction replacement error mismatch: have %v, want %v", err, txpool.ErrReplaceUnderpriced)
|
t.Fatalf("original proper queued transaction replacement error mismatch: have %v, want %v", err, txpool.ErrReplaceUnderpriced)
|
||||||
}
|
}
|
||||||
if err := pool.addRemote(pricedTransaction(2, 100000, big.NewInt(threshold), key)); err != nil {
|
if err := pool.addRemote(pricedTransaction(2, 100000, big.NewInt(threshold), key)); err != nil {
|
||||||
|
|
@ -2096,7 +2096,7 @@ func TestReplacementDynamicFee(t *testing.T) {
|
||||||
}
|
}
|
||||||
// 2. Don't bump tip or feecap => discard
|
// 2. Don't bump tip or feecap => discard
|
||||||
tx = dynamicFeeTx(nonce, 100001, big.NewInt(2), big.NewInt(1), key)
|
tx = dynamicFeeTx(nonce, 100001, big.NewInt(2), big.NewInt(1), key)
|
||||||
if err := pool.addRemote(tx); err != txpool.ErrReplaceUnderpriced {
|
if err := pool.addRemote(tx); !errors.Is(err, txpool.ErrReplaceUnderpriced) {
|
||||||
t.Fatalf("original cheap %s transaction replacement error mismatch: have %v, want %v", stage, err, txpool.ErrReplaceUnderpriced)
|
t.Fatalf("original cheap %s transaction replacement error mismatch: have %v, want %v", stage, err, txpool.ErrReplaceUnderpriced)
|
||||||
}
|
}
|
||||||
// 3. Bump both more than min => accept
|
// 3. Bump both more than min => accept
|
||||||
|
|
@ -2117,24 +2117,25 @@ func TestReplacementDynamicFee(t *testing.T) {
|
||||||
if err := pool.addRemoteSync(tx); err != nil {
|
if err := pool.addRemoteSync(tx); err != nil {
|
||||||
t.Fatalf("failed to add original proper %s transaction: %v", stage, err)
|
t.Fatalf("failed to add original proper %s transaction: %v", stage, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 6. Bump tip max allowed so it's still underpriced => discard
|
// 6. Bump tip max allowed so it's still underpriced => discard
|
||||||
tx = dynamicFeeTx(nonce, 100000, big.NewInt(gasFeeCap), big.NewInt(tipThreshold-1), key)
|
tx = dynamicFeeTx(nonce, 100000, big.NewInt(gasFeeCap), big.NewInt(tipThreshold-1), key)
|
||||||
if err := pool.addRemote(tx); err != txpool.ErrReplaceUnderpriced {
|
if err := pool.addRemote(tx); !errors.Is(err, txpool.ErrReplaceUnderpriced) {
|
||||||
t.Fatalf("original proper %s transaction replacement error mismatch: have %v, want %v", stage, err, txpool.ErrReplaceUnderpriced)
|
t.Fatalf("original proper %s transaction replacement error mismatch: have %v, want %v", stage, err, txpool.ErrReplaceUnderpriced)
|
||||||
}
|
}
|
||||||
// 7. Bump fee cap max allowed so it's still underpriced => discard
|
// 7. Bump fee cap max allowed so it's still underpriced => discard
|
||||||
tx = dynamicFeeTx(nonce, 100000, big.NewInt(feeCapThreshold-1), big.NewInt(gasTipCap), key)
|
tx = dynamicFeeTx(nonce, 100000, big.NewInt(feeCapThreshold-1), big.NewInt(gasTipCap), key)
|
||||||
if err := pool.addRemote(tx); err != txpool.ErrReplaceUnderpriced {
|
if err := pool.addRemote(tx); !errors.Is(err, txpool.ErrReplaceUnderpriced) {
|
||||||
t.Fatalf("original proper %s transaction replacement error mismatch: have %v, want %v", stage, err, txpool.ErrReplaceUnderpriced)
|
t.Fatalf("original proper %s transaction replacement error mismatch: have %v, want %v", stage, err, txpool.ErrReplaceUnderpriced)
|
||||||
}
|
}
|
||||||
// 8. Bump tip min for acceptance => accept
|
// 8. Bump tip min for acceptance => accept
|
||||||
tx = dynamicFeeTx(nonce, 100000, big.NewInt(gasFeeCap), big.NewInt(tipThreshold), key)
|
tx = dynamicFeeTx(nonce, 100000, big.NewInt(gasFeeCap), big.NewInt(tipThreshold), key)
|
||||||
if err := pool.addRemote(tx); err != txpool.ErrReplaceUnderpriced {
|
if err := pool.addRemote(tx); !errors.Is(err, txpool.ErrReplaceUnderpriced) {
|
||||||
t.Fatalf("original proper %s transaction replacement error mismatch: have %v, want %v", stage, err, txpool.ErrReplaceUnderpriced)
|
t.Fatalf("original proper %s transaction replacement error mismatch: have %v, want %v", stage, err, txpool.ErrReplaceUnderpriced)
|
||||||
}
|
}
|
||||||
// 9. Bump fee cap min for acceptance => accept
|
// 9. Bump fee cap min for acceptance => accept
|
||||||
tx = dynamicFeeTx(nonce, 100000, big.NewInt(feeCapThreshold), big.NewInt(gasTipCap), key)
|
tx = dynamicFeeTx(nonce, 100000, big.NewInt(feeCapThreshold), big.NewInt(gasTipCap), key)
|
||||||
if err := pool.addRemote(tx); err != txpool.ErrReplaceUnderpriced {
|
if err := pool.addRemote(tx); !errors.Is(err, txpool.ErrReplaceUnderpriced) {
|
||||||
t.Fatalf("original proper %s transaction replacement error mismatch: have %v, want %v", stage, err, txpool.ErrReplaceUnderpriced)
|
t.Fatalf("original proper %s transaction replacement error mismatch: have %v, want %v", stage, err, txpool.ErrReplaceUnderpriced)
|
||||||
}
|
}
|
||||||
// 10. Check events match expected (3 new executable txs during pending, 0 during queue)
|
// 10. Check events match expected (3 new executable txs during pending, 0 during queue)
|
||||||
|
|
|
||||||
46
core/txpool/locals/errors.go
Normal file
46
core/txpool/locals/errors.go
Normal file
|
|
@ -0,0 +1,46 @@
|
||||||
|
// 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 locals
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/core/txpool"
|
||||||
|
"github.com/ethereum/go-ethereum/core/txpool/legacypool"
|
||||||
|
)
|
||||||
|
|
||||||
|
// IsTemporaryReject determines whether the given error indicates a temporary
|
||||||
|
// reason to reject a transaction from being included in the txpool. The result
|
||||||
|
// may change if the txpool's state changes later.
|
||||||
|
func IsTemporaryReject(err error) bool {
|
||||||
|
switch {
|
||||||
|
case errors.Is(err, legacypool.ErrOutOfOrderTxFromDelegated):
|
||||||
|
return true
|
||||||
|
case errors.Is(err, txpool.ErrInflightTxLimitReached):
|
||||||
|
return true
|
||||||
|
case errors.Is(err, legacypool.ErrAuthorityReserved):
|
||||||
|
return true
|
||||||
|
case errors.Is(err, txpool.ErrUnderpriced):
|
||||||
|
return true
|
||||||
|
case errors.Is(err, legacypool.ErrTxPoolOverflow):
|
||||||
|
return true
|
||||||
|
case errors.Is(err, legacypool.ErrFutureReplacePending):
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -74,32 +74,22 @@ func New(journalPath string, journalTime time.Duration, chainConfig *params.Chai
|
||||||
|
|
||||||
// Track adds a transaction to the tracked set.
|
// Track adds a transaction to the tracked set.
|
||||||
// Note: blob-type transactions are ignored.
|
// Note: blob-type transactions are ignored.
|
||||||
func (tracker *TxTracker) Track(tx *types.Transaction) error {
|
func (tracker *TxTracker) Track(tx *types.Transaction) {
|
||||||
return tracker.TrackAll([]*types.Transaction{tx})[0]
|
tracker.TrackAll([]*types.Transaction{tx})
|
||||||
}
|
}
|
||||||
|
|
||||||
// TrackAll adds a list of transactions to the tracked set.
|
// TrackAll adds a list of transactions to the tracked set.
|
||||||
// Note: blob-type transactions are ignored.
|
// Note: blob-type transactions are ignored.
|
||||||
func (tracker *TxTracker) TrackAll(txs []*types.Transaction) []error {
|
func (tracker *TxTracker) TrackAll(txs []*types.Transaction) {
|
||||||
tracker.mu.Lock()
|
tracker.mu.Lock()
|
||||||
defer tracker.mu.Unlock()
|
defer tracker.mu.Unlock()
|
||||||
|
|
||||||
var errors []error
|
|
||||||
for _, tx := range txs {
|
for _, tx := range txs {
|
||||||
if tx.Type() == types.BlobTxType {
|
if tx.Type() == types.BlobTxType {
|
||||||
errors = append(errors, nil)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
// Ignore the transactions which are failed for fundamental
|
|
||||||
// validation such as invalid parameters.
|
|
||||||
if err := tracker.pool.ValidateTxBasics(tx); err != nil {
|
|
||||||
log.Debug("Invalid transaction submitted", "hash", tx.Hash(), "err", err)
|
|
||||||
errors = append(errors, err)
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
// If we're already tracking it, it's a no-op
|
// If we're already tracking it, it's a no-op
|
||||||
if _, ok := tracker.all[tx.Hash()]; ok {
|
if _, ok := tracker.all[tx.Hash()]; ok {
|
||||||
errors = append(errors, nil)
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
// Theoretically, checking the error here is unnecessary since sender recovery
|
// Theoretically, checking the error here is unnecessary since sender recovery
|
||||||
|
|
@ -108,11 +98,8 @@ func (tracker *TxTracker) TrackAll(txs []*types.Transaction) []error {
|
||||||
// Therefore, the error is still checked just in case.
|
// Therefore, the error is still checked just in case.
|
||||||
addr, err := types.Sender(tracker.signer, tx)
|
addr, err := types.Sender(tracker.signer, tx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errors = append(errors, err)
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
errors = append(errors, nil)
|
|
||||||
|
|
||||||
tracker.all[tx.Hash()] = tx
|
tracker.all[tx.Hash()] = tx
|
||||||
if tracker.byAddr[addr] == nil {
|
if tracker.byAddr[addr] == nil {
|
||||||
tracker.byAddr[addr] = legacypool.NewSortedMap()
|
tracker.byAddr[addr] = legacypool.NewSortedMap()
|
||||||
|
|
@ -124,7 +111,6 @@ func (tracker *TxTracker) TrackAll(txs []*types.Transaction) []error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
localGauge.Update(int64(len(tracker.all)))
|
localGauge.Update(int64(len(tracker.all)))
|
||||||
return errors
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// recheck checks and returns any transactions that needs to be resubmitted.
|
// recheck checks and returns any transactions that needs to be resubmitted.
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,6 @@
|
||||||
package locals
|
package locals
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"math/big"
|
"math/big"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
@ -91,10 +90,12 @@ func (env *testEnv) close() {
|
||||||
env.chain.Stop()
|
env.chain.Stop()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// nolint:unused
|
||||||
func (env *testEnv) setGasTip(gasTip uint64) {
|
func (env *testEnv) setGasTip(gasTip uint64) {
|
||||||
env.pool.SetGasTip(new(big.Int).SetUint64(gasTip))
|
env.pool.SetGasTip(new(big.Int).SetUint64(gasTip))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// nolint:unused
|
||||||
func (env *testEnv) makeTx(nonce uint64, gasPrice *big.Int) *types.Transaction {
|
func (env *testEnv) makeTx(nonce uint64, gasPrice *big.Int) *types.Transaction {
|
||||||
if nonce == 0 {
|
if nonce == 0 {
|
||||||
head := env.chain.CurrentHeader()
|
head := env.chain.CurrentHeader()
|
||||||
|
|
@ -121,6 +122,7 @@ func (env *testEnv) makeTxs(n int) []*types.Transaction {
|
||||||
return txs
|
return txs
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// nolint:unused
|
||||||
func (env *testEnv) commit() {
|
func (env *testEnv) commit() {
|
||||||
head := env.chain.CurrentBlock()
|
head := env.chain.CurrentBlock()
|
||||||
block := env.chain.GetBlock(head.Hash(), head.Number.Uint64())
|
block := env.chain.GetBlock(head.Hash(), head.Number.Uint64())
|
||||||
|
|
@ -137,60 +139,6 @@ func (env *testEnv) commit() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRejectInvalids(t *testing.T) {
|
|
||||||
env := newTestEnv(t, 10, 0, "")
|
|
||||||
defer env.close()
|
|
||||||
|
|
||||||
var cases = []struct {
|
|
||||||
gasTip uint64
|
|
||||||
tx *types.Transaction
|
|
||||||
expErr error
|
|
||||||
commit bool
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
tx: env.makeTx(5, nil), // stale
|
|
||||||
expErr: core.ErrNonceTooLow,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
tx: env.makeTx(11, nil), // future transaction
|
|
||||||
expErr: nil,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
gasTip: params.GWei,
|
|
||||||
tx: env.makeTx(0, new(big.Int).SetUint64(params.GWei/2)), // low price
|
|
||||||
expErr: txpool.ErrUnderpriced,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
tx: types.NewTransaction(10, common.Address{0x00}, big.NewInt(1000), params.TxGas, big.NewInt(params.GWei), nil), // invalid signature
|
|
||||||
expErr: types.ErrInvalidSig,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
commit: true,
|
|
||||||
tx: env.makeTx(10, nil), // stale
|
|
||||||
expErr: core.ErrNonceTooLow,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
tx: env.makeTx(11, nil),
|
|
||||||
expErr: nil,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for i, c := range cases {
|
|
||||||
if c.gasTip != 0 {
|
|
||||||
env.setGasTip(c.gasTip)
|
|
||||||
}
|
|
||||||
if c.commit {
|
|
||||||
env.commit()
|
|
||||||
}
|
|
||||||
gotErr := env.tracker.Track(c.tx)
|
|
||||||
if c.expErr == nil && gotErr != nil {
|
|
||||||
t.Fatalf("%d, unexpected error: %v", i, gotErr)
|
|
||||||
}
|
|
||||||
if c.expErr != nil && !errors.Is(gotErr, c.expErr) {
|
|
||||||
t.Fatalf("%d, unexpected error, want: %v, got: %v", i, c.expErr, gotErr)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestResubmit(t *testing.T) {
|
func TestResubmit(t *testing.T) {
|
||||||
env := newTestEnv(t, 10, 0, "")
|
env := newTestEnv(t, 10, 0, "")
|
||||||
defer env.close()
|
defer env.close()
|
||||||
|
|
|
||||||
|
|
@ -186,13 +186,15 @@ func (p *TxPool) loop(head *types.Header) {
|
||||||
// Try to inject a busy marker and start a reset if successful
|
// Try to inject a busy marker and start a reset if successful
|
||||||
select {
|
select {
|
||||||
case resetBusy <- struct{}{}:
|
case resetBusy <- struct{}{}:
|
||||||
statedb, err := p.chain.StateAt(newHead.Root)
|
// Updates the statedb with the new chain head. The head state may be
|
||||||
if err != nil {
|
// unavailable if the initial state sync has not yet completed.
|
||||||
log.Crit("Failed to reset txpool state", "err", err)
|
if statedb, err := p.chain.StateAt(newHead.Root); err != nil {
|
||||||
}
|
log.Error("Failed to reset txpool state", "err", err)
|
||||||
|
} else {
|
||||||
p.stateLock.Lock()
|
p.stateLock.Lock()
|
||||||
p.state = statedb
|
p.state = statedb
|
||||||
p.stateLock.Unlock()
|
p.stateLock.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
// Busy marker injected, start a new subpool reset
|
// Busy marker injected, start a new subpool reset
|
||||||
go func(oldHead, newHead *types.Header) {
|
go func(oldHead, newHead *types.Header) {
|
||||||
|
|
@ -322,31 +324,6 @@ func (p *TxPool) GetBlobs(vhashes []common.Hash) ([]*kzg4844.Blob, []*kzg4844.Pr
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ValidateTxBasics checks whether a transaction is valid according to the consensus
|
|
||||||
// rules, but does not check state-dependent validation such as sufficient balance.
|
|
||||||
func (p *TxPool) ValidateTxBasics(tx *types.Transaction) error {
|
|
||||||
addr, err := types.Sender(p.signer, tx)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
// Reject transactions with stale nonce. Gapped-nonce future transactions
|
|
||||||
// are considered valid and will be handled by the subpool according to its
|
|
||||||
// internal policy.
|
|
||||||
p.stateLock.RLock()
|
|
||||||
nonce := p.state.GetNonce(addr)
|
|
||||||
p.stateLock.RUnlock()
|
|
||||||
|
|
||||||
if nonce > tx.Nonce() {
|
|
||||||
return core.ErrNonceTooLow
|
|
||||||
}
|
|
||||||
for _, subpool := range p.subpools {
|
|
||||||
if subpool.Filter(tx) {
|
|
||||||
return subpool.ValidateTxBasics(tx)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return fmt.Errorf("%w: received type %d", core.ErrTxTypeNotSupported, tx.Type())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add enqueues a batch of transactions into the pool if they are valid. Due
|
// Add enqueues a batch of transactions into the pool if they are valid. Due
|
||||||
// to the large transaction churn, add may postpone fully integrating the tx
|
// to the large transaction churn, add may postpone fully integrating the tx
|
||||||
// to a later point to batch multiple ones together.
|
// to a later point to batch multiple ones together.
|
||||||
|
|
|
||||||
|
|
@ -131,12 +131,12 @@ func ValidateTransaction(tx *types.Transaction, head *types.Header, signer types
|
||||||
}
|
}
|
||||||
// Ensure the gasprice is high enough to cover the requirement of the calling pool
|
// Ensure the gasprice is high enough to cover the requirement of the calling pool
|
||||||
if tx.GasTipCapIntCmp(opts.MinTip) < 0 {
|
if tx.GasTipCapIntCmp(opts.MinTip) < 0 {
|
||||||
return fmt.Errorf("%w: gas tip cap %v, minimum needed %v", ErrUnderpriced, tx.GasTipCap(), opts.MinTip)
|
return fmt.Errorf("%w: gas tip cap %v, minimum needed %v", ErrTxGasPriceTooLow, tx.GasTipCap(), opts.MinTip)
|
||||||
}
|
}
|
||||||
if tx.Type() == types.BlobTxType {
|
if tx.Type() == types.BlobTxType {
|
||||||
// Ensure the blob fee cap satisfies the minimum blob gas price
|
// Ensure the blob fee cap satisfies the minimum blob gas price
|
||||||
if tx.BlobGasFeeCapIntCmp(blobTxMinBlobGasPrice) < 0 {
|
if tx.BlobGasFeeCapIntCmp(blobTxMinBlobGasPrice) < 0 {
|
||||||
return fmt.Errorf("%w: blob fee cap %v, minimum needed %v", ErrUnderpriced, tx.BlobGasFeeCap(), blobTxMinBlobGasPrice)
|
return fmt.Errorf("%w: blob fee cap %v, minimum needed %v", ErrTxGasPriceTooLow, tx.BlobGasFeeCap(), blobTxMinBlobGasPrice)
|
||||||
}
|
}
|
||||||
sidecar := tx.BlobTxSidecar()
|
sidecar := tx.BlobTxSidecar()
|
||||||
if sidecar == nil {
|
if sidecar == nil {
|
||||||
|
|
|
||||||
|
|
@ -29,12 +29,13 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/consensus/misc/eip4844"
|
"github.com/ethereum/go-ethereum/consensus/misc/eip4844"
|
||||||
"github.com/ethereum/go-ethereum/core"
|
"github.com/ethereum/go-ethereum/core"
|
||||||
"github.com/ethereum/go-ethereum/core/filtermaps"
|
"github.com/ethereum/go-ethereum/core/filtermaps"
|
||||||
|
"github.com/ethereum/go-ethereum/core/history"
|
||||||
"github.com/ethereum/go-ethereum/core/rawdb"
|
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||||
"github.com/ethereum/go-ethereum/core/state"
|
"github.com/ethereum/go-ethereum/core/state"
|
||||||
"github.com/ethereum/go-ethereum/core/txpool"
|
"github.com/ethereum/go-ethereum/core/txpool"
|
||||||
|
"github.com/ethereum/go-ethereum/core/txpool/locals"
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
"github.com/ethereum/go-ethereum/core/vm"
|
"github.com/ethereum/go-ethereum/core/vm"
|
||||||
"github.com/ethereum/go-ethereum/eth/ethconfig"
|
|
||||||
"github.com/ethereum/go-ethereum/eth/gasprice"
|
"github.com/ethereum/go-ethereum/eth/gasprice"
|
||||||
"github.com/ethereum/go-ethereum/eth/tracers"
|
"github.com/ethereum/go-ethereum/eth/tracers"
|
||||||
"github.com/ethereum/go-ethereum/ethdb"
|
"github.com/ethereum/go-ethereum/ethdb"
|
||||||
|
|
@ -156,7 +157,7 @@ func (b *EthAPIBackend) BlockByNumber(ctx context.Context, number rpc.BlockNumbe
|
||||||
}
|
}
|
||||||
block := b.eth.blockchain.GetBlockByNumber(bn)
|
block := b.eth.blockchain.GetBlockByNumber(bn)
|
||||||
if block == nil && bn < b.HistoryPruningCutoff() {
|
if block == nil && bn < b.HistoryPruningCutoff() {
|
||||||
return nil, ðconfig.PrunedHistoryError{}
|
return nil, &history.PrunedHistoryError{}
|
||||||
}
|
}
|
||||||
return block, nil
|
return block, nil
|
||||||
}
|
}
|
||||||
|
|
@ -168,7 +169,7 @@ func (b *EthAPIBackend) BlockByHash(ctx context.Context, hash common.Hash) (*typ
|
||||||
}
|
}
|
||||||
block := b.eth.blockchain.GetBlock(hash, *number)
|
block := b.eth.blockchain.GetBlock(hash, *number)
|
||||||
if block == nil && *number < b.HistoryPruningCutoff() {
|
if block == nil && *number < b.HistoryPruningCutoff() {
|
||||||
return nil, ðconfig.PrunedHistoryError{}
|
return nil, &history.PrunedHistoryError{}
|
||||||
}
|
}
|
||||||
return block, nil
|
return block, nil
|
||||||
}
|
}
|
||||||
|
|
@ -181,7 +182,7 @@ func (b *EthAPIBackend) GetBody(ctx context.Context, hash common.Hash, number rp
|
||||||
body := b.eth.blockchain.GetBody(hash)
|
body := b.eth.blockchain.GetBody(hash)
|
||||||
if body == nil {
|
if body == nil {
|
||||||
if uint64(number) < b.HistoryPruningCutoff() {
|
if uint64(number) < b.HistoryPruningCutoff() {
|
||||||
return nil, ðconfig.PrunedHistoryError{}
|
return nil, &history.PrunedHistoryError{}
|
||||||
}
|
}
|
||||||
return nil, errors.New("block body not found")
|
return nil, errors.New("block body not found")
|
||||||
}
|
}
|
||||||
|
|
@ -203,7 +204,7 @@ func (b *EthAPIBackend) BlockByNumberOrHash(ctx context.Context, blockNrOrHash r
|
||||||
block := b.eth.blockchain.GetBlock(hash, header.Number.Uint64())
|
block := b.eth.blockchain.GetBlock(hash, header.Number.Uint64())
|
||||||
if block == nil {
|
if block == nil {
|
||||||
if header.Number.Uint64() < b.HistoryPruningCutoff() {
|
if header.Number.Uint64() < b.HistoryPruningCutoff() {
|
||||||
return nil, ðconfig.PrunedHistoryError{}
|
return nil, &history.PrunedHistoryError{}
|
||||||
}
|
}
|
||||||
return nil, errors.New("header found, but block body is missing")
|
return nil, errors.New("header found, but block body is missing")
|
||||||
}
|
}
|
||||||
|
|
@ -307,19 +308,24 @@ func (b *EthAPIBackend) SubscribeLogsEvent(ch chan<- []*types.Log) event.Subscri
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *EthAPIBackend) SendTx(ctx context.Context, signedTx *types.Transaction) error {
|
func (b *EthAPIBackend) SendTx(ctx context.Context, signedTx *types.Transaction) error {
|
||||||
locals := b.eth.localTxTracker
|
|
||||||
if locals != nil {
|
|
||||||
if err := locals.Track(signedTx); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// No error will be returned to user if the transaction fails stateful
|
|
||||||
// validation (e.g., no available slot), as the locally submitted transactions
|
|
||||||
// may be resubmitted later via the local tracker.
|
|
||||||
err := b.eth.txPool.Add([]*types.Transaction{signedTx}, false)[0]
|
err := b.eth.txPool.Add([]*types.Transaction{signedTx}, false)[0]
|
||||||
if err != nil && locals == nil {
|
|
||||||
|
// If the local transaction tracker is not configured, returns whatever
|
||||||
|
// returned from the txpool.
|
||||||
|
if b.eth.localTxTracker == nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
// If the transaction fails with an error indicating it is invalid, or if there is
|
||||||
|
// very little chance it will be accepted later (e.g., the gas price is below the
|
||||||
|
// configured minimum, or the sender has insufficient funds to cover the cost),
|
||||||
|
// propagate the error to the user.
|
||||||
|
if err != nil && !locals.IsTemporaryReject(err) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// No error will be returned to user if the transaction fails with a temporary
|
||||||
|
// error and might be accepted later (e.g., the transaction pool is full).
|
||||||
|
// Locally submitted transactions will be resubmitted later via the local tracker.
|
||||||
|
b.eth.localTxTracker.Track(signedTx)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -437,6 +443,14 @@ func (b *EthAPIBackend) RPCTxFeeCap() float64 {
|
||||||
return b.eth.config.RPCTxFeeCap
|
return b.eth.config.RPCTxFeeCap
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (b *EthAPIBackend) CurrentView() *filtermaps.ChainView {
|
||||||
|
head := b.eth.blockchain.CurrentBlock()
|
||||||
|
if head == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return filtermaps.NewChainView(b.eth.blockchain, head.Number.Uint64(), head.Hash())
|
||||||
|
}
|
||||||
|
|
||||||
func (b *EthAPIBackend) NewMatcherBackend() filtermaps.MatcherBackend {
|
func (b *EthAPIBackend) NewMatcherBackend() filtermaps.MatcherBackend {
|
||||||
return b.eth.filterMaps.NewMatcherBackend()
|
return b.eth.filterMaps.NewMatcherBackend()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
157
eth/api_backend_test.go
Normal file
157
eth/api_backend_test.go
Normal file
|
|
@ -0,0 +1,157 @@
|
||||||
|
// 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 eth
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto/ecdsa"
|
||||||
|
"errors"
|
||||||
|
"math/big"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/ethereum/go-ethereum/consensus/beacon"
|
||||||
|
"github.com/ethereum/go-ethereum/consensus/ethash"
|
||||||
|
"github.com/ethereum/go-ethereum/core"
|
||||||
|
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||||
|
"github.com/ethereum/go-ethereum/core/txpool"
|
||||||
|
"github.com/ethereum/go-ethereum/core/txpool/blobpool"
|
||||||
|
"github.com/ethereum/go-ethereum/core/txpool/legacypool"
|
||||||
|
"github.com/ethereum/go-ethereum/core/txpool/locals"
|
||||||
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
|
"github.com/ethereum/go-ethereum/core/vm"
|
||||||
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
|
"github.com/ethereum/go-ethereum/params"
|
||||||
|
"github.com/holiman/uint256"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
|
||||||
|
address = crypto.PubkeyToAddress(key.PublicKey)
|
||||||
|
funds = big.NewInt(1000_000_000_000_000)
|
||||||
|
gspec = &core.Genesis{
|
||||||
|
Config: params.MergedTestChainConfig,
|
||||||
|
Alloc: types.GenesisAlloc{
|
||||||
|
address: {Balance: funds},
|
||||||
|
},
|
||||||
|
Difficulty: common.Big0,
|
||||||
|
BaseFee: big.NewInt(params.InitialBaseFee),
|
||||||
|
}
|
||||||
|
signer = types.LatestSignerForChainID(gspec.Config.ChainID)
|
||||||
|
)
|
||||||
|
|
||||||
|
func initBackend(withLocal bool) *EthAPIBackend {
|
||||||
|
var (
|
||||||
|
// Create a database pre-initialize with a genesis block
|
||||||
|
db = rawdb.NewMemoryDatabase()
|
||||||
|
engine = beacon.New(ethash.NewFaker())
|
||||||
|
)
|
||||||
|
chain, _ := core.NewBlockChain(db, nil, gspec, nil, engine, vm.Config{}, nil)
|
||||||
|
|
||||||
|
txconfig := legacypool.DefaultConfig
|
||||||
|
txconfig.Journal = "" // Don't litter the disk with test journals
|
||||||
|
|
||||||
|
blobPool := blobpool.New(blobpool.Config{Datadir: ""}, chain, nil)
|
||||||
|
legacyPool := legacypool.New(txconfig, chain)
|
||||||
|
txpool, _ := txpool.New(txconfig.PriceLimit, chain, []txpool.SubPool{legacyPool, blobPool})
|
||||||
|
|
||||||
|
eth := &Ethereum{
|
||||||
|
blockchain: chain,
|
||||||
|
txPool: txpool,
|
||||||
|
}
|
||||||
|
if withLocal {
|
||||||
|
eth.localTxTracker = locals.New("", time.Minute, gspec.Config, txpool)
|
||||||
|
}
|
||||||
|
return &EthAPIBackend{
|
||||||
|
eth: eth,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeTx(nonce uint64, gasPrice *big.Int, amount *big.Int, key *ecdsa.PrivateKey) *types.Transaction {
|
||||||
|
if gasPrice == nil {
|
||||||
|
gasPrice = big.NewInt(params.GWei)
|
||||||
|
}
|
||||||
|
if amount == nil {
|
||||||
|
amount = big.NewInt(1000)
|
||||||
|
}
|
||||||
|
tx, _ := types.SignTx(types.NewTransaction(nonce, common.Address{0x00}, amount, params.TxGas, gasPrice, nil), signer, key)
|
||||||
|
return tx
|
||||||
|
}
|
||||||
|
|
||||||
|
type unsignedAuth struct {
|
||||||
|
nonce uint64
|
||||||
|
key *ecdsa.PrivateKey
|
||||||
|
}
|
||||||
|
|
||||||
|
func pricedSetCodeTx(nonce uint64, gaslimit uint64, gasFee, tip *uint256.Int, key *ecdsa.PrivateKey, unsigned []unsignedAuth) *types.Transaction {
|
||||||
|
var authList []types.SetCodeAuthorization
|
||||||
|
for _, u := range unsigned {
|
||||||
|
auth, _ := types.SignSetCode(u.key, types.SetCodeAuthorization{
|
||||||
|
ChainID: *uint256.MustFromBig(gspec.Config.ChainID),
|
||||||
|
Address: common.Address{0x42},
|
||||||
|
Nonce: u.nonce,
|
||||||
|
})
|
||||||
|
authList = append(authList, auth)
|
||||||
|
}
|
||||||
|
return pricedSetCodeTxWithAuth(nonce, gaslimit, gasFee, tip, key, authList)
|
||||||
|
}
|
||||||
|
|
||||||
|
func pricedSetCodeTxWithAuth(nonce uint64, gaslimit uint64, gasFee, tip *uint256.Int, key *ecdsa.PrivateKey, authList []types.SetCodeAuthorization) *types.Transaction {
|
||||||
|
return types.MustSignNewTx(key, signer, &types.SetCodeTx{
|
||||||
|
ChainID: uint256.MustFromBig(gspec.Config.ChainID),
|
||||||
|
Nonce: nonce,
|
||||||
|
GasTipCap: tip,
|
||||||
|
GasFeeCap: gasFee,
|
||||||
|
Gas: gaslimit,
|
||||||
|
To: common.Address{},
|
||||||
|
Value: uint256.NewInt(100),
|
||||||
|
Data: nil,
|
||||||
|
AccessList: nil,
|
||||||
|
AuthList: authList,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSendTx(t *testing.T) {
|
||||||
|
testSendTx(t, false)
|
||||||
|
testSendTx(t, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testSendTx(t *testing.T, withLocal bool) {
|
||||||
|
b := initBackend(withLocal)
|
||||||
|
|
||||||
|
txA := pricedSetCodeTx(0, 250000, uint256.NewInt(params.GWei), uint256.NewInt(params.GWei), key, []unsignedAuth{
|
||||||
|
{
|
||||||
|
nonce: 0,
|
||||||
|
key: key,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
b.SendTx(context.Background(), txA)
|
||||||
|
|
||||||
|
txB := makeTx(1, nil, nil, key)
|
||||||
|
err := b.SendTx(context.Background(), txB)
|
||||||
|
|
||||||
|
if withLocal {
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Unexpected error sending tx: %v", err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if !errors.Is(err, txpool.ErrInflightTxLimitReached) {
|
||||||
|
t.Fatalf("Unexpected error, want: %v, got: %v", txpool.ErrInflightTxLimitReached, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -76,6 +76,7 @@ type Ethereum struct {
|
||||||
|
|
||||||
handler *handler
|
handler *handler
|
||||||
discmix *enode.FairMix
|
discmix *enode.FairMix
|
||||||
|
dropper *dropper
|
||||||
|
|
||||||
// DB interfaces
|
// DB interfaces
|
||||||
chainDb ethdb.Database // Block chain database
|
chainDb ethdb.Database // Block chain database
|
||||||
|
|
@ -144,7 +145,7 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) {
|
||||||
|
|
||||||
// Here we determine genesis hash and active ChainConfig.
|
// Here we determine genesis hash and active ChainConfig.
|
||||||
// We need these to figure out the consensus parameters and to set up history pruning.
|
// We need these to figure out the consensus parameters and to set up history pruning.
|
||||||
chainConfig, genesisHash, err := core.LoadChainConfig(chainDb, config.Genesis)
|
chainConfig, _, err := core.LoadChainConfig(chainDb, config.Genesis)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -152,22 +153,6 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate history pruning configuration.
|
|
||||||
var (
|
|
||||||
cutoffNumber uint64
|
|
||||||
cutoffHash common.Hash
|
|
||||||
)
|
|
||||||
if config.HistoryMode == ethconfig.PostMergeHistory {
|
|
||||||
prunecfg, ok := ethconfig.HistoryPrunePoints[genesisHash]
|
|
||||||
if !ok {
|
|
||||||
return nil, fmt.Errorf("no history pruning point is defined for genesis %x", genesisHash)
|
|
||||||
}
|
|
||||||
cutoffNumber = prunecfg.BlockNumber
|
|
||||||
cutoffHash = prunecfg.BlockHash
|
|
||||||
log.Info("Chain cutoff configured", "number", cutoffNumber, "hash", cutoffHash)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set networkID to chainID by default.
|
// Set networkID to chainID by default.
|
||||||
networkID := config.NetworkId
|
networkID := config.NetworkId
|
||||||
if networkID == 0 {
|
if networkID == 0 {
|
||||||
|
|
@ -194,6 +179,7 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) {
|
||||||
}
|
}
|
||||||
log.Info("Initialising Ethereum protocol", "network", networkID, "dbversion", dbVer)
|
log.Info("Initialising Ethereum protocol", "network", networkID, "dbversion", dbVer)
|
||||||
|
|
||||||
|
// Create BlockChain object.
|
||||||
if !config.SkipBcVersionCheck {
|
if !config.SkipBcVersionCheck {
|
||||||
if bcVersion != nil && *bcVersion > core.BlockChainVersion {
|
if bcVersion != nil && *bcVersion > core.BlockChainVersion {
|
||||||
return nil, fmt.Errorf("database version is v%d, Geth %s only supports v%d", *bcVersion, version.WithMeta, core.BlockChainVersion)
|
return nil, fmt.Errorf("database version is v%d, Geth %s only supports v%d", *bcVersion, version.WithMeta, core.BlockChainVersion)
|
||||||
|
|
@ -218,8 +204,7 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) {
|
||||||
Preimages: config.Preimages,
|
Preimages: config.Preimages,
|
||||||
StateHistory: config.StateHistory,
|
StateHistory: config.StateHistory,
|
||||||
StateScheme: scheme,
|
StateScheme: scheme,
|
||||||
HistoryPruningCutoffNumber: cutoffNumber,
|
ChainHistoryMode: config.HistoryMode,
|
||||||
HistoryPruningCutoffHash: cutoffHash,
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
if config.VMTrace != "" {
|
if config.VMTrace != "" {
|
||||||
|
|
@ -245,6 +230,8 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Initialize filtermaps log index.
|
||||||
fmConfig := filtermaps.Config{
|
fmConfig := filtermaps.Config{
|
||||||
History: config.LogHistory,
|
History: config.LogHistory,
|
||||||
Disabled: config.LogNoHistory,
|
Disabled: config.LogNoHistory,
|
||||||
|
|
@ -260,6 +247,7 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) {
|
||||||
eth.filterMaps = filtermaps.NewFilterMaps(chainDb, chainView, historyCutoff, finalBlock, filtermaps.DefaultParams, fmConfig)
|
eth.filterMaps = filtermaps.NewFilterMaps(chainDb, chainView, historyCutoff, finalBlock, filtermaps.DefaultParams, fmConfig)
|
||||||
eth.closeFilterMaps = make(chan chan struct{})
|
eth.closeFilterMaps = make(chan chan struct{})
|
||||||
|
|
||||||
|
// TxPool
|
||||||
if config.TxPool.Journal != "" {
|
if config.TxPool.Journal != "" {
|
||||||
config.TxPool.Journal = stack.ResolvePath(config.TxPool.Journal)
|
config.TxPool.Journal = stack.ResolvePath(config.TxPool.Journal)
|
||||||
}
|
}
|
||||||
|
|
@ -284,6 +272,7 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) {
|
||||||
eth.localTxTracker = locals.New(config.TxPool.Journal, rejournal, eth.blockchain.Config(), eth.txPool)
|
eth.localTxTracker = locals.New(config.TxPool.Journal, rejournal, eth.blockchain.Config(), eth.txPool)
|
||||||
stack.RegisterLifecycle(eth.localTxTracker)
|
stack.RegisterLifecycle(eth.localTxTracker)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Permit the downloader to use the trie cache allowance during fast sync
|
// Permit the downloader to use the trie cache allowance during fast sync
|
||||||
cacheLimit := cacheConfig.TrieCleanLimit + cacheConfig.TrieDirtyLimit + cacheConfig.SnapshotLimit
|
cacheLimit := cacheConfig.TrieCleanLimit + cacheConfig.TrieDirtyLimit + cacheConfig.SnapshotLimit
|
||||||
if eth.handler, err = newHandler(&handlerConfig{
|
if eth.handler, err = newHandler(&handlerConfig{
|
||||||
|
|
@ -300,6 +289,8 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
eth.dropper = newDropper(eth.p2pServer.MaxDialedConns(), eth.p2pServer.MaxInboundConns())
|
||||||
|
|
||||||
eth.miner = miner.New(eth, config.Miner, eth.engine)
|
eth.miner = miner.New(eth, config.Miner, eth.engine)
|
||||||
eth.miner.SetExtra(makeExtraData(config.Miner.ExtraData))
|
eth.miner.SetExtra(makeExtraData(config.Miner.ExtraData))
|
||||||
eth.miner.SetPrioAddresses(config.TxPool.Locals)
|
eth.miner.SetPrioAddresses(config.TxPool.Locals)
|
||||||
|
|
@ -410,6 +401,9 @@ func (s *Ethereum) Start() error {
|
||||||
// Start the networking layer
|
// Start the networking layer
|
||||||
s.handler.Start(s.p2pServer.MaxPeers)
|
s.handler.Start(s.p2pServer.MaxPeers)
|
||||||
|
|
||||||
|
// Start the connection manager
|
||||||
|
s.dropper.Start(s.p2pServer, func() bool { return !s.Synced() })
|
||||||
|
|
||||||
// start log indexer
|
// start log indexer
|
||||||
s.filterMaps.Start()
|
s.filterMaps.Start()
|
||||||
go s.updateFilterMapsHeads()
|
go s.updateFilterMapsHeads()
|
||||||
|
|
@ -511,6 +505,7 @@ func (s *Ethereum) setupDiscovery() error {
|
||||||
func (s *Ethereum) Stop() error {
|
func (s *Ethereum) Stop() error {
|
||||||
// Stop all the peer-related stuff first.
|
// Stop all the peer-related stuff first.
|
||||||
s.discmix.Close()
|
s.discmix.Close()
|
||||||
|
s.dropper.Stop()
|
||||||
s.handler.Stop()
|
s.handler.Stop()
|
||||||
|
|
||||||
// Then stop everything else.
|
// Then stop everything else.
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ import (
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"math"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
|
@ -124,9 +125,13 @@ func NewSimulatedBeacon(period uint64, feeRecipient common.Address, eth *eth.Eth
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// cap the dev mode period to a reasonable maximum value to avoid
|
||||||
|
// overflowing the time.Duration (int64) that it will occupy
|
||||||
|
const maxPeriod = uint64(math.MaxInt64 / time.Second)
|
||||||
return &SimulatedBeacon{
|
return &SimulatedBeacon{
|
||||||
eth: eth,
|
eth: eth,
|
||||||
period: period,
|
period: min(period, maxPeriod),
|
||||||
shutdownCh: make(chan struct{}),
|
shutdownCh: make(chan struct{}),
|
||||||
engineAPI: engineAPI,
|
engineAPI: engineAPI,
|
||||||
lastBlockTime: block.Time,
|
lastBlockTime: block.Time,
|
||||||
|
|
|
||||||
167
eth/dropper.go
Normal file
167
eth/dropper.go
Normal file
|
|
@ -0,0 +1,167 @@
|
||||||
|
// 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 eth
|
||||||
|
|
||||||
|
import (
|
||||||
|
mrand "math/rand"
|
||||||
|
"slices"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/ethereum/go-ethereum/common/mclock"
|
||||||
|
"github.com/ethereum/go-ethereum/log"
|
||||||
|
"github.com/ethereum/go-ethereum/metrics"
|
||||||
|
"github.com/ethereum/go-ethereum/p2p"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// Interval between peer drop events (uniform between min and max)
|
||||||
|
peerDropIntervalMin = 3 * time.Minute
|
||||||
|
// Interval between peer drop events (uniform between min and max)
|
||||||
|
peerDropIntervalMax = 7 * time.Minute
|
||||||
|
// Avoid dropping peers for some time after connection
|
||||||
|
doNotDropBefore = 10 * time.Minute
|
||||||
|
// How close to max should we initiate the drop timer. O should be fine,
|
||||||
|
// dropping when no more peers can be added. Larger numbers result in more
|
||||||
|
// aggressive drop behavior.
|
||||||
|
peerDropThreshold = 0
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// droppedInbound is the number of inbound peers dropped
|
||||||
|
droppedInbound = metrics.NewRegisteredMeter("eth/dropper/inbound", nil)
|
||||||
|
// droppedOutbound is the number of outbound peers dropped
|
||||||
|
droppedOutbound = metrics.NewRegisteredMeter("eth/dropper/outbound", nil)
|
||||||
|
)
|
||||||
|
|
||||||
|
// dropper monitors the state of the peer pool and makes changes as follows:
|
||||||
|
// - during sync the Downloader handles peer connections, so dropper is disabled
|
||||||
|
// - if not syncing and the peer count is close to the limit, it drops peers
|
||||||
|
// randomly every peerDropInterval to make space for new peers
|
||||||
|
// - peers are dropped separately from the inboud pool and from the dialed pool
|
||||||
|
type dropper struct {
|
||||||
|
maxDialPeers int // maximum number of dialed peers
|
||||||
|
maxInboundPeers int // maximum number of inbound peers
|
||||||
|
peersFunc getPeersFunc
|
||||||
|
syncingFunc getSyncingFunc
|
||||||
|
|
||||||
|
// peerDropTimer introduces churn if we are close to limit capacity.
|
||||||
|
// We handle Dialed and Inbound connections separately
|
||||||
|
peerDropTimer *time.Timer
|
||||||
|
|
||||||
|
wg sync.WaitGroup // wg for graceful shutdown
|
||||||
|
shutdownCh chan struct{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Callback type to get the list of connected peers.
|
||||||
|
type getPeersFunc func() []*p2p.Peer
|
||||||
|
|
||||||
|
// Callback type to get syncing status.
|
||||||
|
// Returns true while syncing, false when synced.
|
||||||
|
type getSyncingFunc func() bool
|
||||||
|
|
||||||
|
func newDropper(maxDialPeers, maxInboundPeers int) *dropper {
|
||||||
|
cm := &dropper{
|
||||||
|
maxDialPeers: maxDialPeers,
|
||||||
|
maxInboundPeers: maxInboundPeers,
|
||||||
|
peerDropTimer: time.NewTimer(randomDuration(peerDropIntervalMin, peerDropIntervalMax)),
|
||||||
|
shutdownCh: make(chan struct{}),
|
||||||
|
}
|
||||||
|
if peerDropIntervalMin > peerDropIntervalMax {
|
||||||
|
panic("peerDropIntervalMin duration must be less than or equal to peerDropIntervalMax duration")
|
||||||
|
}
|
||||||
|
return cm
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start the dropper.
|
||||||
|
func (cm *dropper) Start(srv *p2p.Server, syncingFunc getSyncingFunc) {
|
||||||
|
cm.peersFunc = srv.Peers
|
||||||
|
cm.syncingFunc = syncingFunc
|
||||||
|
cm.wg.Add(1)
|
||||||
|
go cm.loop()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stop the dropper.
|
||||||
|
func (cm *dropper) Stop() {
|
||||||
|
cm.peerDropTimer.Stop()
|
||||||
|
close(cm.shutdownCh)
|
||||||
|
cm.wg.Wait()
|
||||||
|
}
|
||||||
|
|
||||||
|
// dropRandomPeer selects one of the peers randomly and drops it from the peer pool.
|
||||||
|
func (cm *dropper) dropRandomPeer() bool {
|
||||||
|
peers := cm.peersFunc()
|
||||||
|
var numInbound int
|
||||||
|
for _, p := range peers {
|
||||||
|
if p.Inbound() {
|
||||||
|
numInbound++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
numDialed := len(peers) - numInbound
|
||||||
|
|
||||||
|
selectDoNotDrop := func(p *p2p.Peer) bool {
|
||||||
|
// Avoid dropping trusted and static peers, or recent peers.
|
||||||
|
// Only drop peers if their respective category (dialed/inbound)
|
||||||
|
// is close to limit capacity.
|
||||||
|
return p.Trusted() || p.StaticDialed() ||
|
||||||
|
p.Lifetime() < mclock.AbsTime(doNotDropBefore) ||
|
||||||
|
(p.DynDialed() && cm.maxDialPeers-numDialed > peerDropThreshold) ||
|
||||||
|
(p.Inbound() && cm.maxInboundPeers-numInbound > peerDropThreshold)
|
||||||
|
}
|
||||||
|
|
||||||
|
droppable := slices.DeleteFunc(peers, selectDoNotDrop)
|
||||||
|
if len(droppable) > 0 {
|
||||||
|
p := droppable[mrand.Intn(len(droppable))]
|
||||||
|
log.Debug("Dropping random peer", "inbound", p.Inbound(),
|
||||||
|
"id", p.ID(), "duration", common.PrettyDuration(p.Lifetime()), "peercountbefore", len(peers))
|
||||||
|
p.Disconnect(p2p.DiscUselessPeer)
|
||||||
|
if p.Inbound() {
|
||||||
|
droppedInbound.Mark(1)
|
||||||
|
} else {
|
||||||
|
droppedOutbound.Mark(1)
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// randomDuration generates a random duration between min and max.
|
||||||
|
func randomDuration(min, max time.Duration) time.Duration {
|
||||||
|
if min > max {
|
||||||
|
panic("min duration must be less than or equal to max duration")
|
||||||
|
}
|
||||||
|
return time.Duration(mrand.Int63n(int64(max-min)) + int64(min))
|
||||||
|
}
|
||||||
|
|
||||||
|
// loop is the main loop of the connection dropper.
|
||||||
|
func (cm *dropper) loop() {
|
||||||
|
defer cm.wg.Done()
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-cm.peerDropTimer.C:
|
||||||
|
// Drop a random peer if we are not syncing and the peer count is close to the limit.
|
||||||
|
if !cm.syncingFunc() {
|
||||||
|
cm.dropRandomPeer()
|
||||||
|
}
|
||||||
|
cm.peerDropTimer.Reset(randomDuration(peerDropIntervalMin, peerDropIntervalMax))
|
||||||
|
case <-cm.shutdownCh:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -27,6 +27,7 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/consensus/clique"
|
"github.com/ethereum/go-ethereum/consensus/clique"
|
||||||
"github.com/ethereum/go-ethereum/consensus/ethash"
|
"github.com/ethereum/go-ethereum/consensus/ethash"
|
||||||
"github.com/ethereum/go-ethereum/core"
|
"github.com/ethereum/go-ethereum/core"
|
||||||
|
"github.com/ethereum/go-ethereum/core/history"
|
||||||
"github.com/ethereum/go-ethereum/core/txpool/blobpool"
|
"github.com/ethereum/go-ethereum/core/txpool/blobpool"
|
||||||
"github.com/ethereum/go-ethereum/core/txpool/legacypool"
|
"github.com/ethereum/go-ethereum/core/txpool/legacypool"
|
||||||
"github.com/ethereum/go-ethereum/eth/gasprice"
|
"github.com/ethereum/go-ethereum/eth/gasprice"
|
||||||
|
|
@ -48,7 +49,7 @@ var FullNodeGPO = gasprice.Config{
|
||||||
|
|
||||||
// Defaults contains default settings for use on the Ethereum main net.
|
// Defaults contains default settings for use on the Ethereum main net.
|
||||||
var Defaults = Config{
|
var Defaults = Config{
|
||||||
HistoryMode: AllHistory,
|
HistoryMode: history.KeepAll,
|
||||||
SyncMode: SnapSync,
|
SyncMode: SnapSync,
|
||||||
NetworkId: 0, // enable auto configuration of networkID == chainID
|
NetworkId: 0, // enable auto configuration of networkID == chainID
|
||||||
TxLookupLimit: 2350000,
|
TxLookupLimit: 2350000,
|
||||||
|
|
@ -84,7 +85,7 @@ type Config struct {
|
||||||
SyncMode SyncMode
|
SyncMode SyncMode
|
||||||
|
|
||||||
// HistoryMode configures chain history retention.
|
// HistoryMode configures chain history retention.
|
||||||
HistoryMode HistoryMode
|
HistoryMode history.HistoryMode
|
||||||
|
|
||||||
// This can be set to list of enrtree:// URLs which will be queried for
|
// This can be set to list of enrtree:// URLs which will be queried for
|
||||||
// nodes to connect to.
|
// nodes to connect to.
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ import (
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/core"
|
"github.com/ethereum/go-ethereum/core"
|
||||||
|
"github.com/ethereum/go-ethereum/core/history"
|
||||||
"github.com/ethereum/go-ethereum/core/txpool/blobpool"
|
"github.com/ethereum/go-ethereum/core/txpool/blobpool"
|
||||||
"github.com/ethereum/go-ethereum/core/txpool/legacypool"
|
"github.com/ethereum/go-ethereum/core/txpool/legacypool"
|
||||||
"github.com/ethereum/go-ethereum/eth/gasprice"
|
"github.com/ethereum/go-ethereum/eth/gasprice"
|
||||||
|
|
@ -19,13 +20,16 @@ func (c Config) MarshalTOML() (interface{}, error) {
|
||||||
Genesis *core.Genesis `toml:",omitempty"`
|
Genesis *core.Genesis `toml:",omitempty"`
|
||||||
NetworkId uint64
|
NetworkId uint64
|
||||||
SyncMode SyncMode
|
SyncMode SyncMode
|
||||||
HistoryMode HistoryMode
|
HistoryMode history.HistoryMode
|
||||||
EthDiscoveryURLs []string
|
EthDiscoveryURLs []string
|
||||||
SnapDiscoveryURLs []string
|
SnapDiscoveryURLs []string
|
||||||
NoPruning bool
|
NoPruning bool
|
||||||
NoPrefetch bool
|
NoPrefetch bool
|
||||||
TxLookupLimit uint64 `toml:",omitempty"`
|
TxLookupLimit uint64 `toml:",omitempty"`
|
||||||
TransactionHistory uint64 `toml:",omitempty"`
|
TransactionHistory uint64 `toml:",omitempty"`
|
||||||
|
LogHistory uint64 `toml:",omitempty"`
|
||||||
|
LogNoHistory bool `toml:",omitempty"`
|
||||||
|
LogExportCheckpoints string
|
||||||
StateHistory uint64 `toml:",omitempty"`
|
StateHistory uint64 `toml:",omitempty"`
|
||||||
StateScheme string `toml:",omitempty"`
|
StateScheme string `toml:",omitempty"`
|
||||||
RequiredBlocks map[uint64]common.Hash `toml:"-"`
|
RequiredBlocks map[uint64]common.Hash `toml:"-"`
|
||||||
|
|
@ -63,6 +67,9 @@ func (c Config) MarshalTOML() (interface{}, error) {
|
||||||
enc.NoPrefetch = c.NoPrefetch
|
enc.NoPrefetch = c.NoPrefetch
|
||||||
enc.TxLookupLimit = c.TxLookupLimit
|
enc.TxLookupLimit = c.TxLookupLimit
|
||||||
enc.TransactionHistory = c.TransactionHistory
|
enc.TransactionHistory = c.TransactionHistory
|
||||||
|
enc.LogHistory = c.LogHistory
|
||||||
|
enc.LogNoHistory = c.LogNoHistory
|
||||||
|
enc.LogExportCheckpoints = c.LogExportCheckpoints
|
||||||
enc.StateHistory = c.StateHistory
|
enc.StateHistory = c.StateHistory
|
||||||
enc.StateScheme = c.StateScheme
|
enc.StateScheme = c.StateScheme
|
||||||
enc.RequiredBlocks = c.RequiredBlocks
|
enc.RequiredBlocks = c.RequiredBlocks
|
||||||
|
|
@ -97,13 +104,16 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error {
|
||||||
Genesis *core.Genesis `toml:",omitempty"`
|
Genesis *core.Genesis `toml:",omitempty"`
|
||||||
NetworkId *uint64
|
NetworkId *uint64
|
||||||
SyncMode *SyncMode
|
SyncMode *SyncMode
|
||||||
HistoryMode *HistoryMode
|
HistoryMode *history.HistoryMode
|
||||||
EthDiscoveryURLs []string
|
EthDiscoveryURLs []string
|
||||||
SnapDiscoveryURLs []string
|
SnapDiscoveryURLs []string
|
||||||
NoPruning *bool
|
NoPruning *bool
|
||||||
NoPrefetch *bool
|
NoPrefetch *bool
|
||||||
TxLookupLimit *uint64 `toml:",omitempty"`
|
TxLookupLimit *uint64 `toml:",omitempty"`
|
||||||
TransactionHistory *uint64 `toml:",omitempty"`
|
TransactionHistory *uint64 `toml:",omitempty"`
|
||||||
|
LogHistory *uint64 `toml:",omitempty"`
|
||||||
|
LogNoHistory *bool `toml:",omitempty"`
|
||||||
|
LogExportCheckpoints *string
|
||||||
StateHistory *uint64 `toml:",omitempty"`
|
StateHistory *uint64 `toml:",omitempty"`
|
||||||
StateScheme *string `toml:",omitempty"`
|
StateScheme *string `toml:",omitempty"`
|
||||||
RequiredBlocks map[uint64]common.Hash `toml:"-"`
|
RequiredBlocks map[uint64]common.Hash `toml:"-"`
|
||||||
|
|
@ -164,6 +174,15 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error {
|
||||||
if dec.TransactionHistory != nil {
|
if dec.TransactionHistory != nil {
|
||||||
c.TransactionHistory = *dec.TransactionHistory
|
c.TransactionHistory = *dec.TransactionHistory
|
||||||
}
|
}
|
||||||
|
if dec.LogHistory != nil {
|
||||||
|
c.LogHistory = *dec.LogHistory
|
||||||
|
}
|
||||||
|
if dec.LogNoHistory != nil {
|
||||||
|
c.LogNoHistory = *dec.LogNoHistory
|
||||||
|
}
|
||||||
|
if dec.LogExportCheckpoints != nil {
|
||||||
|
c.LogExportCheckpoints = *dec.LogExportCheckpoints
|
||||||
|
}
|
||||||
if dec.StateHistory != nil {
|
if dec.StateHistory != nil {
|
||||||
c.StateHistory = *dec.StateHistory
|
c.StateHistory = *dec.StateHistory
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -345,7 +345,7 @@ func (f *TxFetcher) Enqueue(peer string, txs []*types.Transaction, direct bool)
|
||||||
// Track the transaction hash if the price is too low for us.
|
// Track the transaction hash if the price is too low for us.
|
||||||
// Avoid re-request this transaction when we receive another
|
// Avoid re-request this transaction when we receive another
|
||||||
// announcement.
|
// announcement.
|
||||||
if errors.Is(err, txpool.ErrUnderpriced) || errors.Is(err, txpool.ErrReplaceUnderpriced) {
|
if errors.Is(err, txpool.ErrUnderpriced) || errors.Is(err, txpool.ErrReplaceUnderpriced) || errors.Is(err, txpool.ErrTxGasPriceTooLow) {
|
||||||
f.underpriced.Add(batch[j].Hash(), batch[j].Time())
|
f.underpriced.Add(batch[j].Hash(), batch[j].Time())
|
||||||
}
|
}
|
||||||
// Track a few interesting failure types
|
// Track a few interesting failure types
|
||||||
|
|
@ -355,7 +355,7 @@ func (f *TxFetcher) Enqueue(peer string, txs []*types.Transaction, direct bool)
|
||||||
case errors.Is(err, txpool.ErrAlreadyKnown):
|
case errors.Is(err, txpool.ErrAlreadyKnown):
|
||||||
duplicate++
|
duplicate++
|
||||||
|
|
||||||
case errors.Is(err, txpool.ErrUnderpriced) || errors.Is(err, txpool.ErrReplaceUnderpriced):
|
case errors.Is(err, txpool.ErrUnderpriced) || errors.Is(err, txpool.ErrReplaceUnderpriced) || errors.Is(err, txpool.ErrTxGasPriceTooLow):
|
||||||
underpriced++
|
underpriced++
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
|
|
||||||
|
|
@ -1244,10 +1244,12 @@ func TestTransactionFetcherUnderpricedDedup(t *testing.T) {
|
||||||
func(txs []*types.Transaction) []error {
|
func(txs []*types.Transaction) []error {
|
||||||
errs := make([]error, len(txs))
|
errs := make([]error, len(txs))
|
||||||
for i := 0; i < len(errs); i++ {
|
for i := 0; i < len(errs); i++ {
|
||||||
if i%2 == 0 {
|
if i%3 == 0 {
|
||||||
errs[i] = txpool.ErrUnderpriced
|
errs[i] = txpool.ErrUnderpriced
|
||||||
} else {
|
} else if i%3 == 1 {
|
||||||
errs[i] = txpool.ErrReplaceUnderpriced
|
errs[i] = txpool.ErrReplaceUnderpriced
|
||||||
|
} else {
|
||||||
|
errs[i] = txpool.ErrTxGasPriceTooLow
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return errs
|
return errs
|
||||||
|
|
|
||||||
|
|
@ -28,8 +28,8 @@ import (
|
||||||
"github.com/ethereum/go-ethereum"
|
"github.com/ethereum/go-ethereum"
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"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/history"
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
"github.com/ethereum/go-ethereum/eth/ethconfig"
|
|
||||||
"github.com/ethereum/go-ethereum/internal/ethapi"
|
"github.com/ethereum/go-ethereum/internal/ethapi"
|
||||||
"github.com/ethereum/go-ethereum/rpc"
|
"github.com/ethereum/go-ethereum/rpc"
|
||||||
)
|
)
|
||||||
|
|
@ -360,7 +360,7 @@ func (api *FilterAPI) GetLogs(ctx context.Context, crit FilterCriteria) ([]*type
|
||||||
return nil, errInvalidBlockRange
|
return nil, errInvalidBlockRange
|
||||||
}
|
}
|
||||||
if begin > 0 && begin < int64(api.events.backend.HistoryPruningCutoff()) {
|
if begin > 0 && begin < int64(api.events.backend.HistoryPruningCutoff()) {
|
||||||
return nil, ðconfig.PrunedHistoryError{}
|
return nil, &history.PrunedHistoryError{}
|
||||||
}
|
}
|
||||||
// Construct the range filter
|
// Construct the range filter
|
||||||
filter = api.sys.NewRangeFilter(begin, end, crit.Addresses, crit.Topics)
|
filter = api.sys.NewRangeFilter(begin, end, crit.Addresses, crit.Topics)
|
||||||
|
|
|
||||||
|
|
@ -26,8 +26,8 @@ import (
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/core/filtermaps"
|
"github.com/ethereum/go-ethereum/core/filtermaps"
|
||||||
|
"github.com/ethereum/go-ethereum/core/history"
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
"github.com/ethereum/go-ethereum/eth/ethconfig"
|
|
||||||
"github.com/ethereum/go-ethereum/log"
|
"github.com/ethereum/go-ethereum/log"
|
||||||
"github.com/ethereum/go-ethereum/rpc"
|
"github.com/ethereum/go-ethereum/rpc"
|
||||||
)
|
)
|
||||||
|
|
@ -88,7 +88,7 @@ func (f *Filter) Logs(ctx context.Context) ([]*types.Log, error) {
|
||||||
return nil, errors.New("unknown block")
|
return nil, errors.New("unknown block")
|
||||||
}
|
}
|
||||||
if header.Number.Uint64() < f.sys.backend.HistoryPruningCutoff() {
|
if header.Number.Uint64() < f.sys.backend.HistoryPruningCutoff() {
|
||||||
return nil, ðconfig.PrunedHistoryError{}
|
return nil, &history.PrunedHistoryError{}
|
||||||
}
|
}
|
||||||
return f.blockLogs(ctx, header)
|
return f.blockLogs(ctx, header)
|
||||||
}
|
}
|
||||||
|
|
@ -146,16 +146,18 @@ func (f *Filter) Logs(ctx context.Context) ([]*types.Log, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
rangeLogsTestSync = iota
|
rangeLogsTestDone = iota // zero range
|
||||||
rangeLogsTestTrimmed
|
rangeLogsTestSync // before sync; zero range
|
||||||
rangeLogsTestIndexed
|
rangeLogsTestSynced // after sync; valid blocks range
|
||||||
rangeLogsTestUnindexed
|
rangeLogsTestIndexed // individual search range
|
||||||
rangeLogsTestDone
|
rangeLogsTestUnindexed // individual search range
|
||||||
|
rangeLogsTestResults // results range after search iteration
|
||||||
|
rangeLogsTestReorg // results range trimmed by reorg
|
||||||
)
|
)
|
||||||
|
|
||||||
type rangeLogsTestEvent struct {
|
type rangeLogsTestEvent struct {
|
||||||
event int
|
event int
|
||||||
begin, end uint64
|
blocks common.Range[uint64]
|
||||||
}
|
}
|
||||||
|
|
||||||
// searchSession represents a single search session.
|
// searchSession represents a single search session.
|
||||||
|
|
@ -164,7 +166,9 @@ type searchSession struct {
|
||||||
filter *Filter
|
filter *Filter
|
||||||
mb filtermaps.MatcherBackend
|
mb filtermaps.MatcherBackend
|
||||||
syncRange filtermaps.SyncRange // latest synchronized state with the matcher
|
syncRange filtermaps.SyncRange // latest synchronized state with the matcher
|
||||||
firstBlock, lastBlock uint64 // specified search range; each can be MaxUint64
|
chainView *filtermaps.ChainView // can be more recent than the indexed view in syncRange
|
||||||
|
// block ranges always refer to the current chainView
|
||||||
|
firstBlock, lastBlock uint64 // specified search range; MaxUint64 means latest block
|
||||||
searchRange common.Range[uint64] // actual search range; end trimmed to latest head
|
searchRange common.Range[uint64] // actual search range; end trimmed to latest head
|
||||||
matchRange common.Range[uint64] // range in which we have results (subset of searchRange)
|
matchRange common.Range[uint64] // range in which we have results (subset of searchRange)
|
||||||
matches []*types.Log // valid set of matches in matchRange
|
matches []*types.Log // valid set of matches in matchRange
|
||||||
|
|
@ -182,84 +186,99 @@ func newSearchSession(ctx context.Context, filter *Filter, mb filtermaps.Matcher
|
||||||
}
|
}
|
||||||
// enforce a consistent state before starting the search in order to be able
|
// enforce a consistent state before starting the search in order to be able
|
||||||
// to determine valid range later
|
// to determine valid range later
|
||||||
if err := s.syncMatcher(0); err != nil {
|
var err error
|
||||||
|
s.syncRange, err = s.mb.SyncLogIndex(s.ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := s.updateChainView(); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return s, nil
|
return s, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// syncMatcher performs a synchronization step with the matcher. The resulting
|
// updateChainView updates to the latest view of the underlying chain and sets
|
||||||
// syncRange structure holds information about the latest range of indexed blocks
|
// searchRange by replacing MaxUint64 (meaning latest block) with actual head
|
||||||
// and the guaranteed valid blocks whose log index have not been changed since
|
// number in the specified search range.
|
||||||
// the previous synchronization.
|
// If the session already had an existing chain view and set of matches then
|
||||||
// The function also performs trimming of the match set in order to always keep
|
// it also trims part of the match set that a chain reorg might have invalidated.
|
||||||
// it consistent with the synced matcher state.
|
func (s *searchSession) updateChainView() error {
|
||||||
// Tail trimming is only performed if the first block of the valid log index range
|
// update chain view based on current chain head (might be more recent than
|
||||||
// is higher than trimTailThreshold. This is useful because unindexed log search
|
// the indexed view of syncRange as the indexer updates it asynchronously
|
||||||
// is not affected by the valid tail (on the other hand, valid head is taken into
|
// with some delay
|
||||||
// account in order to provide reorg safety, even though the log index is not used).
|
newChainView := s.filter.sys.backend.CurrentView()
|
||||||
// In case of indexed search the tail is only trimmed if the first part of the
|
if newChainView == nil {
|
||||||
// recently obtained results might be invalid. If guaranteed valid new results
|
return errors.New("head block not available")
|
||||||
// have been added at the head of previously validated results then there is no
|
|
||||||
// need to discard those even if the index tail have been unindexed since that.
|
|
||||||
func (s *searchSession) syncMatcher(trimTailThreshold uint64) error {
|
|
||||||
if s.filter.rangeLogsTestHook != nil && !s.matchRange.IsEmpty() {
|
|
||||||
s.filter.rangeLogsTestHook <- rangeLogsTestEvent{event: rangeLogsTestSync, begin: s.matchRange.First(), end: s.matchRange.Last()}
|
|
||||||
}
|
|
||||||
var err error
|
|
||||||
s.syncRange, err = s.mb.SyncLogIndex(s.ctx)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
head := newChainView.HeadNumber()
|
||||||
|
|
||||||
// update actual search range based on current head number
|
// update actual search range based on current head number
|
||||||
first := min(s.firstBlock, s.syncRange.HeadNumber)
|
firstBlock, lastBlock := s.firstBlock, s.lastBlock
|
||||||
last := min(s.lastBlock, s.syncRange.HeadNumber)
|
if firstBlock == math.MaxUint64 {
|
||||||
s.searchRange = common.NewRange(first, last+1-first)
|
firstBlock = head
|
||||||
// discard everything that is not needed or might be invalid
|
|
||||||
trimRange := s.syncRange.ValidBlocks
|
|
||||||
if trimRange.First() <= trimTailThreshold {
|
|
||||||
// everything before this point is already known to be valid; if this is
|
|
||||||
// valid then keep everything before
|
|
||||||
trimRange.SetFirst(0)
|
|
||||||
}
|
}
|
||||||
trimRange = trimRange.Intersection(s.searchRange)
|
if lastBlock == math.MaxUint64 {
|
||||||
s.trimMatches(trimRange)
|
lastBlock = head
|
||||||
if s.filter.rangeLogsTestHook != nil {
|
}
|
||||||
|
if firstBlock > lastBlock || lastBlock > head {
|
||||||
|
return errInvalidBlockRange
|
||||||
|
}
|
||||||
|
s.searchRange = common.NewRange(firstBlock, lastBlock+1-firstBlock)
|
||||||
|
|
||||||
|
// Trim existing match set in case a reorg may have invalidated some results
|
||||||
if !s.matchRange.IsEmpty() {
|
if !s.matchRange.IsEmpty() {
|
||||||
s.filter.rangeLogsTestHook <- rangeLogsTestEvent{event: rangeLogsTestTrimmed, begin: s.matchRange.First(), end: s.matchRange.Last()}
|
trimRange := newChainView.SharedRange(s.chainView).Intersection(s.searchRange)
|
||||||
} else {
|
s.matchRange, s.matches = s.trimMatches(trimRange, s.matchRange, s.matches)
|
||||||
s.filter.rangeLogsTestHook <- rangeLogsTestEvent{event: rangeLogsTestTrimmed, begin: 0, end: 0}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
s.chainView = newChainView
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// trimMatches removes any entries from the current set of matches that is outside
|
// trimMatches removes any entries from the specified set of matches that is
|
||||||
// the given range.
|
// outside the given range.
|
||||||
func (s *searchSession) trimMatches(trimRange common.Range[uint64]) {
|
func (s *searchSession) trimMatches(trimRange, matchRange common.Range[uint64], matches []*types.Log) (common.Range[uint64], []*types.Log) {
|
||||||
s.matchRange = s.matchRange.Intersection(trimRange)
|
newRange := matchRange.Intersection(trimRange)
|
||||||
if s.matchRange.IsEmpty() {
|
if newRange == matchRange {
|
||||||
s.matches = nil
|
return matchRange, matches
|
||||||
return
|
|
||||||
}
|
}
|
||||||
for len(s.matches) > 0 && s.matches[0].BlockNumber < s.matchRange.First() {
|
if newRange.IsEmpty() {
|
||||||
s.matches = s.matches[1:]
|
return newRange, nil
|
||||||
}
|
}
|
||||||
for len(s.matches) > 0 && s.matches[len(s.matches)-1].BlockNumber > s.matchRange.Last() {
|
for len(matches) > 0 && matches[0].BlockNumber < newRange.First() {
|
||||||
s.matches = s.matches[:len(s.matches)-1]
|
matches = matches[1:]
|
||||||
}
|
}
|
||||||
|
for len(matches) > 0 && matches[len(matches)-1].BlockNumber > newRange.Last() {
|
||||||
|
matches = matches[:len(matches)-1]
|
||||||
|
}
|
||||||
|
return newRange, matches
|
||||||
}
|
}
|
||||||
|
|
||||||
// searchInRange performs a single range search, either indexed or unindexed.
|
// searchInRange performs a single range search, either indexed or unindexed.
|
||||||
func (s *searchSession) searchInRange(r common.Range[uint64], indexed bool) ([]*types.Log, error) {
|
func (s *searchSession) searchInRange(r common.Range[uint64], indexed bool) (common.Range[uint64], []*types.Log, error) {
|
||||||
first, last := r.First(), r.Last()
|
|
||||||
if indexed {
|
if indexed {
|
||||||
if s.filter.rangeLogsTestHook != nil {
|
if s.filter.rangeLogsTestHook != nil {
|
||||||
s.filter.rangeLogsTestHook <- rangeLogsTestEvent{rangeLogsTestIndexed, first, last}
|
s.filter.rangeLogsTestHook <- rangeLogsTestEvent{rangeLogsTestIndexed, r}
|
||||||
}
|
}
|
||||||
results, err := s.filter.indexedLogs(s.ctx, s.mb, first, last)
|
results, err := s.filter.indexedLogs(s.ctx, s.mb, r.First(), r.Last())
|
||||||
if err != filtermaps.ErrMatchAll {
|
if err != nil && !errors.Is(err, filtermaps.ErrMatchAll) {
|
||||||
return results, err
|
return common.Range[uint64]{}, nil, err
|
||||||
|
}
|
||||||
|
if err == nil {
|
||||||
|
// sync with filtermaps matcher
|
||||||
|
if s.filter.rangeLogsTestHook != nil {
|
||||||
|
s.filter.rangeLogsTestHook <- rangeLogsTestEvent{rangeLogsTestSync, common.Range[uint64]{}}
|
||||||
|
}
|
||||||
|
var syncErr error
|
||||||
|
if s.syncRange, syncErr = s.mb.SyncLogIndex(s.ctx); syncErr != nil {
|
||||||
|
return common.Range[uint64]{}, nil, syncErr
|
||||||
|
}
|
||||||
|
if s.filter.rangeLogsTestHook != nil {
|
||||||
|
s.filter.rangeLogsTestHook <- rangeLogsTestEvent{rangeLogsTestSynced, s.syncRange.ValidBlocks}
|
||||||
|
}
|
||||||
|
// discard everything that might be invalid
|
||||||
|
trimRange := s.syncRange.ValidBlocks.Intersection(s.chainView.SharedRange(s.syncRange.IndexedView))
|
||||||
|
matchRange, matches := s.trimMatches(trimRange, r, results)
|
||||||
|
return matchRange, matches, nil
|
||||||
}
|
}
|
||||||
// "match all" filters are not supported by filtermaps; fall back to
|
// "match all" filters are not supported by filtermaps; fall back to
|
||||||
// unindexed search which is the most efficient in this case
|
// unindexed search which is the most efficient in this case
|
||||||
|
|
@ -267,79 +286,85 @@ func (s *searchSession) searchInRange(r common.Range[uint64], indexed bool) ([]*
|
||||||
// fall through to unindexed case
|
// fall through to unindexed case
|
||||||
}
|
}
|
||||||
if s.filter.rangeLogsTestHook != nil {
|
if s.filter.rangeLogsTestHook != nil {
|
||||||
s.filter.rangeLogsTestHook <- rangeLogsTestEvent{rangeLogsTestUnindexed, first, last}
|
s.filter.rangeLogsTestHook <- rangeLogsTestEvent{rangeLogsTestUnindexed, r}
|
||||||
}
|
}
|
||||||
return s.filter.unindexedLogs(s.ctx, first, last)
|
matches, err := s.filter.unindexedLogs(s.ctx, s.chainView, r.First(), r.Last())
|
||||||
|
if err != nil {
|
||||||
|
return common.Range[uint64]{}, nil, err
|
||||||
|
}
|
||||||
|
return r, matches, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// doSearchIteration performs a search on a range missing from an incomplete set
|
// doSearchIteration performs a search on a range missing from an incomplete set
|
||||||
// of results, adds the new section and removes invalidated entries.
|
// of results, adds the new section and removes invalidated entries.
|
||||||
func (s *searchSession) doSearchIteration() error {
|
func (s *searchSession) doSearchIteration() error {
|
||||||
switch {
|
switch {
|
||||||
case s.syncRange.IndexedBlocks.IsEmpty():
|
|
||||||
// indexer is not ready; fallback to completely unindexed search, do not check valid range
|
|
||||||
var err error
|
|
||||||
s.matchRange = s.searchRange
|
|
||||||
s.matches, err = s.searchInRange(s.searchRange, false)
|
|
||||||
return err
|
|
||||||
|
|
||||||
case s.matchRange.IsEmpty():
|
case s.matchRange.IsEmpty():
|
||||||
// no results yet; try search in entire range
|
// no results yet; try search in entire range
|
||||||
indexedSearchRange := s.searchRange.Intersection(s.syncRange.IndexedBlocks)
|
indexedSearchRange := s.searchRange.Intersection(s.syncRange.IndexedBlocks)
|
||||||
var err error
|
|
||||||
if s.forceUnindexed = indexedSearchRange.IsEmpty(); !s.forceUnindexed {
|
if s.forceUnindexed = indexedSearchRange.IsEmpty(); !s.forceUnindexed {
|
||||||
// indexed search on the intersection of indexed and searched range
|
// indexed search on the intersection of indexed and searched range
|
||||||
s.matchRange = indexedSearchRange
|
matchRange, matches, err := s.searchInRange(indexedSearchRange, true)
|
||||||
s.matches, err = s.searchInRange(indexedSearchRange, true)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return s.syncMatcher(0) // trim everything that the matcher considers potentially invalid
|
s.matchRange = matchRange
|
||||||
|
s.matches = matches
|
||||||
|
return nil
|
||||||
} else {
|
} else {
|
||||||
// no intersection of indexed and searched range; unindexed search on the whole searched range
|
// no intersection of indexed and searched range; unindexed search on
|
||||||
s.matchRange = s.searchRange
|
// the whole searched range
|
||||||
s.matches, err = s.searchInRange(s.searchRange, false)
|
matchRange, matches, err := s.searchInRange(s.searchRange, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return s.syncMatcher(math.MaxUint64) // unindexed search is not affected by the tail of valid range
|
s.matchRange = matchRange
|
||||||
|
s.matches = matches
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
case !s.matchRange.IsEmpty() && s.matchRange.First() > s.searchRange.First():
|
case !s.matchRange.IsEmpty() && s.matchRange.First() > s.searchRange.First():
|
||||||
// we have results but tail section is missing; do unindexed search for
|
// Results are available, but the tail section is missing. Perform an unindexed
|
||||||
// the tail part but still allow indexed search for missing head section
|
// search for the missing tail, while still allowing indexed search for the head.
|
||||||
|
//
|
||||||
|
// The unindexed search is necessary because the tail portion of the indexes
|
||||||
|
// has been pruned.
|
||||||
tailRange := common.NewRange(s.searchRange.First(), s.matchRange.First()-s.searchRange.First())
|
tailRange := common.NewRange(s.searchRange.First(), s.matchRange.First()-s.searchRange.First())
|
||||||
tailMatches, err := s.searchInRange(tailRange, false)
|
_, tailMatches, err := s.searchInRange(tailRange, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
s.matches = append(tailMatches, s.matches...)
|
s.matches = append(tailMatches, s.matches...)
|
||||||
s.matchRange = tailRange.Union(s.matchRange)
|
s.matchRange = tailRange.Union(s.matchRange)
|
||||||
return s.syncMatcher(math.MaxUint64) // unindexed search is not affected by the tail of valid range
|
return nil
|
||||||
|
|
||||||
case !s.matchRange.IsEmpty() && s.matchRange.First() == s.searchRange.First() && s.searchRange.AfterLast() > s.matchRange.AfterLast():
|
case !s.matchRange.IsEmpty() && s.matchRange.First() == s.searchRange.First() && s.searchRange.AfterLast() > s.matchRange.AfterLast():
|
||||||
// we have results but head section is missing
|
// Results are available, but the head section is missing. Try to perform
|
||||||
|
// the indexed search for the missing head, or fallback to unindexed search
|
||||||
|
// if the tail portion of indexed range has been pruned.
|
||||||
headRange := common.NewRange(s.matchRange.AfterLast(), s.searchRange.AfterLast()-s.matchRange.AfterLast())
|
headRange := common.NewRange(s.matchRange.AfterLast(), s.searchRange.AfterLast()-s.matchRange.AfterLast())
|
||||||
if !s.forceUnindexed {
|
if !s.forceUnindexed {
|
||||||
indexedHeadRange := headRange.Intersection(s.syncRange.IndexedBlocks)
|
indexedHeadRange := headRange.Intersection(s.syncRange.IndexedBlocks)
|
||||||
if !indexedHeadRange.IsEmpty() && indexedHeadRange.First() == headRange.First() {
|
if !indexedHeadRange.IsEmpty() && indexedHeadRange.First() == headRange.First() {
|
||||||
// indexed head range search is possible
|
|
||||||
headRange = indexedHeadRange
|
headRange = indexedHeadRange
|
||||||
} else {
|
} else {
|
||||||
|
// The tail portion of the indexes has been pruned, falling back
|
||||||
|
// to unindexed search.
|
||||||
s.forceUnindexed = true
|
s.forceUnindexed = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
headMatches, err := s.searchInRange(headRange, !s.forceUnindexed)
|
headMatchRange, headMatches, err := s.searchInRange(headRange, !s.forceUnindexed)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
s.matches = append(s.matches, headMatches...)
|
if headMatchRange.First() != s.matchRange.AfterLast() {
|
||||||
s.matchRange = s.matchRange.Union(headRange)
|
// improbable corner case, first part of new head range invalidated by tail unindexing
|
||||||
if s.forceUnindexed {
|
s.matches, s.matchRange = headMatches, headMatchRange
|
||||||
return s.syncMatcher(math.MaxUint64) // unindexed search is not affected by the tail of valid range
|
return nil
|
||||||
} else {
|
|
||||||
return s.syncMatcher(headRange.First()) // trim if the tail of latest head search results might be invalid
|
|
||||||
}
|
}
|
||||||
|
s.matches = append(s.matches, headMatches...)
|
||||||
|
s.matchRange = s.matchRange.Union(headMatchRange)
|
||||||
|
return nil
|
||||||
|
|
||||||
default:
|
default:
|
||||||
panic("invalid search session state")
|
panic("invalid search session state")
|
||||||
|
|
@ -349,7 +374,7 @@ func (s *searchSession) doSearchIteration() error {
|
||||||
func (f *Filter) rangeLogs(ctx context.Context, firstBlock, lastBlock uint64) ([]*types.Log, error) {
|
func (f *Filter) rangeLogs(ctx context.Context, firstBlock, lastBlock uint64) ([]*types.Log, error) {
|
||||||
if f.rangeLogsTestHook != nil {
|
if f.rangeLogsTestHook != nil {
|
||||||
defer func() {
|
defer func() {
|
||||||
f.rangeLogsTestHook <- rangeLogsTestEvent{rangeLogsTestDone, 0, 0}
|
f.rangeLogsTestHook <- rangeLogsTestEvent{rangeLogsTestDone, common.Range[uint64]{}}
|
||||||
close(f.rangeLogsTestHook)
|
close(f.rangeLogsTestHook)
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
@ -366,7 +391,17 @@ func (f *Filter) rangeLogs(ctx context.Context, firstBlock, lastBlock uint64) ([
|
||||||
}
|
}
|
||||||
for session.searchRange != session.matchRange {
|
for session.searchRange != session.matchRange {
|
||||||
if err := session.doSearchIteration(); err != nil {
|
if err := session.doSearchIteration(); err != nil {
|
||||||
return session.matches, err
|
return nil, err
|
||||||
|
}
|
||||||
|
if f.rangeLogsTestHook != nil {
|
||||||
|
f.rangeLogsTestHook <- rangeLogsTestEvent{rangeLogsTestResults, session.matchRange}
|
||||||
|
}
|
||||||
|
mr := session.matchRange
|
||||||
|
if err := session.updateChainView(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if f.rangeLogsTestHook != nil && session.matchRange != mr {
|
||||||
|
f.rangeLogsTestHook <- rangeLogsTestEvent{rangeLogsTestReorg, session.matchRange}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return session.matches, nil
|
return session.matches, nil
|
||||||
|
|
@ -382,7 +417,7 @@ func (f *Filter) indexedLogs(ctx context.Context, mb filtermaps.MatcherBackend,
|
||||||
|
|
||||||
// unindexedLogs returns the logs matching the filter criteria based on raw block
|
// unindexedLogs returns the logs matching the filter criteria based on raw block
|
||||||
// iteration and bloom matching.
|
// iteration and bloom matching.
|
||||||
func (f *Filter) unindexedLogs(ctx context.Context, begin, end uint64) ([]*types.Log, error) {
|
func (f *Filter) unindexedLogs(ctx context.Context, chainView *filtermaps.ChainView, begin, end uint64) ([]*types.Log, error) {
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
log.Debug("Performing unindexed log search", "begin", begin, "end", end)
|
log.Debug("Performing unindexed log search", "begin", begin, "end", end)
|
||||||
var matches []*types.Log
|
var matches []*types.Log
|
||||||
|
|
@ -392,9 +427,14 @@ func (f *Filter) unindexedLogs(ctx context.Context, begin, end uint64) ([]*types
|
||||||
return matches, ctx.Err()
|
return matches, ctx.Err()
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
header, err := f.sys.backend.HeaderByNumber(ctx, rpc.BlockNumber(blockNumber))
|
if blockNumber > chainView.HeadNumber() {
|
||||||
if header == nil || err != nil {
|
// check here so that we can return matches up until head along with
|
||||||
return matches, err
|
// the error
|
||||||
|
return matches, errInvalidBlockRange
|
||||||
|
}
|
||||||
|
header := chainView.Header(blockNumber)
|
||||||
|
if header == nil {
|
||||||
|
return matches, errors.New("header not found")
|
||||||
}
|
}
|
||||||
found, err := f.blockLogs(ctx, header)
|
found, err := f.blockLogs(ctx, header)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
|
|
@ -30,8 +30,8 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/common/lru"
|
"github.com/ethereum/go-ethereum/common/lru"
|
||||||
"github.com/ethereum/go-ethereum/core"
|
"github.com/ethereum/go-ethereum/core"
|
||||||
"github.com/ethereum/go-ethereum/core/filtermaps"
|
"github.com/ethereum/go-ethereum/core/filtermaps"
|
||||||
|
"github.com/ethereum/go-ethereum/core/history"
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
"github.com/ethereum/go-ethereum/eth/ethconfig"
|
|
||||||
"github.com/ethereum/go-ethereum/ethdb"
|
"github.com/ethereum/go-ethereum/ethdb"
|
||||||
"github.com/ethereum/go-ethereum/event"
|
"github.com/ethereum/go-ethereum/event"
|
||||||
"github.com/ethereum/go-ethereum/log"
|
"github.com/ethereum/go-ethereum/log"
|
||||||
|
|
@ -71,6 +71,7 @@ type Backend interface {
|
||||||
SubscribeRemovedLogsEvent(ch chan<- core.RemovedLogsEvent) event.Subscription
|
SubscribeRemovedLogsEvent(ch chan<- core.RemovedLogsEvent) event.Subscription
|
||||||
SubscribeLogsEvent(ch chan<- []*types.Log) event.Subscription
|
SubscribeLogsEvent(ch chan<- []*types.Log) event.Subscription
|
||||||
|
|
||||||
|
CurrentView() *filtermaps.ChainView
|
||||||
NewMatcherBackend() filtermaps.MatcherBackend
|
NewMatcherBackend() filtermaps.MatcherBackend
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -311,7 +312,7 @@ func (es *EventSystem) SubscribeLogs(crit ethereum.FilterQuery, logs chan []*typ
|
||||||
}
|
}
|
||||||
// Queries beyond the pruning cutoff are not supported.
|
// Queries beyond the pruning cutoff are not supported.
|
||||||
if uint64(from) < es.backend.HistoryPruningCutoff() {
|
if uint64(from) < es.backend.HistoryPruningCutoff() {
|
||||||
return nil, ðconfig.PrunedHistoryError{}
|
return nil, &history.PrunedHistoryError{}
|
||||||
}
|
}
|
||||||
|
|
||||||
// only interested in new mined logs
|
// only interested in new mined logs
|
||||||
|
|
|
||||||
|
|
@ -154,6 +154,11 @@ func (b *testBackend) SubscribeChainEvent(ch chan<- core.ChainEvent) event.Subsc
|
||||||
return b.chainFeed.Subscribe(ch)
|
return b.chainFeed.Subscribe(ch)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (b *testBackend) CurrentView() *filtermaps.ChainView {
|
||||||
|
head := b.CurrentBlock()
|
||||||
|
return filtermaps.NewChainView(b, head.Number.Uint64(), head.Hash())
|
||||||
|
}
|
||||||
|
|
||||||
func (b *testBackend) NewMatcherBackend() filtermaps.MatcherBackend {
|
func (b *testBackend) NewMatcherBackend() filtermaps.MatcherBackend {
|
||||||
return b.fm.NewMatcherBackend()
|
return b.fm.NewMatcherBackend()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -453,7 +453,8 @@ func TestRangeLogs(t *testing.T) {
|
||||||
addresses = []common.Address{{}}
|
addresses = []common.Address{{}}
|
||||||
)
|
)
|
||||||
|
|
||||||
expEvent := func(exp rangeLogsTestEvent) {
|
expEvent := func(expEvent int, expFirst, expAfterLast uint64) {
|
||||||
|
exp := rangeLogsTestEvent{expEvent, common.NewRange[uint64](expFirst, expAfterLast-expFirst)}
|
||||||
event++
|
event++
|
||||||
ev := <-filter.rangeLogsTestHook
|
ev := <-filter.rangeLogsTestHook
|
||||||
if ev != exp {
|
if ev != exp {
|
||||||
|
|
@ -472,7 +473,6 @@ func TestRangeLogs(t *testing.T) {
|
||||||
for range filter.rangeLogsTestHook {
|
for range filter.rangeLogsTestHook {
|
||||||
}
|
}
|
||||||
}(filter)
|
}(filter)
|
||||||
expEvent(rangeLogsTestEvent{rangeLogsTestTrimmed, 0, 0})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
updateHead := func() {
|
updateHead := func() {
|
||||||
|
|
@ -483,81 +483,122 @@ func TestRangeLogs(t *testing.T) {
|
||||||
|
|
||||||
// test case #1
|
// test case #1
|
||||||
newFilter(300, 500)
|
newFilter(300, 500)
|
||||||
expEvent(rangeLogsTestEvent{rangeLogsTestIndexed, 401, 500})
|
expEvent(rangeLogsTestIndexed, 401, 501)
|
||||||
expEvent(rangeLogsTestEvent{rangeLogsTestSync, 401, 500})
|
expEvent(rangeLogsTestSync, 0, 0)
|
||||||
expEvent(rangeLogsTestEvent{rangeLogsTestTrimmed, 401, 500})
|
expEvent(rangeLogsTestSynced, 401, 601)
|
||||||
expEvent(rangeLogsTestEvent{rangeLogsTestUnindexed, 300, 400})
|
expEvent(rangeLogsTestResults, 401, 501)
|
||||||
|
expEvent(rangeLogsTestUnindexed, 300, 401)
|
||||||
if _, err := bc.InsertChain(chain[600:700]); err != nil {
|
if _, err := bc.InsertChain(chain[600:700]); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
updateHead()
|
updateHead()
|
||||||
expEvent(rangeLogsTestEvent{rangeLogsTestSync, 300, 500})
|
expEvent(rangeLogsTestResults, 300, 501)
|
||||||
expEvent(rangeLogsTestEvent{rangeLogsTestTrimmed, 300, 500}) // unindexed search is not affected by trimmed tail
|
expEvent(rangeLogsTestDone, 0, 0)
|
||||||
expEvent(rangeLogsTestEvent{rangeLogsTestDone, 0, 0})
|
|
||||||
|
|
||||||
// test case #2
|
// test case #2
|
||||||
newFilter(400, int64(rpc.LatestBlockNumber))
|
newFilter(400, int64(rpc.LatestBlockNumber))
|
||||||
expEvent(rangeLogsTestEvent{rangeLogsTestIndexed, 501, 700})
|
expEvent(rangeLogsTestIndexed, 501, 701)
|
||||||
if _, err := bc.InsertChain(chain[700:800]); err != nil {
|
if _, err := bc.InsertChain(chain[700:800]); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
updateHead()
|
updateHead()
|
||||||
expEvent(rangeLogsTestEvent{rangeLogsTestSync, 501, 700})
|
expEvent(rangeLogsTestSync, 0, 0)
|
||||||
expEvent(rangeLogsTestEvent{rangeLogsTestTrimmed, 601, 698})
|
expEvent(rangeLogsTestSynced, 601, 699)
|
||||||
expEvent(rangeLogsTestEvent{rangeLogsTestUnindexed, 400, 600})
|
expEvent(rangeLogsTestResults, 601, 699)
|
||||||
expEvent(rangeLogsTestEvent{rangeLogsTestSync, 400, 698})
|
expEvent(rangeLogsTestUnindexed, 400, 601)
|
||||||
expEvent(rangeLogsTestEvent{rangeLogsTestTrimmed, 400, 698})
|
expEvent(rangeLogsTestResults, 400, 699)
|
||||||
expEvent(rangeLogsTestEvent{rangeLogsTestIndexed, 699, 800})
|
expEvent(rangeLogsTestIndexed, 699, 801)
|
||||||
if err := bc.SetHead(750); err != nil {
|
if _, err := bc.SetCanonical(chain[749]); err != nil { // set head to block 750
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
updateHead()
|
updateHead()
|
||||||
expEvent(rangeLogsTestEvent{rangeLogsTestSync, 400, 800})
|
expEvent(rangeLogsTestSync, 0, 0)
|
||||||
expEvent(rangeLogsTestEvent{rangeLogsTestTrimmed, 400, 748})
|
expEvent(rangeLogsTestSynced, 601, 749)
|
||||||
expEvent(rangeLogsTestEvent{rangeLogsTestIndexed, 749, 750})
|
expEvent(rangeLogsTestResults, 400, 749)
|
||||||
expEvent(rangeLogsTestEvent{rangeLogsTestSync, 400, 750})
|
expEvent(rangeLogsTestIndexed, 749, 751)
|
||||||
expEvent(rangeLogsTestEvent{rangeLogsTestTrimmed, 400, 750})
|
expEvent(rangeLogsTestSync, 0, 0)
|
||||||
expEvent(rangeLogsTestEvent{rangeLogsTestDone, 0, 0})
|
expEvent(rangeLogsTestSynced, 551, 751)
|
||||||
|
expEvent(rangeLogsTestResults, 400, 751)
|
||||||
|
expEvent(rangeLogsTestDone, 0, 0)
|
||||||
|
|
||||||
// test case #3
|
// test case #3
|
||||||
newFilter(int64(rpc.LatestBlockNumber), int64(rpc.LatestBlockNumber))
|
newFilter(int64(rpc.LatestBlockNumber), int64(rpc.LatestBlockNumber))
|
||||||
expEvent(rangeLogsTestEvent{rangeLogsTestIndexed, 750, 750})
|
expEvent(rangeLogsTestIndexed, 750, 751)
|
||||||
if err := bc.SetHead(740); err != nil {
|
if _, err := bc.SetCanonical(chain[739]); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
updateHead()
|
updateHead()
|
||||||
expEvent(rangeLogsTestEvent{rangeLogsTestSync, 750, 750})
|
expEvent(rangeLogsTestSync, 0, 0)
|
||||||
expEvent(rangeLogsTestEvent{rangeLogsTestTrimmed, 0, 0})
|
expEvent(rangeLogsTestSynced, 551, 739)
|
||||||
expEvent(rangeLogsTestEvent{rangeLogsTestIndexed, 740, 740})
|
expEvent(rangeLogsTestResults, 0, 0)
|
||||||
|
expEvent(rangeLogsTestIndexed, 740, 741)
|
||||||
if _, err := bc.InsertChain(chain[740:750]); err != nil {
|
if _, err := bc.InsertChain(chain[740:750]); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
updateHead()
|
updateHead()
|
||||||
expEvent(rangeLogsTestEvent{rangeLogsTestSync, 740, 740})
|
expEvent(rangeLogsTestSync, 0, 0)
|
||||||
expEvent(rangeLogsTestEvent{rangeLogsTestTrimmed, 0, 0})
|
expEvent(rangeLogsTestSynced, 551, 739)
|
||||||
expEvent(rangeLogsTestEvent{rangeLogsTestIndexed, 750, 750})
|
expEvent(rangeLogsTestResults, 0, 0)
|
||||||
expEvent(rangeLogsTestEvent{rangeLogsTestSync, 750, 750})
|
expEvent(rangeLogsTestIndexed, 750, 751)
|
||||||
expEvent(rangeLogsTestEvent{rangeLogsTestTrimmed, 750, 750})
|
expEvent(rangeLogsTestSync, 0, 0)
|
||||||
expEvent(rangeLogsTestEvent{rangeLogsTestDone, 0, 0})
|
expEvent(rangeLogsTestSynced, 551, 751)
|
||||||
|
expEvent(rangeLogsTestResults, 750, 751)
|
||||||
|
expEvent(rangeLogsTestDone, 0, 0)
|
||||||
|
|
||||||
// test case #4
|
// test case #4
|
||||||
|
if _, err := bc.SetCanonical(chain[499]); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
updateHead()
|
||||||
newFilter(400, int64(rpc.LatestBlockNumber))
|
newFilter(400, int64(rpc.LatestBlockNumber))
|
||||||
expEvent(rangeLogsTestEvent{rangeLogsTestIndexed, 551, 750})
|
expEvent(rangeLogsTestIndexed, 400, 501)
|
||||||
expEvent(rangeLogsTestEvent{rangeLogsTestSync, 551, 750})
|
if _, err := bc.InsertChain(chain[500:650]); err != nil {
|
||||||
expEvent(rangeLogsTestEvent{rangeLogsTestTrimmed, 551, 750})
|
t.Fatal(err)
|
||||||
expEvent(rangeLogsTestEvent{rangeLogsTestUnindexed, 400, 550})
|
}
|
||||||
|
updateHead()
|
||||||
|
expEvent(rangeLogsTestSync, 0, 0)
|
||||||
|
expEvent(rangeLogsTestSynced, 451, 499)
|
||||||
|
expEvent(rangeLogsTestResults, 451, 499)
|
||||||
|
expEvent(rangeLogsTestUnindexed, 400, 451)
|
||||||
|
expEvent(rangeLogsTestResults, 400, 499)
|
||||||
|
// indexed head extension seems possible
|
||||||
|
expEvent(rangeLogsTestIndexed, 499, 651)
|
||||||
|
// further head extension causes tail unindexing in searched range
|
||||||
|
if _, err := bc.InsertChain(chain[650:750]); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
updateHead()
|
||||||
|
expEvent(rangeLogsTestSync, 0, 0)
|
||||||
|
expEvent(rangeLogsTestSynced, 551, 649)
|
||||||
|
// tail trimmed to 551; cannot merge with existing results
|
||||||
|
expEvent(rangeLogsTestResults, 551, 649)
|
||||||
|
expEvent(rangeLogsTestUnindexed, 400, 551)
|
||||||
|
expEvent(rangeLogsTestResults, 400, 649)
|
||||||
|
expEvent(rangeLogsTestIndexed, 649, 751)
|
||||||
|
expEvent(rangeLogsTestSync, 0, 0)
|
||||||
|
expEvent(rangeLogsTestSynced, 551, 751)
|
||||||
|
expEvent(rangeLogsTestResults, 400, 751)
|
||||||
|
expEvent(rangeLogsTestDone, 0, 0)
|
||||||
|
|
||||||
|
// test case #5
|
||||||
|
newFilter(400, int64(rpc.LatestBlockNumber))
|
||||||
|
expEvent(rangeLogsTestIndexed, 551, 751)
|
||||||
|
expEvent(rangeLogsTestSync, 0, 0)
|
||||||
|
expEvent(rangeLogsTestSynced, 551, 751)
|
||||||
|
expEvent(rangeLogsTestResults, 551, 751)
|
||||||
|
expEvent(rangeLogsTestUnindexed, 400, 551)
|
||||||
if _, err := bc.InsertChain(chain[750:1000]); err != nil {
|
if _, err := bc.InsertChain(chain[750:1000]); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
updateHead()
|
updateHead()
|
||||||
expEvent(rangeLogsTestEvent{rangeLogsTestSync, 400, 750})
|
expEvent(rangeLogsTestResults, 400, 751)
|
||||||
// indexed range affected by tail pruning so we have to discard the entire
|
// indexed tail already beyond results head; revert to unindexed head search
|
||||||
// match set
|
expEvent(rangeLogsTestUnindexed, 751, 1001)
|
||||||
expEvent(rangeLogsTestEvent{rangeLogsTestTrimmed, 0, 0})
|
if _, err := bc.SetCanonical(chain[899]); err != nil {
|
||||||
expEvent(rangeLogsTestEvent{rangeLogsTestIndexed, 801, 1000})
|
t.Fatal(err)
|
||||||
expEvent(rangeLogsTestEvent{rangeLogsTestSync, 801, 1000})
|
}
|
||||||
expEvent(rangeLogsTestEvent{rangeLogsTestTrimmed, 801, 1000})
|
updateHead()
|
||||||
expEvent(rangeLogsTestEvent{rangeLogsTestUnindexed, 400, 800})
|
expEvent(rangeLogsTestResults, 400, 1001)
|
||||||
expEvent(rangeLogsTestEvent{rangeLogsTestSync, 400, 1000})
|
expEvent(rangeLogsTestReorg, 400, 901)
|
||||||
expEvent(rangeLogsTestEvent{rangeLogsTestTrimmed, 400, 1000})
|
expEvent(rangeLogsTestDone, 0, 0)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -108,8 +108,10 @@ func (oracle *Oracle) processBlock(bf *blockFees, percentiles []float64) {
|
||||||
bf.results.gasUsedRatio = float64(bf.header.GasUsed) / float64(bf.header.GasLimit)
|
bf.results.gasUsedRatio = float64(bf.header.GasUsed) / float64(bf.header.GasLimit)
|
||||||
if blobGasUsed := bf.header.BlobGasUsed; blobGasUsed != nil {
|
if blobGasUsed := bf.header.BlobGasUsed; blobGasUsed != nil {
|
||||||
maxBlobGas := eip4844.MaxBlobGasPerBlock(config, bf.header.Time)
|
maxBlobGas := eip4844.MaxBlobGasPerBlock(config, bf.header.Time)
|
||||||
|
if maxBlobGas != 0 {
|
||||||
bf.results.blobGasUsedRatio = float64(*blobGasUsed) / float64(maxBlobGas)
|
bf.results.blobGasUsedRatio = float64(*blobGasUsed) / float64(maxBlobGas)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if len(percentiles) == 0 {
|
if len(percentiles) == 0 {
|
||||||
// rewards were not requested, return null
|
// rewards were not requested, return null
|
||||||
|
|
|
||||||
|
|
@ -484,7 +484,7 @@ func (h *handler) BroadcastTransactions(txs types.Transactions) {
|
||||||
total := new(big.Int).Exp(direct, big.NewInt(2), nil) // Stabilise total peer count a bit based on sqrt peers
|
total := new(big.Int).Exp(direct, big.NewInt(2), nil) // Stabilise total peer count a bit based on sqrt peers
|
||||||
|
|
||||||
var (
|
var (
|
||||||
signer = types.LatestSignerForChainID(h.chain.Config().ChainID) // Don't care about chain status, we just need *a* sender
|
signer = types.LatestSigner(h.chain.Config()) // Don't care about chain status, we just need *a* sender
|
||||||
hasher = crypto.NewKeccakState()
|
hasher = crypto.NewKeccakState()
|
||||||
hash = make([]byte, 32)
|
hash = make([]byte, 32)
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -25,16 +25,14 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"go.uber.org/goleak"
|
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/crypto/kzg4844"
|
|
||||||
"github.com/holiman/uint256"
|
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
|
"github.com/ethereum/go-ethereum/crypto/kzg4844"
|
||||||
"github.com/ethereum/go-ethereum/params"
|
"github.com/ethereum/go-ethereum/params"
|
||||||
|
"github.com/holiman/uint256"
|
||||||
|
"go.uber.org/goleak"
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ bind.ContractBackend = (Client)(nil)
|
var _ bind.ContractBackend = (Client)(nil)
|
||||||
|
|
|
||||||
|
|
@ -619,6 +619,9 @@ func (b testBackend) SubscribeRemovedLogsEvent(ch chan<- core.RemovedLogsEvent)
|
||||||
func (b testBackend) SubscribeLogsEvent(ch chan<- []*types.Log) event.Subscription {
|
func (b testBackend) SubscribeLogsEvent(ch chan<- []*types.Log) event.Subscription {
|
||||||
panic("implement me")
|
panic("implement me")
|
||||||
}
|
}
|
||||||
|
func (b testBackend) CurrentView() *filtermaps.ChainView {
|
||||||
|
panic("implement me")
|
||||||
|
}
|
||||||
func (b testBackend) NewMatcherBackend() filtermaps.MatcherBackend {
|
func (b testBackend) NewMatcherBackend() filtermaps.MatcherBackend {
|
||||||
panic("implement me")
|
panic("implement me")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -95,6 +95,7 @@ type Backend interface {
|
||||||
SubscribeRemovedLogsEvent(ch chan<- core.RemovedLogsEvent) event.Subscription
|
SubscribeRemovedLogsEvent(ch chan<- core.RemovedLogsEvent) event.Subscription
|
||||||
SubscribeLogsEvent(ch chan<- []*types.Log) event.Subscription
|
SubscribeLogsEvent(ch chan<- []*types.Log) event.Subscription
|
||||||
|
|
||||||
|
CurrentView() *filtermaps.ChainView
|
||||||
NewMatcherBackend() filtermaps.MatcherBackend
|
NewMatcherBackend() filtermaps.MatcherBackend
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -314,9 +314,13 @@ func (sim *simulator) processBlock(ctx context.Context, block *simBlock, header,
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
// EIP-7002
|
// EIP-7002
|
||||||
core.ProcessWithdrawalQueue(&requests, evm)
|
if err := core.ProcessWithdrawalQueue(&requests, evm); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
// EIP-7251
|
// EIP-7251
|
||||||
core.ProcessConsolidationQueue(&requests, evm)
|
if err := core.ProcessConsolidationQueue(&requests, evm); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if requests != nil {
|
if requests != nil {
|
||||||
reqHash := types.CalcRequestsHash(requests)
|
reqHash := types.CalcRequestsHash(requests)
|
||||||
|
|
|
||||||
|
|
@ -401,6 +401,7 @@ func (b *backendMock) SubscribeRemovedLogsEvent(ch chan<- core.RemovedLogsEvent)
|
||||||
|
|
||||||
func (b *backendMock) Engine() consensus.Engine { return nil }
|
func (b *backendMock) Engine() consensus.Engine { return nil }
|
||||||
|
|
||||||
|
func (b *backendMock) CurrentView() *filtermaps.ChainView { return nil }
|
||||||
func (b *backendMock) NewMatcherBackend() filtermaps.MatcherBackend { return nil }
|
func (b *backendMock) NewMatcherBackend() filtermaps.MatcherBackend { return nil }
|
||||||
|
|
||||||
func (b *backendMock) HistoryPruningCutoff() uint64 { return 0 }
|
func (b *backendMock) HistoryPruningCutoff() uint64 { return 0 }
|
||||||
|
|
|
||||||
|
|
@ -127,9 +127,13 @@ func (miner *Miner) generateWork(params *generateParams, witness bool) *newPaylo
|
||||||
return &newPayloadResult{err: err}
|
return &newPayloadResult{err: err}
|
||||||
}
|
}
|
||||||
// EIP-7002
|
// EIP-7002
|
||||||
core.ProcessWithdrawalQueue(&requests, work.evm)
|
if err := core.ProcessWithdrawalQueue(&requests, work.evm); err != nil {
|
||||||
|
return &newPayloadResult{err: err}
|
||||||
|
}
|
||||||
// EIP-7251 consolidations
|
// EIP-7251 consolidations
|
||||||
core.ProcessConsolidationQueue(&requests, work.evm)
|
if err := core.ProcessConsolidationQueue(&requests, work.evm); err != nil {
|
||||||
|
return &newPayloadResult{err: err}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if requests != nil {
|
if requests != nil {
|
||||||
reqHash := types.CalcRequestsHash(requests)
|
reqHash := types.CalcRequestsHash(requests)
|
||||||
|
|
|
||||||
|
|
@ -49,7 +49,11 @@ var (
|
||||||
serveSuccessMeter = metrics.NewRegisteredMeter("p2p/serves/success", nil)
|
serveSuccessMeter = metrics.NewRegisteredMeter("p2p/serves/success", nil)
|
||||||
dialMeter = metrics.NewRegisteredMeter("p2p/dials", nil)
|
dialMeter = metrics.NewRegisteredMeter("p2p/dials", nil)
|
||||||
dialSuccessMeter = metrics.NewRegisteredMeter("p2p/dials/success", nil)
|
dialSuccessMeter = metrics.NewRegisteredMeter("p2p/dials/success", nil)
|
||||||
dialConnectionError = metrics.NewRegisteredMeter("p2p/dials/error/connection", nil)
|
dialConnectionError = metrics.NewRegisteredMeter("p2p/dials/error/connection", nil) // dial timeout; no route to host; connection refused; network is unreachable
|
||||||
|
|
||||||
|
// count peers that stayed connected for at least 1 min
|
||||||
|
serve1MinSuccessMeter = metrics.NewRegisteredMeter("p2p/serves/success/1min", nil)
|
||||||
|
dial1MinSuccessMeter = metrics.NewRegisteredMeter("p2p/dials/success/1min", nil)
|
||||||
|
|
||||||
// handshake error meters
|
// handshake error meters
|
||||||
dialTooManyPeers = metrics.NewRegisteredMeter("p2p/dials/error/saturated", nil)
|
dialTooManyPeers = metrics.NewRegisteredMeter("p2p/dials/error/saturated", nil)
|
||||||
|
|
@ -57,34 +61,41 @@ var (
|
||||||
dialSelf = metrics.NewRegisteredMeter("p2p/dials/error/self", nil)
|
dialSelf = metrics.NewRegisteredMeter("p2p/dials/error/self", nil)
|
||||||
dialUselessPeer = metrics.NewRegisteredMeter("p2p/dials/error/useless", nil)
|
dialUselessPeer = metrics.NewRegisteredMeter("p2p/dials/error/useless", nil)
|
||||||
dialUnexpectedIdentity = metrics.NewRegisteredMeter("p2p/dials/error/id/unexpected", nil)
|
dialUnexpectedIdentity = metrics.NewRegisteredMeter("p2p/dials/error/id/unexpected", nil)
|
||||||
dialEncHandshakeError = metrics.NewRegisteredMeter("p2p/dials/error/rlpx/enc", nil)
|
dialEncHandshakeError = metrics.NewRegisteredMeter("p2p/dials/error/rlpx/enc", nil) // EOF; connection reset during handshake; message too big; i/o timeout
|
||||||
dialProtoHandshakeError = metrics.NewRegisteredMeter("p2p/dials/error/rlpx/proto", nil)
|
dialProtoHandshakeError = metrics.NewRegisteredMeter("p2p/dials/error/rlpx/proto", nil) // EOF
|
||||||
|
|
||||||
|
// capture the rest of errors that are not handled by the above meters
|
||||||
|
dialOtherError = metrics.NewRegisteredMeter("p2p/dials/error/other", nil)
|
||||||
)
|
)
|
||||||
|
|
||||||
// markDialError matches errors that occur while setting up a dial connection
|
// markDialError matches errors that occur while setting up a dial connection to the
|
||||||
// to the corresponding meter.
|
// corresponding meter. We don't maintain meters for evert possible error, just for
|
||||||
|
// the most interesting ones.
|
||||||
func markDialError(err error) {
|
func markDialError(err error) {
|
||||||
if !metrics.Enabled() {
|
if !metrics.Enabled() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err2 := errors.Unwrap(err); err2 != nil {
|
|
||||||
err = err2
|
var reason DiscReason
|
||||||
}
|
var handshakeErr *protoHandshakeError
|
||||||
switch err {
|
d := errors.As(err, &reason)
|
||||||
case DiscTooManyPeers:
|
switch {
|
||||||
|
case d && reason == DiscTooManyPeers:
|
||||||
dialTooManyPeers.Mark(1)
|
dialTooManyPeers.Mark(1)
|
||||||
case DiscAlreadyConnected:
|
case d && reason == DiscAlreadyConnected:
|
||||||
dialAlreadyConnected.Mark(1)
|
dialAlreadyConnected.Mark(1)
|
||||||
case DiscSelf:
|
case d && reason == DiscSelf:
|
||||||
dialSelf.Mark(1)
|
dialSelf.Mark(1)
|
||||||
case DiscUselessPeer:
|
case d && reason == DiscUselessPeer:
|
||||||
dialUselessPeer.Mark(1)
|
dialUselessPeer.Mark(1)
|
||||||
case DiscUnexpectedIdentity:
|
case d && reason == DiscUnexpectedIdentity:
|
||||||
dialUnexpectedIdentity.Mark(1)
|
dialUnexpectedIdentity.Mark(1)
|
||||||
case errEncHandshakeError:
|
case errors.As(err, &handshakeErr):
|
||||||
dialEncHandshakeError.Mark(1)
|
|
||||||
case errProtoHandshakeError:
|
|
||||||
dialProtoHandshakeError.Mark(1)
|
dialProtoHandshakeError.Mark(1)
|
||||||
|
case errors.Is(err, errEncHandshakeError):
|
||||||
|
dialEncHandshakeError.Mark(1)
|
||||||
|
default:
|
||||||
|
dialOtherError.Mark(1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
34
p2p/peer.go
34
p2p/peer.go
|
|
@ -220,11 +220,35 @@ func (p *Peer) String() string {
|
||||||
return fmt.Sprintf("Peer %x %v", id[:8], p.RemoteAddr())
|
return fmt.Sprintf("Peer %x %v", id[:8], p.RemoteAddr())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Inbound returns true if the peer is an inbound connection
|
// Inbound returns true if the peer is an inbound (not dialed) connection.
|
||||||
func (p *Peer) Inbound() bool {
|
func (p *Peer) Inbound() bool {
|
||||||
return p.rw.is(inboundConn)
|
return p.rw.is(inboundConn)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Trusted returns true if the peer is configured as trusted.
|
||||||
|
// Trusted peers are accepted in above the MaxInboundConns limit.
|
||||||
|
// The peer can be either inbound or dialed.
|
||||||
|
func (p *Peer) Trusted() bool {
|
||||||
|
return p.rw.is(trustedConn)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DynDialed returns true if the peer was dialed successfully (passed handshake) and
|
||||||
|
// it is not configured as static.
|
||||||
|
func (p *Peer) DynDialed() bool {
|
||||||
|
return p.rw.is(dynDialedConn)
|
||||||
|
}
|
||||||
|
|
||||||
|
// StaticDialed returns true if the peer was dialed successfully (passed handshake) and
|
||||||
|
// it is configured as static.
|
||||||
|
func (p *Peer) StaticDialed() bool {
|
||||||
|
return p.rw.is(staticDialedConn)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lifetime returns the time since peer creation.
|
||||||
|
func (p *Peer) Lifetime() mclock.AbsTime {
|
||||||
|
return mclock.Now() - p.created
|
||||||
|
}
|
||||||
|
|
||||||
func newPeer(log log.Logger, conn *conn, protocols []Protocol) *Peer {
|
func newPeer(log log.Logger, conn *conn, protocols []Protocol) *Peer {
|
||||||
protomap := matchProtocols(protocols, conn.caps, conn)
|
protomap := matchProtocols(protocols, conn.caps, conn)
|
||||||
p := &Peer{
|
p := &Peer{
|
||||||
|
|
@ -254,6 +278,8 @@ func (p *Peer) run() (remoteRequested bool, err error) {
|
||||||
p.wg.Add(2)
|
p.wg.Add(2)
|
||||||
go p.readLoop(readErr)
|
go p.readLoop(readErr)
|
||||||
go p.pingLoop()
|
go p.pingLoop()
|
||||||
|
live1min := time.NewTimer(1 * time.Minute)
|
||||||
|
defer live1min.Stop()
|
||||||
|
|
||||||
// Start all protocol handlers.
|
// Start all protocol handlers.
|
||||||
writeStart <- struct{}{}
|
writeStart <- struct{}{}
|
||||||
|
|
@ -285,6 +311,12 @@ loop:
|
||||||
case err = <-p.disc:
|
case err = <-p.disc:
|
||||||
reason = discReasonForError(err)
|
reason = discReasonForError(err)
|
||||||
break loop
|
break loop
|
||||||
|
case <-live1min.C:
|
||||||
|
if p.Inbound() {
|
||||||
|
serve1MinSuccessMeter.Mark(1)
|
||||||
|
} else {
|
||||||
|
dial1MinSuccessMeter.Mark(1)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -68,9 +68,13 @@ const (
|
||||||
var (
|
var (
|
||||||
errServerStopped = errors.New("server stopped")
|
errServerStopped = errors.New("server stopped")
|
||||||
errEncHandshakeError = errors.New("rlpx enc error")
|
errEncHandshakeError = errors.New("rlpx enc error")
|
||||||
errProtoHandshakeError = errors.New("rlpx proto error")
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type protoHandshakeError struct{ err error }
|
||||||
|
|
||||||
|
func (e *protoHandshakeError) Error() string { return fmt.Sprintf("rlpx proto error: %v", e.err) }
|
||||||
|
func (e *protoHandshakeError) Unwrap() error { return e.err }
|
||||||
|
|
||||||
// Server manages all peer connections.
|
// Server manages all peer connections.
|
||||||
type Server struct {
|
type Server struct {
|
||||||
// Config fields may not be modified while the server is running.
|
// Config fields may not be modified while the server is running.
|
||||||
|
|
@ -508,7 +512,7 @@ func (srv *Server) setupDiscovery() error {
|
||||||
func (srv *Server) setupDialScheduler() {
|
func (srv *Server) setupDialScheduler() {
|
||||||
config := dialConfig{
|
config := dialConfig{
|
||||||
self: srv.localnode.ID(),
|
self: srv.localnode.ID(),
|
||||||
maxDialPeers: srv.maxDialedConns(),
|
maxDialPeers: srv.MaxDialedConns(),
|
||||||
maxActiveDials: srv.MaxPendingPeers,
|
maxActiveDials: srv.MaxPendingPeers,
|
||||||
log: srv.Logger,
|
log: srv.Logger,
|
||||||
netRestrict: srv.NetRestrict,
|
netRestrict: srv.NetRestrict,
|
||||||
|
|
@ -527,11 +531,11 @@ func (srv *Server) setupDialScheduler() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (srv *Server) maxInboundConns() int {
|
func (srv *Server) MaxInboundConns() int {
|
||||||
return srv.MaxPeers - srv.maxDialedConns()
|
return srv.MaxPeers - srv.MaxDialedConns()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (srv *Server) maxDialedConns() (limit int) {
|
func (srv *Server) MaxDialedConns() (limit int) {
|
||||||
if srv.NoDial || srv.MaxPeers == 0 {
|
if srv.NoDial || srv.MaxPeers == 0 {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
@ -736,7 +740,7 @@ func (srv *Server) postHandshakeChecks(peers map[enode.ID]*Peer, inboundCount in
|
||||||
switch {
|
switch {
|
||||||
case !c.is(trustedConn) && len(peers) >= srv.MaxPeers:
|
case !c.is(trustedConn) && len(peers) >= srv.MaxPeers:
|
||||||
return DiscTooManyPeers
|
return DiscTooManyPeers
|
||||||
case !c.is(trustedConn) && c.is(inboundConn) && inboundCount >= srv.maxInboundConns():
|
case !c.is(trustedConn) && c.is(inboundConn) && inboundCount >= srv.MaxInboundConns():
|
||||||
return DiscTooManyPeers
|
return DiscTooManyPeers
|
||||||
case peers[c.node.ID()] != nil:
|
case peers[c.node.ID()] != nil:
|
||||||
return DiscAlreadyConnected
|
return DiscAlreadyConnected
|
||||||
|
|
@ -907,7 +911,7 @@ func (srv *Server) setupConn(c *conn, dialDest *enode.Node) error {
|
||||||
phs, err := c.doProtoHandshake(srv.ourHandshake)
|
phs, err := c.doProtoHandshake(srv.ourHandshake)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
clog.Trace("Failed p2p handshake", "err", err)
|
clog.Trace("Failed p2p handshake", "err", err)
|
||||||
return fmt.Errorf("%w: %v", errProtoHandshakeError, err)
|
return &protoHandshakeError{err: err}
|
||||||
}
|
}
|
||||||
if id := c.node.ID(); !bytes.Equal(crypto.Keccak256(phs.ID), id[:]) {
|
if id := c.node.ID(); !bytes.Equal(crypto.Keccak256(phs.ID), id[:]) {
|
||||||
clog.Trace("Wrong devp2p handshake identity", "phsid", hex.EncodeToString(phs.ID))
|
clog.Trace("Wrong devp2p handshake identity", "phsid", hex.EncodeToString(phs.ID))
|
||||||
|
|
|
||||||
|
|
@ -410,11 +410,11 @@ func TestServerSetupConn(t *testing.T) {
|
||||||
wantCloseErr: DiscUnexpectedIdentity,
|
wantCloseErr: DiscUnexpectedIdentity,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
tt: &setupTransport{pubkey: clientpub, protoHandshakeErr: errProtoHandshakeError},
|
tt: &setupTransport{pubkey: clientpub, protoHandshakeErr: DiscTooManyPeers},
|
||||||
dialDest: enode.NewV4(clientpub, nil, 0, 0),
|
dialDest: enode.NewV4(clientpub, nil, 0, 0),
|
||||||
flags: dynDialedConn,
|
flags: dynDialedConn,
|
||||||
wantCalls: "doEncHandshake,doProtoHandshake,close,",
|
wantCalls: "doEncHandshake,doProtoHandshake,close,",
|
||||||
wantCloseErr: errProtoHandshakeError,
|
wantCloseErr: DiscTooManyPeers,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
tt: &setupTransport{pubkey: srvpub, phs: protoHandshake{ID: crypto.FromECDSAPub(srvpub)[1:]}},
|
tt: &setupTransport{pubkey: srvpub, phs: protoHandshake{ID: crypto.FromECDSAPub(srvpub)[1:]}},
|
||||||
|
|
|
||||||
|
|
@ -60,10 +60,12 @@ var (
|
||||||
TerminalTotalDifficulty: MainnetTerminalTotalDifficulty, // 58_750_000_000_000_000_000_000
|
TerminalTotalDifficulty: MainnetTerminalTotalDifficulty, // 58_750_000_000_000_000_000_000
|
||||||
ShanghaiTime: newUint64(1681338455),
|
ShanghaiTime: newUint64(1681338455),
|
||||||
CancunTime: newUint64(1710338135),
|
CancunTime: newUint64(1710338135),
|
||||||
|
PragueTime: newUint64(1746612311),
|
||||||
DepositContractAddress: common.HexToAddress("0x00000000219ab540356cbb839cbe05303d7705fa"),
|
DepositContractAddress: common.HexToAddress("0x00000000219ab540356cbb839cbe05303d7705fa"),
|
||||||
Ethash: new(EthashConfig),
|
Ethash: new(EthashConfig),
|
||||||
BlobScheduleConfig: &BlobScheduleConfig{
|
BlobScheduleConfig: &BlobScheduleConfig{
|
||||||
Cancun: DefaultCancunBlobConfig,
|
Cancun: DefaultCancunBlobConfig,
|
||||||
|
Prague: DefaultPragueBlobConfig,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
// HoleskyChainConfig contains the chain parameters to run a node on the Holesky test network.
|
// HoleskyChainConfig contains the chain parameters to run a node on the Holesky test network.
|
||||||
|
|
@ -907,6 +909,23 @@ func (c *ChainConfig) LatestFork(time uint64) forks.Fork {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Timestamp returns the timestamp associated with the fork or returns nil if
|
||||||
|
// the fork isn't defined or isn't a time-based fork.
|
||||||
|
func (c *ChainConfig) Timestamp(fork forks.Fork) *uint64 {
|
||||||
|
switch {
|
||||||
|
case fork == forks.Osaka:
|
||||||
|
return c.OsakaTime
|
||||||
|
case fork == forks.Prague:
|
||||||
|
return c.PragueTime
|
||||||
|
case fork == forks.Cancun:
|
||||||
|
return c.CancunTime
|
||||||
|
case fork == forks.Shanghai:
|
||||||
|
return c.ShanghaiTime
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// isForkBlockIncompatible returns true if a fork scheduled at block s1 cannot be
|
// isForkBlockIncompatible returns true if a fork scheduled at block s1 cannot be
|
||||||
// rescheduled to block s2 because head is already past the fork.
|
// rescheduled to block s2 because head is already past the fork.
|
||||||
func isForkBlockIncompatible(s1, s2, head *big.Int) bool {
|
func isForkBlockIncompatible(s1, s2, head *big.Int) bool {
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ package forks
|
||||||
type Fork int
|
type Fork int
|
||||||
|
|
||||||
const (
|
const (
|
||||||
Frontier = iota
|
Frontier Fork = iota
|
||||||
FrontierThawing
|
FrontierThawing
|
||||||
Homestead
|
Homestead
|
||||||
DAO
|
DAO
|
||||||
|
|
@ -41,3 +41,35 @@ const (
|
||||||
Prague
|
Prague
|
||||||
Osaka
|
Osaka
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// String implements fmt.Stringer.
|
||||||
|
func (f Fork) String() string {
|
||||||
|
s, ok := forkToString[f]
|
||||||
|
if !ok {
|
||||||
|
return "Unknown fork"
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
var forkToString = map[Fork]string{
|
||||||
|
Frontier: "Frontier",
|
||||||
|
FrontierThawing: "Frontier Thawing",
|
||||||
|
Homestead: "Homestead",
|
||||||
|
DAO: "DAO",
|
||||||
|
TangerineWhistle: "Tangerine Whistle",
|
||||||
|
SpuriousDragon: "Spurious Dragon",
|
||||||
|
Byzantium: "Byzantium",
|
||||||
|
Constantinople: "Constantinople",
|
||||||
|
Petersburg: "Petersburg",
|
||||||
|
Istanbul: "Istanbul",
|
||||||
|
MuirGlacier: "Muir Glacier",
|
||||||
|
Berlin: "Berlin",
|
||||||
|
London: "London",
|
||||||
|
ArrowGlacier: "Arrow Glacier",
|
||||||
|
GrayGlacier: "Gray Glacier",
|
||||||
|
Paris: "Paris",
|
||||||
|
Shanghai: "Shanghai",
|
||||||
|
Cancun: "Cancun",
|
||||||
|
Prague: "Prague",
|
||||||
|
Osaka: "Osaka",
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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 = 15 // Minor version component of the current release
|
Minor = 15 // Minor version component of the current release
|
||||||
Patch = 8 // Patch version component of the current release
|
Patch = 9 // Patch version component of the current release
|
||||||
Meta = "stable" // Version metadata to append to the version string
|
Meta = "stable" // Version metadata to append to the version string
|
||||||
)
|
)
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue