mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-06-01 04:28:37 +00:00
fix: merge conflicts
This commit is contained in:
commit
b524bd3eb8
49 changed files with 1448 additions and 440 deletions
|
|
@ -218,11 +218,15 @@ func (tc *conn) read(c net.PacketConn) v5wire.Packet {
|
||||||
if err := c.SetReadDeadline(time.Now().Add(waitTime)); err != nil {
|
if err := c.SetReadDeadline(time.Now().Add(waitTime)); err != nil {
|
||||||
return &readError{err}
|
return &readError{err}
|
||||||
}
|
}
|
||||||
n, fromAddr, err := c.ReadFrom(buf)
|
n, _, err := c.ReadFrom(buf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &readError{err}
|
return &readError{err}
|
||||||
}
|
}
|
||||||
_, _, p, err := tc.codec.Decode(buf[:n], fromAddr.String())
|
// Always use tc.remoteAddr for session lookup. The actual source address of
|
||||||
|
// the packet may differ from tc.remoteAddr when the remote node is reachable
|
||||||
|
// via multiple networks (e.g. Docker bridge vs. overlay), but the codec's
|
||||||
|
// session cache is keyed by the address used during Encode.
|
||||||
|
_, _, p, err := tc.codec.Decode(buf[:n], tc.remoteAddr.String())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &readError{err}
|
return &readError{err}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -731,13 +731,16 @@ func pruneHistory(ctx *cli.Context) error {
|
||||||
|
|
||||||
// Determine the prune point based on the history mode.
|
// Determine the prune point based on the history mode.
|
||||||
genesisHash := chain.Genesis().Hash()
|
genesisHash := chain.Genesis().Hash()
|
||||||
prunePoint := history.GetPrunePoint(genesisHash, mode)
|
policy, err := history.NewPolicy(mode, genesisHash)
|
||||||
if prunePoint == nil {
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if policy.Target == nil {
|
||||||
return fmt.Errorf("prune point for %q not found for this network", mode.String())
|
return fmt.Errorf("prune point for %q not found for this network", mode.String())
|
||||||
}
|
}
|
||||||
var (
|
var (
|
||||||
targetBlock = prunePoint.BlockNumber
|
targetBlock = policy.Target.BlockNumber
|
||||||
targetBlockHash = prunePoint.BlockHash
|
targetBlockHash = policy.Target.BlockHash
|
||||||
)
|
)
|
||||||
|
|
||||||
// Check the current freezer tail to see if pruning is needed/possible.
|
// Check the current freezer tail to see if pruning is needed/possible.
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,7 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/log"
|
"github.com/ethereum/go-ethereum/log"
|
||||||
"github.com/ethereum/go-ethereum/rlp"
|
"github.com/ethereum/go-ethereum/rlp"
|
||||||
"github.com/ethereum/go-ethereum/trie"
|
"github.com/ethereum/go-ethereum/trie"
|
||||||
|
"github.com/ethereum/go-ethereum/triedb"
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -105,7 +106,9 @@ information about the specified address.
|
||||||
Usage: "Traverse the state with given root hash and perform quick verification",
|
Usage: "Traverse the state with given root hash and perform quick verification",
|
||||||
ArgsUsage: "<root>",
|
ArgsUsage: "<root>",
|
||||||
Action: traverseState,
|
Action: traverseState,
|
||||||
Flags: slices.Concat(utils.NetworkFlags, utils.DatabaseFlags),
|
Flags: slices.Concat([]cli.Flag{
|
||||||
|
utils.AccountFlag,
|
||||||
|
}, utils.NetworkFlags, utils.DatabaseFlags),
|
||||||
Description: `
|
Description: `
|
||||||
geth snapshot traverse-state <state-root>
|
geth snapshot traverse-state <state-root>
|
||||||
will traverse the whole state from the given state root and will abort if any
|
will traverse the whole state from the given state root and will abort if any
|
||||||
|
|
@ -113,6 +116,8 @@ referenced trie node or contract code is missing. This command can be used for
|
||||||
state integrity verification. The default checking target is the HEAD state.
|
state integrity verification. The default checking target is the HEAD state.
|
||||||
|
|
||||||
It's also usable without snapshot enabled.
|
It's also usable without snapshot enabled.
|
||||||
|
|
||||||
|
If --account is specified, only the storage trie of that account is traversed.
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -120,7 +125,9 @@ It's also usable without snapshot enabled.
|
||||||
Usage: "Traverse the state with given root hash and perform detailed verification",
|
Usage: "Traverse the state with given root hash and perform detailed verification",
|
||||||
ArgsUsage: "<root>",
|
ArgsUsage: "<root>",
|
||||||
Action: traverseRawState,
|
Action: traverseRawState,
|
||||||
Flags: slices.Concat(utils.NetworkFlags, utils.DatabaseFlags),
|
Flags: slices.Concat([]cli.Flag{
|
||||||
|
utils.AccountFlag,
|
||||||
|
}, utils.NetworkFlags, utils.DatabaseFlags),
|
||||||
Description: `
|
Description: `
|
||||||
geth snapshot traverse-rawstate <state-root>
|
geth snapshot traverse-rawstate <state-root>
|
||||||
will traverse the whole state from the given root and will abort if any referenced
|
will traverse the whole state from the given root and will abort if any referenced
|
||||||
|
|
@ -129,6 +136,8 @@ verification. The default checking target is the HEAD state. It's basically iden
|
||||||
to traverse-state, but the check granularity is smaller.
|
to traverse-state, but the check granularity is smaller.
|
||||||
|
|
||||||
It's also usable without snapshot enabled.
|
It's also usable without snapshot enabled.
|
||||||
|
|
||||||
|
If --account is specified, only the storage trie of that account is traversed.
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -272,6 +281,120 @@ func checkDanglingStorage(ctx *cli.Context) error {
|
||||||
return snapshot.CheckDanglingStorage(db)
|
return snapshot.CheckDanglingStorage(db)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// parseAccount parses the account flag value as either an address (20 bytes)
|
||||||
|
// or an account hash (32 bytes) and returns the hashed account key.
|
||||||
|
func parseAccount(input string) (common.Hash, error) {
|
||||||
|
switch len(input) {
|
||||||
|
case 40, 42: // address
|
||||||
|
return crypto.Keccak256Hash(common.HexToAddress(input).Bytes()), nil
|
||||||
|
case 64, 66: // hash
|
||||||
|
return common.HexToHash(input), nil
|
||||||
|
default:
|
||||||
|
return common.Hash{}, errors.New("malformed account address or hash")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// lookupAccount resolves the account from the state trie using the given
|
||||||
|
// account hash.
|
||||||
|
func lookupAccount(accountHash common.Hash, tr *trie.Trie) (*types.StateAccount, error) {
|
||||||
|
accData, err := tr.Get(accountHash.Bytes())
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to get account %s: %w", accountHash, err)
|
||||||
|
}
|
||||||
|
if accData == nil {
|
||||||
|
return nil, fmt.Errorf("account not found: %s", accountHash)
|
||||||
|
}
|
||||||
|
var acc types.StateAccount
|
||||||
|
if err := rlp.DecodeBytes(accData, &acc); err != nil {
|
||||||
|
return nil, fmt.Errorf("invalid account data %s: %w", accountHash, err)
|
||||||
|
}
|
||||||
|
return &acc, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func traverseStorage(id *trie.ID, db *triedb.Database, report bool, detail bool) error {
|
||||||
|
tr, err := trie.NewStateTrie(id, db)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("Failed to open storage trie", "account", id.Owner, "root", id.Root, "err", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
var (
|
||||||
|
slots int
|
||||||
|
nodes int
|
||||||
|
lastReport time.Time
|
||||||
|
start = time.Now()
|
||||||
|
)
|
||||||
|
it, err := tr.NodeIterator(nil)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("Failed to open storage iterator", "account", id.Owner, "root", id.Root, "err", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
logger := log.Debug
|
||||||
|
if report {
|
||||||
|
logger = log.Info
|
||||||
|
}
|
||||||
|
logger("Start traversing storage trie", "account", id.Owner, "storageRoot", id.Root)
|
||||||
|
|
||||||
|
if !detail {
|
||||||
|
iter := trie.NewIterator(it)
|
||||||
|
for iter.Next() {
|
||||||
|
slots += 1
|
||||||
|
if time.Since(lastReport) > time.Second*8 {
|
||||||
|
logger("Traversing storage", "account", id.Owner, "slots", slots, "elapsed", common.PrettyDuration(time.Since(start)))
|
||||||
|
lastReport = time.Now()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if iter.Err != nil {
|
||||||
|
log.Error("Failed to traverse storage trie", "root", id.Root, "err", iter.Err)
|
||||||
|
return iter.Err
|
||||||
|
}
|
||||||
|
logger("Storage is complete", "account", id.Owner, "slots", slots, "elapsed", common.PrettyDuration(time.Since(start)))
|
||||||
|
} else {
|
||||||
|
reader, err := db.NodeReader(id.StateRoot)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("Failed to open state reader", "err", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
var (
|
||||||
|
buffer = make([]byte, 32)
|
||||||
|
hasher = crypto.NewKeccakState()
|
||||||
|
)
|
||||||
|
for it.Next(true) {
|
||||||
|
nodes += 1
|
||||||
|
node := it.Hash()
|
||||||
|
|
||||||
|
// Check the presence for non-empty hash node(embedded node doesn't
|
||||||
|
// have their own hash).
|
||||||
|
if node != (common.Hash{}) {
|
||||||
|
blob, _ := reader.Node(id.Owner, it.Path(), node)
|
||||||
|
if len(blob) == 0 {
|
||||||
|
log.Error("Missing trie node(storage)", "hash", node)
|
||||||
|
return errors.New("missing storage")
|
||||||
|
}
|
||||||
|
hasher.Reset()
|
||||||
|
hasher.Write(blob)
|
||||||
|
hasher.Read(buffer)
|
||||||
|
if !bytes.Equal(buffer, node.Bytes()) {
|
||||||
|
log.Error("Invalid trie node(storage)", "hash", node.Hex(), "value", blob)
|
||||||
|
return errors.New("invalid storage node")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if it.Leaf() {
|
||||||
|
slots += 1
|
||||||
|
}
|
||||||
|
if time.Since(lastReport) > time.Second*8 {
|
||||||
|
logger("Traversing storage", "account", id.Owner, "nodes", nodes, "slots", slots, "elapsed", common.PrettyDuration(time.Since(start)))
|
||||||
|
lastReport = time.Now()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := it.Error(); err != nil {
|
||||||
|
log.Error("Failed to traverse storage trie", "root", id.Root, "err", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
logger("Storage is complete", "account", id.Owner, "nodes", nodes, "slots", slots, "elapsed", common.PrettyDuration(time.Since(start)))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// traverseState is a helper function used for pruning verification.
|
// traverseState is a helper function used for pruning verification.
|
||||||
// Basically it just iterates the trie, ensure all nodes and associated
|
// Basically it just iterates the trie, ensure all nodes and associated
|
||||||
// contract codes are present.
|
// contract codes are present.
|
||||||
|
|
@ -309,6 +432,30 @@ func traverseState(ctx *cli.Context) error {
|
||||||
root = headBlock.Root()
|
root = headBlock.Root()
|
||||||
log.Info("Start traversing the state", "root", root, "number", headBlock.NumberU64())
|
log.Info("Start traversing the state", "root", root, "number", headBlock.NumberU64())
|
||||||
}
|
}
|
||||||
|
// If --account is specified, only traverse the storage trie of that account.
|
||||||
|
if accountStr := ctx.String(utils.AccountFlag.Name); accountStr != "" {
|
||||||
|
accountHash, err := parseAccount(accountStr)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("Failed to parse account", "err", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// Use raw trie since the account key is already hashed.
|
||||||
|
t, err := trie.New(trie.StateTrieID(root), triedb)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("Failed to open state trie", "root", root, "err", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
acc, err := lookupAccount(accountHash, t)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("Failed to look up account", "hash", accountHash, "err", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if acc.Root == types.EmptyRootHash {
|
||||||
|
log.Info("Account has no storage", "hash", accountHash)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return traverseStorage(trie.StorageTrieID(root, accountHash, acc.Root), triedb, true, false)
|
||||||
|
}
|
||||||
t, err := trie.NewStateTrie(trie.StateTrieID(root), triedb)
|
t, err := trie.NewStateTrie(trie.StateTrieID(root), triedb)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("Failed to open trie", "root", root, "err", err)
|
log.Error("Failed to open trie", "root", root, "err", err)
|
||||||
|
|
@ -335,30 +482,10 @@ func traverseState(ctx *cli.Context) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if acc.Root != types.EmptyRootHash {
|
if acc.Root != types.EmptyRootHash {
|
||||||
id := trie.StorageTrieID(root, common.BytesToHash(accIter.Key), acc.Root)
|
err := traverseStorage(trie.StorageTrieID(root, common.BytesToHash(accIter.Key), acc.Root), triedb, false, false)
|
||||||
storageTrie, err := trie.NewStateTrie(id, triedb)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("Failed to open storage trie", "root", acc.Root, "err", err)
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
storageIt, err := storageTrie.NodeIterator(nil)
|
|
||||||
if err != nil {
|
|
||||||
log.Error("Failed to open storage iterator", "root", acc.Root, "err", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
storageIter := trie.NewIterator(storageIt)
|
|
||||||
for storageIter.Next() {
|
|
||||||
slots += 1
|
|
||||||
|
|
||||||
if time.Since(lastReport) > time.Second*8 {
|
|
||||||
log.Info("Traversing state", "accounts", accounts, "slots", slots, "codes", codes, "elapsed", common.PrettyDuration(time.Since(start)))
|
|
||||||
lastReport = time.Now()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if storageIter.Err != nil {
|
|
||||||
log.Error("Failed to traverse storage trie", "root", acc.Root, "err", storageIter.Err)
|
|
||||||
return storageIter.Err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if !bytes.Equal(acc.CodeHash, types.EmptyCodeHash.Bytes()) {
|
if !bytes.Equal(acc.CodeHash, types.EmptyCodeHash.Bytes()) {
|
||||||
if !rawdb.HasCode(chaindb, common.BytesToHash(acc.CodeHash)) {
|
if !rawdb.HasCode(chaindb, common.BytesToHash(acc.CodeHash)) {
|
||||||
|
|
@ -418,6 +545,30 @@ func traverseRawState(ctx *cli.Context) error {
|
||||||
root = headBlock.Root()
|
root = headBlock.Root()
|
||||||
log.Info("Start traversing the state", "root", root, "number", headBlock.NumberU64())
|
log.Info("Start traversing the state", "root", root, "number", headBlock.NumberU64())
|
||||||
}
|
}
|
||||||
|
// If --account is specified, only traverse the storage trie of that account.
|
||||||
|
if accountStr := ctx.String(utils.AccountFlag.Name); accountStr != "" {
|
||||||
|
accountHash, err := parseAccount(accountStr)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("Failed to parse account", "err", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// Use raw trie since the account key is already hashed.
|
||||||
|
t, err := trie.New(trie.StateTrieID(root), triedb)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("Failed to open state trie", "root", root, "err", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
acc, err := lookupAccount(accountHash, t)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("Failed to look up account", "hash", accountHash, "err", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if acc.Root == types.EmptyRootHash {
|
||||||
|
log.Info("Account has no storage", "hash", accountHash)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return traverseStorage(trie.StorageTrieID(root, accountHash, acc.Root), triedb, true, true)
|
||||||
|
}
|
||||||
t, err := trie.NewStateTrie(trie.StateTrieID(root), triedb)
|
t, err := trie.NewStateTrie(trie.StateTrieID(root), triedb)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("Failed to open trie", "root", root, "err", err)
|
log.Error("Failed to open trie", "root", root, "err", err)
|
||||||
|
|
@ -473,50 +624,10 @@ func traverseRawState(ctx *cli.Context) error {
|
||||||
return errors.New("invalid account")
|
return errors.New("invalid account")
|
||||||
}
|
}
|
||||||
if acc.Root != types.EmptyRootHash {
|
if acc.Root != types.EmptyRootHash {
|
||||||
id := trie.StorageTrieID(root, common.BytesToHash(accIter.LeafKey()), acc.Root)
|
err := traverseStorage(trie.StorageTrieID(root, common.BytesToHash(accIter.LeafKey()), acc.Root), triedb, false, true)
|
||||||
storageTrie, err := trie.NewStateTrie(id, triedb)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("Failed to open storage trie", "root", acc.Root, "err", err)
|
|
||||||
return errors.New("missing storage trie")
|
|
||||||
}
|
|
||||||
storageIter, err := storageTrie.NodeIterator(nil)
|
|
||||||
if err != nil {
|
|
||||||
log.Error("Failed to open storage iterator", "root", acc.Root, "err", err)
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
for storageIter.Next(true) {
|
|
||||||
nodes += 1
|
|
||||||
node := storageIter.Hash()
|
|
||||||
|
|
||||||
// Check the presence for non-empty hash node(embedded node doesn't
|
|
||||||
// have their own hash).
|
|
||||||
if node != (common.Hash{}) {
|
|
||||||
blob, _ := reader.Node(common.BytesToHash(accIter.LeafKey()), storageIter.Path(), node)
|
|
||||||
if len(blob) == 0 {
|
|
||||||
log.Error("Missing trie node(storage)", "hash", node)
|
|
||||||
return errors.New("missing storage")
|
|
||||||
}
|
|
||||||
hasher.Reset()
|
|
||||||
hasher.Write(blob)
|
|
||||||
hasher.Read(got)
|
|
||||||
if !bytes.Equal(got, node.Bytes()) {
|
|
||||||
log.Error("Invalid trie node(storage)", "hash", node.Hex(), "value", blob)
|
|
||||||
return errors.New("invalid storage node")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Bump the counter if it's leaf node.
|
|
||||||
if storageIter.Leaf() {
|
|
||||||
slots += 1
|
|
||||||
}
|
|
||||||
if time.Since(lastReport) > time.Second*8 {
|
|
||||||
log.Info("Traversing state", "nodes", nodes, "accounts", accounts, "slots", slots, "codes", codes, "elapsed", common.PrettyDuration(time.Since(start)))
|
|
||||||
lastReport = time.Now()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if storageIter.Error() != nil {
|
|
||||||
log.Error("Failed to traverse storage trie", "root", acc.Root, "err", storageIter.Error())
|
|
||||||
return storageIter.Error()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if !bytes.Equal(acc.CodeHash, types.EmptyCodeHash.Bytes()) {
|
if !bytes.Equal(acc.CodeHash, types.EmptyCodeHash.Bytes()) {
|
||||||
if !rawdb.HasCode(chaindb, common.BytesToHash(acc.CodeHash)) {
|
if !rawdb.HasCode(chaindb, common.BytesToHash(acc.CodeHash)) {
|
||||||
|
|
|
||||||
|
|
@ -266,7 +266,7 @@ func ImportHistory(chain *core.BlockChain, db ethdb.Database, dir string, networ
|
||||||
len(checksums), len(entries))
|
len(checksums), len(entries))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Determine resume point from last successfully imported block
|
// Determine resume point from last successfully imported block.
|
||||||
var resumeBlock uint64
|
var resumeBlock uint64
|
||||||
if tail := rawdb.ReadEraImportTail(db); tail != nil {
|
if tail := rawdb.ReadEraImportTail(db); tail != nil {
|
||||||
resumeBlock = *tail
|
resumeBlock = *tail
|
||||||
|
|
@ -278,9 +278,8 @@ func ImportHistory(chain *core.BlockChain, db ethdb.Database, dir string, networ
|
||||||
reported = time.Now()
|
reported = time.Now()
|
||||||
imported = 0
|
imported = 0
|
||||||
h = sha256.New()
|
h = sha256.New()
|
||||||
scratch = bytes.NewBuffer(nil)
|
buf = bytes.NewBuffer(nil)
|
||||||
)
|
)
|
||||||
|
|
||||||
for i, file := range entries {
|
for i, file := range entries {
|
||||||
err := func() error {
|
err := func() error {
|
||||||
path := filepath.Join(dir, file)
|
path := filepath.Join(dir, file)
|
||||||
|
|
@ -291,33 +290,36 @@ func ImportHistory(chain *core.BlockChain, db ethdb.Database, dir string, networ
|
||||||
}
|
}
|
||||||
defer f.Close()
|
defer f.Close()
|
||||||
|
|
||||||
// Peek at era block range to see if we can skip entirely
|
// Peek at era block range to see if we can skip entirely.
|
||||||
e, err := from(f)
|
e, err := from(f)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("error opening era: %w", err)
|
return fmt.Errorf("error opening era: %w", err)
|
||||||
}
|
}
|
||||||
eraStart := e.Start()
|
eraStart := e.Start()
|
||||||
eraEnd := eraStart + e.Count() - 1
|
eraEnd := eraStart + e.Count() - 1
|
||||||
|
e.Close()
|
||||||
|
|
||||||
// Skip era files fully behind resume point
|
// Skip era files fully behind resume point.
|
||||||
if resumeBlock > 0 && eraEnd <= resumeBlock {
|
if resumeBlock > 0 && eraEnd <= resumeBlock {
|
||||||
log.Debug("Skipping already imported Era file", "file", file, "eraEnd", eraEnd, "resumeBlock", resumeBlock)
|
log.Debug("Skipping already imported Era file", "file", file, "eraEnd", eraEnd, "resumeBlock", resumeBlock)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Validate against checksum file in directory.
|
||||||
if _, err := f.Seek(0, io.SeekStart); err != nil {
|
if _, err := f.Seek(0, io.SeekStart); err != nil {
|
||||||
return fmt.Errorf("seek %s: %w", path, err)
|
return fmt.Errorf("seek %s: %w", path, err)
|
||||||
}
|
}
|
||||||
if _, err := io.Copy(h, f); err != nil {
|
if _, err := io.Copy(h, f); err != nil {
|
||||||
return fmt.Errorf("checksum %s: %w", path, err)
|
return fmt.Errorf("checksum %s: %w", path, err)
|
||||||
}
|
}
|
||||||
got := common.BytesToHash(h.Sum(scratch.Bytes()[:])).Hex()
|
got := common.BytesToHash(h.Sum(buf.Bytes()[:])).Hex()
|
||||||
want := checksums[i]
|
|
||||||
h.Reset()
|
h.Reset()
|
||||||
scratch.Reset()
|
buf.Reset()
|
||||||
if got != want {
|
if got != checksums[i] {
|
||||||
return fmt.Errorf("%s checksum mismatch: have %s want %s", file, got, want)
|
return fmt.Errorf("%s checksum mismatch: have %s want %s", file, got, checksums[i])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Import all block data from Era1.
|
||||||
if _, err := f.Seek(0, io.SeekStart); err != nil {
|
if _, err := f.Seek(0, io.SeekStart); err != nil {
|
||||||
return fmt.Errorf("seek %s: %w", path, err)
|
return fmt.Errorf("seek %s: %w", path, err)
|
||||||
}
|
}
|
||||||
|
|
@ -325,11 +327,42 @@ func ImportHistory(chain *core.BlockChain, db ethdb.Database, dir string, networ
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("error opening era: %w", err)
|
return fmt.Errorf("error opening era: %w", err)
|
||||||
}
|
}
|
||||||
|
defer e.Close()
|
||||||
|
|
||||||
it, err := e.Iterator()
|
it, err := e.Iterator()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("error creating iterator: %w", err)
|
return fmt.Errorf("error creating iterator: %w", err)
|
||||||
}
|
}
|
||||||
|
var (
|
||||||
|
blocks = make([]*types.Block, 0, importBatchSize)
|
||||||
|
receiptsList = make([]types.Receipts, 0, importBatchSize)
|
||||||
|
flush = func() error {
|
||||||
|
if len(blocks) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
enc := types.EncodeBlockReceiptLists(receiptsList)
|
||||||
|
if _, err := chain.InsertReceiptChain(blocks, enc, math.MaxUint64); err != nil {
|
||||||
|
return fmt.Errorf("error inserting blocks %d-%d: %w",
|
||||||
|
blocks[0].NumberU64(), blocks[len(blocks)-1].NumberU64(), err)
|
||||||
|
}
|
||||||
|
// Track the last successfully imported block for resume support.
|
||||||
|
lastBlock := blocks[len(blocks)-1].NumberU64()
|
||||||
|
rawdb.WriteEraImportTail(db, lastBlock)
|
||||||
|
resumeBlock = lastBlock
|
||||||
|
|
||||||
|
imported += len(blocks)
|
||||||
|
if time.Since(reported) >= 8*time.Second {
|
||||||
|
head := blocks[len(blocks)-1].NumberU64()
|
||||||
|
log.Info("Importing Era files", "head", head, "imported", imported,
|
||||||
|
"elapsed", common.PrettyDuration(time.Since(start)))
|
||||||
|
imported = 0
|
||||||
|
reported = time.Now()
|
||||||
|
}
|
||||||
|
blocks = blocks[:0]
|
||||||
|
receiptsList = receiptsList[:0]
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
)
|
||||||
for it.Next() {
|
for it.Next() {
|
||||||
block, err := it.Block()
|
block, err := it.Block()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -338,7 +371,7 @@ func ImportHistory(chain *core.BlockChain, db ethdb.Database, dir string, networ
|
||||||
if block.Number().BitLen() == 0 {
|
if block.Number().BitLen() == 0 {
|
||||||
continue // skip genesis
|
continue // skip genesis
|
||||||
}
|
}
|
||||||
// Skip blocks already imported (mid-epoch resume)
|
// Skip blocks already imported (mid-era resume).
|
||||||
if resumeBlock > 0 && block.Number().Uint64() <= resumeBlock {
|
if resumeBlock > 0 && block.Number().Uint64() <= resumeBlock {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
@ -346,25 +379,18 @@ func ImportHistory(chain *core.BlockChain, db ethdb.Database, dir string, networ
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("error reading receipts %d: %w", it.Number(), err)
|
return fmt.Errorf("error reading receipts %d: %w", it.Number(), err)
|
||||||
}
|
}
|
||||||
enc := types.EncodeBlockReceiptLists([]types.Receipts{receipts})
|
blocks = append(blocks, block)
|
||||||
if _, err := chain.InsertReceiptChain([]*types.Block{block}, enc, math.MaxUint64); err != nil {
|
receiptsList = append(receiptsList, receipts)
|
||||||
return fmt.Errorf("error inserting body %d: %w", it.Number(), err)
|
if len(blocks) == importBatchSize {
|
||||||
}
|
if err := flush(); err != nil {
|
||||||
rawdb.WriteEraImportTail(db, block.Number().Uint64())
|
return err
|
||||||
resumeBlock = block.Number().Uint64()
|
}
|
||||||
imported++
|
|
||||||
|
|
||||||
if time.Since(reported) >= 8*time.Second {
|
|
||||||
log.Info("Importing Era files", "head", it.Number(), "imported", imported,
|
|
||||||
"elapsed", common.PrettyDuration(time.Since(start)))
|
|
||||||
imported = 0
|
|
||||||
reported = time.Now()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err := it.Error(); err != nil {
|
if err := it.Error(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return nil
|
return flush()
|
||||||
}()
|
}()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
|
||||||
|
|
@ -218,6 +218,10 @@ var (
|
||||||
Usage: "Max number of elements (0 = no limit)",
|
Usage: "Max number of elements (0 = no limit)",
|
||||||
Value: 0,
|
Value: 0,
|
||||||
}
|
}
|
||||||
|
AccountFlag = &cli.StringFlag{
|
||||||
|
Name: "account",
|
||||||
|
Usage: "Specifies the account address or hash to traverse a single storage trie",
|
||||||
|
}
|
||||||
OutputFileFlag = &cli.StringFlag{
|
OutputFileFlag = &cli.StringFlag{
|
||||||
Name: "output",
|
Name: "output",
|
||||||
Usage: "Writes the result in json to the output",
|
Usage: "Writes the result in json to the output",
|
||||||
|
|
@ -1580,7 +1584,9 @@ func setOpenTelemetry(ctx *cli.Context, cfg *node.Config) {
|
||||||
if ctx.IsSet(RPCTelemetryTagsFlag.Name) {
|
if ctx.IsSet(RPCTelemetryTagsFlag.Name) {
|
||||||
tcfg.Tags = ctx.String(RPCTelemetryTagsFlag.Name)
|
tcfg.Tags = ctx.String(RPCTelemetryTagsFlag.Name)
|
||||||
}
|
}
|
||||||
tcfg.SampleRatio = ctx.Float64(RPCTelemetrySampleRatioFlag.Name)
|
if ctx.IsSet(RPCTelemetrySampleRatioFlag.Name) {
|
||||||
|
tcfg.SampleRatio = ctx.Float64(RPCTelemetrySampleRatioFlag.Name)
|
||||||
|
}
|
||||||
|
|
||||||
if tcfg.Endpoint != "" && !tcfg.Enabled {
|
if tcfg.Endpoint != "" && !tcfg.Enabled {
|
||||||
log.Warn(fmt.Sprintf("OpenTelemetry endpoint configured but telemetry is not enabled, use --%s to enable.", RPCTelemetryFlag.Name))
|
log.Warn(fmt.Sprintf("OpenTelemetry endpoint configured but telemetry is not enabled, use --%s to enable.", RPCTelemetryFlag.Name))
|
||||||
|
|
|
||||||
|
|
@ -155,7 +155,9 @@ func testConfigFromCLI(ctx *cli.Context) (cfg testConfig) {
|
||||||
}
|
}
|
||||||
|
|
||||||
cfg.historyPruneBlock = new(uint64)
|
cfg.historyPruneBlock = new(uint64)
|
||||||
*cfg.historyPruneBlock = history.PrunePoints[params.MainnetGenesisHash].BlockNumber
|
if p, err := history.NewPolicy(history.KeepPostMerge, params.MainnetGenesisHash); err == nil {
|
||||||
|
*cfg.historyPruneBlock = p.Target.BlockNumber
|
||||||
|
}
|
||||||
case ctx.Bool(testSepoliaFlag.Name):
|
case ctx.Bool(testSepoliaFlag.Name):
|
||||||
cfg.fsys = builtinTestFiles
|
cfg.fsys = builtinTestFiles
|
||||||
if ctx.IsSet(filterQueryFileFlag.Name) {
|
if ctx.IsSet(filterQueryFileFlag.Name) {
|
||||||
|
|
@ -180,7 +182,9 @@ func testConfigFromCLI(ctx *cli.Context) (cfg testConfig) {
|
||||||
}
|
}
|
||||||
|
|
||||||
cfg.historyPruneBlock = new(uint64)
|
cfg.historyPruneBlock = new(uint64)
|
||||||
*cfg.historyPruneBlock = history.PrunePoints[params.SepoliaGenesisHash].BlockNumber
|
if p, err := history.NewPolicy(history.KeepPostMerge, params.SepoliaGenesisHash); err == nil {
|
||||||
|
*cfg.historyPruneBlock = p.Target.BlockNumber
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
cfg.fsys = os.DirFS(".")
|
cfg.fsys = os.DirFS(".")
|
||||||
cfg.filterQueryFile = ctx.String(filterQueryFileFlag.Name)
|
cfg.filterQueryFile = ctx.String(filterQueryFileFlag.Name)
|
||||||
|
|
|
||||||
|
|
@ -194,9 +194,8 @@ type BlockChainConfig struct {
|
||||||
SnapshotNoBuild bool // Whether the background generation is allowed
|
SnapshotNoBuild bool // Whether the background generation is allowed
|
||||||
SnapshotWait bool // Wait for snapshot construction on startup. TODO(karalabe): This is a dirty hack for testing, nuke it
|
SnapshotWait bool // Wait for snapshot construction on startup. TODO(karalabe): This is a dirty hack for testing, nuke it
|
||||||
|
|
||||||
// This defines the cutoff block for history expiry.
|
// HistoryPolicy defines the chain history pruning intent.
|
||||||
// Blocks before this number may be unavailable in the chain database.
|
HistoryPolicy history.HistoryPolicy
|
||||||
ChainHistoryMode history.HistoryMode
|
|
||||||
|
|
||||||
// Misc options
|
// Misc options
|
||||||
NoPrefetch bool // Whether to disable heuristic state prefetching when processing blocks
|
NoPrefetch bool // Whether to disable heuristic state prefetching when processing blocks
|
||||||
|
|
@ -227,13 +226,13 @@ type BlockChainConfig struct {
|
||||||
// Note the returned object is safe to modify!
|
// Note the returned object is safe to modify!
|
||||||
func DefaultConfig() *BlockChainConfig {
|
func DefaultConfig() *BlockChainConfig {
|
||||||
return &BlockChainConfig{
|
return &BlockChainConfig{
|
||||||
TrieCleanLimit: 256,
|
TrieCleanLimit: 256,
|
||||||
TrieDirtyLimit: 256,
|
TrieDirtyLimit: 256,
|
||||||
TrieTimeLimit: 5 * time.Minute,
|
TrieTimeLimit: 5 * time.Minute,
|
||||||
StateScheme: rawdb.HashScheme,
|
StateScheme: rawdb.HashScheme,
|
||||||
SnapshotLimit: 256,
|
SnapshotLimit: 256,
|
||||||
SnapshotWait: true,
|
SnapshotWait: true,
|
||||||
ChainHistoryMode: history.KeepAll,
|
HistoryPolicy: history.HistoryPolicy{Mode: history.KeepAll},
|
||||||
// Transaction indexing is disabled by default.
|
// Transaction indexing is disabled by default.
|
||||||
// This is appropriate for most unit tests.
|
// This is appropriate for most unit tests.
|
||||||
TxLookupLimit: -1,
|
TxLookupLimit: -1,
|
||||||
|
|
@ -715,82 +714,44 @@ func (bc *BlockChain) loadLastState() error {
|
||||||
|
|
||||||
// initializeHistoryPruning sets bc.historyPrunePoint.
|
// initializeHistoryPruning sets bc.historyPrunePoint.
|
||||||
func (bc *BlockChain) initializeHistoryPruning(latest uint64) error {
|
func (bc *BlockChain) initializeHistoryPruning(latest uint64) error {
|
||||||
var (
|
freezerTail, _ := bc.db.Tail()
|
||||||
freezerTail, _ = bc.db.Tail()
|
policy := bc.cfg.HistoryPolicy
|
||||||
genesisHash = bc.genesisBlock.Hash()
|
|
||||||
mergePoint = history.MergePrunePoints[genesisHash]
|
|
||||||
praguePoint = history.PraguePrunePoints[genesisHash]
|
|
||||||
)
|
|
||||||
switch bc.cfg.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.
|
|
||||||
if mergePoint != nil && freezerTail == mergePoint.BlockNumber {
|
|
||||||
bc.historyPrunePoint.Store(mergePoint)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if praguePoint != nil && freezerTail == praguePoint.BlockNumber {
|
|
||||||
bc.historyPrunePoint.Store(praguePoint)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
log.Error("Chain history database is pruned with unknown configuration", "tail", freezerTail)
|
|
||||||
return errors.New("unexpected database tail")
|
|
||||||
|
|
||||||
case history.KeepPostMerge:
|
switch policy.Mode {
|
||||||
if mergePoint == nil {
|
case history.KeepAll:
|
||||||
return errors.New("history pruning requested for unknown network")
|
if freezerTail > 0 {
|
||||||
|
// Database was pruned externally. Record the actual state.
|
||||||
|
log.Warn("Chain history database is pruned", "tail", freezerTail, "mode", policy.Mode)
|
||||||
|
bc.historyPrunePoint.Store(&history.PrunePoint{
|
||||||
|
BlockNumber: freezerTail,
|
||||||
|
BlockHash: bc.GetCanonicalHash(freezerTail),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
if freezerTail == 0 && latest != 0 {
|
|
||||||
log.Error(fmt.Sprintf("Chain history mode is configured as %q, but database is not pruned.", bc.cfg.ChainHistoryMode.String()))
|
|
||||||
log.Error("Run 'geth prune-history --history.chain postmerge' to prune pre-merge history.")
|
|
||||||
return errors.New("history pruning requested via configuration")
|
|
||||||
}
|
|
||||||
// Check if DB is pruned further than requested (to Prague).
|
|
||||||
if praguePoint != nil && freezerTail == praguePoint.BlockNumber {
|
|
||||||
log.Error("Chain history database is pruned to Prague block, but postmerge mode was requested.")
|
|
||||||
log.Error("History cannot be unpruned. To restore history, use 'geth import-history'.")
|
|
||||||
log.Error("If you intended to keep post-Prague history, use '--history.chain postprague' instead.")
|
|
||||||
return errors.New("database pruned beyond requested history mode")
|
|
||||||
}
|
|
||||||
if freezerTail > 0 && freezerTail != mergePoint.BlockNumber {
|
|
||||||
return errors.New("chain history database pruned to unknown block")
|
|
||||||
}
|
|
||||||
bc.historyPrunePoint.Store(mergePoint)
|
|
||||||
return nil
|
return nil
|
||||||
|
|
||||||
case history.KeepPostPrague:
|
case history.KeepPostMerge, history.KeepPostPrague:
|
||||||
if praguePoint == nil {
|
target := policy.Target
|
||||||
return errors.New("history pruning requested for unknown network")
|
// Already at the target.
|
||||||
}
|
if freezerTail == target.BlockNumber {
|
||||||
// Check if already at the prague prune point.
|
bc.historyPrunePoint.Store(target)
|
||||||
if freezerTail == praguePoint.BlockNumber {
|
|
||||||
bc.historyPrunePoint.Store(praguePoint)
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
// Check if database needs pruning.
|
// Database is pruned beyond the target.
|
||||||
if latest != 0 {
|
if freezerTail > target.BlockNumber {
|
||||||
if freezerTail == 0 {
|
return fmt.Errorf("database pruned beyond requested history (tail=%d, target=%d)", freezerTail, target.BlockNumber)
|
||||||
log.Error(fmt.Sprintf("Chain history mode is configured as %q, but database is not pruned.", bc.cfg.ChainHistoryMode.String()))
|
|
||||||
log.Error("Run 'geth prune-history --history.chain postprague' to prune pre-Prague history.")
|
|
||||||
return errors.New("history pruning requested via configuration")
|
|
||||||
}
|
|
||||||
if mergePoint != nil && freezerTail == mergePoint.BlockNumber {
|
|
||||||
log.Error(fmt.Sprintf("Chain history mode is configured as %q, but database is only pruned to merge block.", bc.cfg.ChainHistoryMode.String()))
|
|
||||||
log.Error("Run 'geth prune-history --history.chain postprague' to prune pre-Prague history.")
|
|
||||||
return errors.New("history pruning requested via configuration")
|
|
||||||
}
|
|
||||||
log.Error("Chain history database is pruned to unknown block", "tail", freezerTail)
|
|
||||||
return errors.New("unexpected database tail")
|
|
||||||
}
|
}
|
||||||
// Fresh database (latest == 0), will sync from prague point.
|
// Database needs pruning (freezerTail < target).
|
||||||
bc.historyPrunePoint.Store(praguePoint)
|
if latest != 0 {
|
||||||
|
log.Error(fmt.Sprintf("Chain history mode is configured as %q, but database is not pruned to the target block.", policy.Mode.String()))
|
||||||
|
log.Error(fmt.Sprintf("Run 'geth prune-history --history.chain %s' to prune history.", policy.Mode.String()))
|
||||||
|
return errors.New("history pruning required")
|
||||||
|
}
|
||||||
|
// Fresh database (latest == 0), will sync from target point.
|
||||||
|
bc.historyPrunePoint.Store(target)
|
||||||
return nil
|
return nil
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("invalid history mode: %d", bc.cfg.ChainHistoryMode)
|
return fmt.Errorf("invalid history mode: %d", policy.Mode)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -2209,24 +2170,18 @@ func (bc *BlockChain) ProcessBlock(ctx context.Context, parentRoot common.Hash,
|
||||||
// If we are past Byzantium, enable prefetching to pull in trie node paths
|
// If we are past Byzantium, enable prefetching to pull in trie node paths
|
||||||
// while processing transactions. Before Byzantium the prefetcher is mostly
|
// while processing transactions. Before Byzantium the prefetcher is mostly
|
||||||
// useless due to the intermediate root hashing after each transaction.
|
// useless due to the intermediate root hashing after each transaction.
|
||||||
var (
|
var witness *stateless.Witness
|
||||||
witness *stateless.Witness
|
|
||||||
witnessStats *stateless.WitnessStats
|
|
||||||
)
|
|
||||||
if bc.chainConfig.IsByzantium(block.Number()) {
|
if bc.chainConfig.IsByzantium(block.Number()) {
|
||||||
// Generate witnesses either if we're self-testing, or if it's the
|
// Generate witnesses either if we're self-testing, or if it's the
|
||||||
// only block being inserted. A bit crude, but witnesses are huge,
|
// only block being inserted. A bit crude, but witnesses are huge,
|
||||||
// so we refuse to make an entire chain of them.
|
// so we refuse to make an entire chain of them.
|
||||||
if config.StatelessSelfValidation || config.MakeWitness {
|
if config.StatelessSelfValidation || config.MakeWitness {
|
||||||
witness, err = stateless.NewWitness(block.Header(), bc)
|
witness, err = stateless.NewWitness(block.Header(), bc, config.EnableWitnessStats)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if config.EnableWitnessStats {
|
|
||||||
witnessStats = stateless.NewWitnessStats()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
statedb.StartPrefetcher("chain", witness, witnessStats)
|
statedb.StartPrefetcher("chain", witness)
|
||||||
defer statedb.StopPrefetcher()
|
defer statedb.StopPrefetcher()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -2345,8 +2300,8 @@ func (bc *BlockChain) ProcessBlock(ctx context.Context, parentRoot common.Hash,
|
||||||
stats.BlockWrite = time.Since(wstart) - max(statedb.AccountCommits, statedb.StorageCommits) /* concurrent */ - statedb.DatabaseCommits
|
stats.BlockWrite = time.Since(wstart) - max(statedb.AccountCommits, statedb.StorageCommits) /* concurrent */ - statedb.DatabaseCommits
|
||||||
}
|
}
|
||||||
// Report the collected witness statistics
|
// Report the collected witness statistics
|
||||||
if witnessStats != nil {
|
if witness != nil {
|
||||||
witnessStats.ReportMetrics(block.NumberU64())
|
witness.ReportMetrics(block.NumberU64())
|
||||||
}
|
}
|
||||||
elapsed := time.Since(startTime) + 1 // prevent zero division
|
elapsed := time.Since(startTime) + 1 // prevent zero division
|
||||||
stats.TotalTime = elapsed
|
stats.TotalTime = elapsed
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,6 @@ 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"
|
||||||
|
|
@ -4337,26 +4336,13 @@ func TestInsertChainWithCutoff(t *testing.T) {
|
||||||
func testInsertChainWithCutoff(t *testing.T, cutoff uint64, ancientLimit uint64, genesis *Genesis, blocks []*types.Block, receipts []types.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)))
|
// 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()
|
ghash := genesis.ToBlock().Hash()
|
||||||
cutoffBlock := blocks[cutoff-1]
|
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 := DefaultConfig().WithStateScheme(rawdb.PathScheme)
|
|
||||||
config.ChainHistoryMode = history.KeepPostMerge
|
|
||||||
|
|
||||||
db, _ := rawdb.Open(rawdb.NewMemoryDatabase(), rawdb.OpenOptions{})
|
db, _ := rawdb.Open(rawdb.NewMemoryDatabase(), rawdb.OpenOptions{})
|
||||||
defer db.Close()
|
defer db.Close()
|
||||||
|
|
||||||
options := DefaultConfig().WithStateScheme(rawdb.PathScheme)
|
chain, _ := NewBlockChain(db, genesis, beacon.New(ethash.NewFaker()), DefaultConfig().WithStateScheme(rawdb.PathScheme))
|
||||||
chain, _ := NewBlockChain(db, genesis, beacon.New(ethash.NewFaker()), options)
|
|
||||||
defer chain.Stop()
|
defer chain.Stop()
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|
|
||||||
|
|
@ -66,10 +66,6 @@ var (
|
||||||
// have enough funds for transfer(topmost call only).
|
// have enough funds for transfer(topmost call only).
|
||||||
ErrInsufficientFundsForTransfer = errors.New("insufficient funds for transfer")
|
ErrInsufficientFundsForTransfer = errors.New("insufficient funds for transfer")
|
||||||
|
|
||||||
// ErrMaxInitCodeSizeExceeded is returned if creation transaction provides the init code bigger
|
|
||||||
// than init code size limit.
|
|
||||||
ErrMaxInitCodeSizeExceeded = errors.New("max initcode size exceeded")
|
|
||||||
|
|
||||||
// ErrInsufficientBalanceWitness is returned if the transaction sender has enough
|
// ErrInsufficientBalanceWitness is returned if the transaction sender has enough
|
||||||
// funds to cover the transfer, but not enough to pay for witness access/modification
|
// funds to cover the transfer, but not enough to pay for witness access/modification
|
||||||
// costs for the transaction
|
// costs for the transaction
|
||||||
|
|
|
||||||
169
core/eth_transfer_logs_test.go
Normal file
169
core/eth_transfer_logs_test.go
Normal file
|
|
@ -0,0 +1,169 @@
|
||||||
|
// Copyright 2026 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 core
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"math/big"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"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/types"
|
||||||
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
|
"github.com/ethereum/go-ethereum/params"
|
||||||
|
)
|
||||||
|
|
||||||
|
var ethTransferTestCode = common.FromHex("6080604052600436106100345760003560e01c8063574ffc311461003957806366e41cb714610090578063f8a8fd6d1461009a575b600080fd5b34801561004557600080fd5b5061004e6100a4565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b6100986100ac565b005b6100a26100f5565b005b63deadbeef81565b7f38e80b5c85ba49b7280ccc8f22548faa62ae30d5a008a1b168fba5f47f5d1ee560405160405180910390a1631234567873ffffffffffffffffffffffffffffffffffffffff16ff5b7f24ec1d3ff24c2f6ff210738839dbc339cd45a5294d85c79361016243157aae7b60405160405180910390a163deadbeef73ffffffffffffffffffffffffffffffffffffffff166002348161014657fe5b046040516024016040516020818303038152906040527f66e41cb7000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19166020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff83818316178352505050506040518082805190602001908083835b602083106101fd57805182526020820191506020810190506020830392506101da565b6001836020036101000a03801982511681845116808217855250505050505090500191505060006040518083038185875af1925050503d806000811461025f576040519150601f19603f3d011682016040523d82523d6000602084013e610264565b606091505b50505056fea265627a7a723158202cce817a434785d8560c200762f972d453ccd30694481be7545f9035a512826364736f6c63430005100032")
|
||||||
|
|
||||||
|
/*
|
||||||
|
pragma solidity >=0.4.22 <0.6.0;
|
||||||
|
|
||||||
|
contract TestLogs {
|
||||||
|
|
||||||
|
address public constant target_contract = 0x00000000000000000000000000000000DeaDBeef;
|
||||||
|
address payable constant selfdestruct_addr = 0x0000000000000000000000000000000012345678;
|
||||||
|
|
||||||
|
event Response(bool success, bytes data);
|
||||||
|
event TestEvent();
|
||||||
|
event TestEvent2();
|
||||||
|
|
||||||
|
function test() public payable {
|
||||||
|
emit TestEvent();
|
||||||
|
target_contract.call.value(msg.value/2)(abi.encodeWithSignature("test2()"));
|
||||||
|
}
|
||||||
|
function test2() public payable {
|
||||||
|
emit TestEvent2();
|
||||||
|
selfdestruct(selfdestruct_addr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
// TestEthTransferLogs tests EIP-7708 ETH transfer log output by simulating a
|
||||||
|
// scenario including transaction, CALL and SELFDESTRUCT value transfers, and
|
||||||
|
// also "ordinary" logs emitted. The same scenario is also tested with no value
|
||||||
|
// transferred.
|
||||||
|
func TestEthTransferLogs(t *testing.T) {
|
||||||
|
testEthTransferLogs(t, 1_000_000_000)
|
||||||
|
testEthTransferLogs(t, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testEthTransferLogs(t *testing.T, value uint64) {
|
||||||
|
var (
|
||||||
|
key1, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
|
||||||
|
addr1 = crypto.PubkeyToAddress(key1.PublicKey)
|
||||||
|
addr2 = common.HexToAddress("cafebabe") // caller
|
||||||
|
addr3 = common.HexToAddress("deadbeef") // callee
|
||||||
|
addr4 = common.HexToAddress("12345678") // selfdestruct target
|
||||||
|
testEvent = crypto.Keccak256Hash([]byte("TestEvent()"))
|
||||||
|
testEvent2 = crypto.Keccak256Hash([]byte("TestEvent2()"))
|
||||||
|
config = *params.MergedTestChainConfig
|
||||||
|
signer = types.LatestSigner(&config)
|
||||||
|
engine = beacon.New(ethash.NewFaker())
|
||||||
|
)
|
||||||
|
|
||||||
|
//TODO remove this hacky config initialization when final Amsterdam config is available
|
||||||
|
config.AmsterdamTime = new(uint64)
|
||||||
|
blobConfig := *config.BlobScheduleConfig
|
||||||
|
blobConfig.Amsterdam = blobConfig.Osaka
|
||||||
|
config.BlobScheduleConfig = &blobConfig
|
||||||
|
|
||||||
|
gspec := &Genesis{
|
||||||
|
Config: &config,
|
||||||
|
Alloc: types.GenesisAlloc{
|
||||||
|
addr1: {Balance: newGwei(1000000000)},
|
||||||
|
addr2: {Code: ethTransferTestCode},
|
||||||
|
addr3: {Code: ethTransferTestCode},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
_, blocks, receipts := GenerateChainWithGenesis(gspec, engine, 1, func(i int, b *BlockGen) {
|
||||||
|
tx := types.MustSignNewTx(key1, signer, &types.DynamicFeeTx{
|
||||||
|
ChainID: gspec.Config.ChainID,
|
||||||
|
Nonce: 0,
|
||||||
|
To: &addr2,
|
||||||
|
Gas: 500_000,
|
||||||
|
GasFeeCap: newGwei(5),
|
||||||
|
GasTipCap: newGwei(5),
|
||||||
|
Value: big.NewInt(int64(value)),
|
||||||
|
Data: common.FromHex("f8a8fd6d"),
|
||||||
|
})
|
||||||
|
b.AddTx(tx)
|
||||||
|
})
|
||||||
|
|
||||||
|
blockHash := blocks[0].Hash()
|
||||||
|
txHash := blocks[0].Transactions()[0].Hash()
|
||||||
|
addr2hash := func(addr common.Address) (hash common.Hash) {
|
||||||
|
copy(hash[12:], addr[:])
|
||||||
|
return
|
||||||
|
}
|
||||||
|
u256 := func(amount uint64) []byte {
|
||||||
|
data := make([]byte, 32)
|
||||||
|
binary.BigEndian.PutUint64(data[24:], amount)
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
|
var expLogs = []*types.Log{
|
||||||
|
{
|
||||||
|
Address: params.SystemAddress,
|
||||||
|
Topics: []common.Hash{params.EthTransferLogEvent, addr2hash(addr1), addr2hash(addr2)},
|
||||||
|
Data: u256(value),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Address: addr2,
|
||||||
|
Topics: []common.Hash{testEvent},
|
||||||
|
Data: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Address: params.SystemAddress,
|
||||||
|
Topics: []common.Hash{params.EthTransferLogEvent, addr2hash(addr2), addr2hash(addr3)},
|
||||||
|
Data: u256(value / 2),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Address: addr3,
|
||||||
|
Topics: []common.Hash{testEvent2},
|
||||||
|
Data: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Address: params.SystemAddress,
|
||||||
|
Topics: []common.Hash{params.EthTransferLogEvent, addr2hash(addr3), addr2hash(addr4)},
|
||||||
|
Data: u256(value / 2),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
if value == 0 {
|
||||||
|
// no ETH transfer logs expected with zero value
|
||||||
|
expLogs = []*types.Log{expLogs[1], expLogs[3]}
|
||||||
|
}
|
||||||
|
for i, log := range expLogs {
|
||||||
|
log.BlockNumber = 1
|
||||||
|
log.BlockHash = blockHash
|
||||||
|
log.BlockTimestamp = 10
|
||||||
|
log.TxIndex = 0
|
||||||
|
log.TxHash = txHash
|
||||||
|
log.Index = uint(i)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(expLogs) != len(receipts[0][0].Logs) {
|
||||||
|
t.Fatalf("Incorrect number of logs (expected: %d, got: %d)", len(expLogs), len(receipts[0][0].Logs))
|
||||||
|
}
|
||||||
|
for i, log := range receipts[0][0].Logs {
|
||||||
|
if !reflect.DeepEqual(expLogs[i], log) {
|
||||||
|
t.Fatalf("Incorrect log at index %d (expected: %v, got: %v)", i, expLogs[i], log)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -25,6 +25,7 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/core/tracing"
|
"github.com/ethereum/go-ethereum/core/tracing"
|
||||||
"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/params"
|
||||||
"github.com/holiman/uint256"
|
"github.com/holiman/uint256"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -138,7 +139,10 @@ func CanTransfer(db vm.StateDB, addr common.Address, amount *uint256.Int) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Transfer subtracts amount from sender and adds amount to recipient using the given Db
|
// Transfer subtracts amount from sender and adds amount to recipient using the given Db
|
||||||
func Transfer(db vm.StateDB, sender, recipient common.Address, amount *uint256.Int) {
|
func Transfer(db vm.StateDB, sender, recipient common.Address, amount *uint256.Int, rules *params.Rules) {
|
||||||
db.SubBalance(sender, amount, tracing.BalanceChangeTransfer)
|
db.SubBalance(sender, amount, tracing.BalanceChangeTransfer)
|
||||||
db.AddBalance(recipient, amount, tracing.BalanceChangeTransfer)
|
db.AddBalance(recipient, amount, tracing.BalanceChangeTransfer)
|
||||||
|
if rules.IsAmsterdam && !amount.IsZero() && sender != recipient {
|
||||||
|
db.AddLog(types.EthTransferLog(sender, recipient, amount))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -77,57 +77,62 @@ func (m *HistoryMode) UnmarshalText(text []byte) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PrunePoint identifies a specific block for history pruning.
|
||||||
type PrunePoint struct {
|
type PrunePoint struct {
|
||||||
BlockNumber uint64
|
BlockNumber uint64
|
||||||
BlockHash common.Hash
|
BlockHash common.Hash
|
||||||
}
|
}
|
||||||
|
|
||||||
// MergePrunePoints contains the pre-defined history pruning cutoff blocks for known networks.
|
// staticPrunePoints contains the pre-defined history pruning cutoff blocks for
|
||||||
// They point to the first post-merge block. Any pruning should truncate *up to* but excluding
|
// known networks, keyed by history mode and genesis hash. They point to the first
|
||||||
// the given block.
|
// block after the respective fork. Any pruning should truncate *up to* but
|
||||||
var MergePrunePoints = map[common.Hash]*PrunePoint{
|
// excluding the given block.
|
||||||
// mainnet
|
var staticPrunePoints = map[HistoryMode]map[common.Hash]*PrunePoint{
|
||||||
params.MainnetGenesisHash: {
|
KeepPostMerge: {
|
||||||
BlockNumber: 15537393,
|
params.MainnetGenesisHash: {
|
||||||
BlockHash: common.HexToHash("0x55b11b918355b1ef9c5db810302ebad0bf2544255b530cdce90674d5887bb286"),
|
BlockNumber: 15537393,
|
||||||
|
BlockHash: common.HexToHash("0x55b11b918355b1ef9c5db810302ebad0bf2544255b530cdce90674d5887bb286"),
|
||||||
|
},
|
||||||
|
params.SepoliaGenesisHash: {
|
||||||
|
BlockNumber: 1450409,
|
||||||
|
BlockHash: common.HexToHash("0x229f6b18ca1552f1d5146deceb5387333f40dc6275aebee3f2c5c4ece07d02db"),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
// sepolia
|
KeepPostPrague: {
|
||||||
params.SepoliaGenesisHash: {
|
params.MainnetGenesisHash: {
|
||||||
BlockNumber: 1450409,
|
BlockNumber: 22431084,
|
||||||
BlockHash: common.HexToHash("0x229f6b18ca1552f1d5146deceb5387333f40dc6275aebee3f2c5c4ece07d02db"),
|
BlockHash: common.HexToHash("0x50c8cab760b2948349c590461b166773c45d8f4858cccf5a43025ab2960152e8"),
|
||||||
|
},
|
||||||
|
params.SepoliaGenesisHash: {
|
||||||
|
BlockNumber: 7836331,
|
||||||
|
BlockHash: common.HexToHash("0xe6571beb68bf24dbd8a6ba354518996920c55a3f8d8fdca423e391b8ad071f22"),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// PraguePrunePoints contains the pre-defined history pruning cutoff blocks for the Prague
|
// HistoryPolicy describes the configured history pruning strategy. It captures
|
||||||
// (Pectra) upgrade. They point to the first post-Prague block. Any pruning should truncate
|
// user intent as opposed to the actual DB state.
|
||||||
// *up to* but excluding the given block.
|
type HistoryPolicy struct {
|
||||||
var PraguePrunePoints = map[common.Hash]*PrunePoint{
|
Mode HistoryMode
|
||||||
// mainnet - first Prague block (May 7, 2025)
|
// Static prune point for PostMerge/PostPrague, nil otherwise.
|
||||||
params.MainnetGenesisHash: {
|
Target *PrunePoint
|
||||||
BlockNumber: 22431084,
|
|
||||||
BlockHash: common.HexToHash("0x50c8cab760b2948349c590461b166773c45d8f4858cccf5a43025ab2960152e8"),
|
|
||||||
},
|
|
||||||
// sepolia - first Prague block (March 5, 2025)
|
|
||||||
params.SepoliaGenesisHash: {
|
|
||||||
BlockNumber: 7836331,
|
|
||||||
BlockHash: common.HexToHash("0xe6571beb68bf24dbd8a6ba354518996920c55a3f8d8fdca423e391b8ad071f22"),
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// PrunePoints is an alias for MergePrunePoints for backward compatibility.
|
// NewPolicy constructs a HistoryPolicy from the given mode and genesis hash.
|
||||||
// Deprecated: Use GetPrunePoint or MergePrunePoints directly.
|
func NewPolicy(mode HistoryMode, genesisHash common.Hash) (HistoryPolicy, error) {
|
||||||
var PrunePoints = MergePrunePoints
|
|
||||||
|
|
||||||
// GetPrunePoint returns the prune point for the given genesis hash and history mode.
|
|
||||||
// Returns nil if no prune point is defined for the given combination.
|
|
||||||
func GetPrunePoint(genesisHash common.Hash, mode HistoryMode) *PrunePoint {
|
|
||||||
switch mode {
|
switch mode {
|
||||||
case KeepPostMerge:
|
case KeepAll:
|
||||||
return MergePrunePoints[genesisHash]
|
return HistoryPolicy{Mode: KeepAll}, nil
|
||||||
case KeepPostPrague:
|
|
||||||
return PraguePrunePoints[genesisHash]
|
case KeepPostMerge, KeepPostPrague:
|
||||||
|
point := staticPrunePoints[mode][genesisHash]
|
||||||
|
if point == nil {
|
||||||
|
return HistoryPolicy{}, fmt.Errorf("%s history pruning not available for network %s", mode, genesisHash.Hex())
|
||||||
|
}
|
||||||
|
return HistoryPolicy{Mode: mode, Target: point}, nil
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return nil
|
return HistoryPolicy{}, fmt.Errorf("invalid history mode: %d", mode)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
58
core/history/historymode_test.go
Normal file
58
core/history/historymode_test.go
Normal file
|
|
@ -0,0 +1,58 @@
|
||||||
|
// Copyright 2026 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 history
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/ethereum/go-ethereum/params"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNewPolicy(t *testing.T) {
|
||||||
|
// KeepAll: no target.
|
||||||
|
p, err := NewPolicy(KeepAll, params.MainnetGenesisHash)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("KeepAll: %v", err)
|
||||||
|
}
|
||||||
|
if p.Mode != KeepAll || p.Target != nil {
|
||||||
|
t.Errorf("KeepAll: unexpected policy %+v", p)
|
||||||
|
}
|
||||||
|
|
||||||
|
// PostMerge: resolves known mainnet prune point.
|
||||||
|
p, err = NewPolicy(KeepPostMerge, params.MainnetGenesisHash)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("PostMerge: %v", err)
|
||||||
|
}
|
||||||
|
if p.Target == nil || p.Target.BlockNumber != 15537393 {
|
||||||
|
t.Errorf("PostMerge: unexpected target %+v", p.Target)
|
||||||
|
}
|
||||||
|
|
||||||
|
// PostPrague: resolves known mainnet prune point.
|
||||||
|
p, err = NewPolicy(KeepPostPrague, params.MainnetGenesisHash)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("PostPrague: %v", err)
|
||||||
|
}
|
||||||
|
if p.Target == nil || p.Target.BlockNumber != 22431084 {
|
||||||
|
t.Errorf("PostPrague: unexpected target %+v", p.Target)
|
||||||
|
}
|
||||||
|
|
||||||
|
// PostMerge on unknown network: error.
|
||||||
|
if _, err = NewPolicy(KeepPostMerge, common.HexToHash("0xdeadbeef")); err == nil {
|
||||||
|
t.Fatal("PostMerge unknown network: expected error")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -480,7 +480,7 @@ func InspectDatabase(db ethdb.Database, keyPrefix, keyStart []byte) error {
|
||||||
receipts.add(size)
|
receipts.add(size)
|
||||||
case bytes.HasPrefix(key, headerPrefix) && bytes.HasSuffix(key, headerTDSuffix) && len(key) == (len(headerPrefix)+8+common.HashLength+len(headerTDSuffix)):
|
case bytes.HasPrefix(key, headerPrefix) && bytes.HasSuffix(key, headerTDSuffix) && len(key) == (len(headerPrefix)+8+common.HashLength+len(headerTDSuffix)):
|
||||||
tds.add(size)
|
tds.add(size)
|
||||||
case bytes.HasPrefix(key, headerPrefix) && bytes.HasSuffix(key, headerHashSuffix) && len(key) == (len(headerPrefix)+8+common.HashLength+len(headerHashSuffix)):
|
case bytes.HasPrefix(key, headerPrefix) && bytes.HasSuffix(key, headerHashSuffix) && len(key) == (len(headerPrefix)+8+len(headerHashSuffix)):
|
||||||
numHashPairings.add(size)
|
numHashPairings.add(size)
|
||||||
case bytes.HasPrefix(key, headerNumberPrefix) && len(key) == (len(headerNumberPrefix)+common.HashLength):
|
case bytes.HasPrefix(key, headerNumberPrefix) && len(key) == (len(headerNumberPrefix)+common.HashLength):
|
||||||
hashNumPairings.add(size)
|
hashNumPairings.add(size)
|
||||||
|
|
|
||||||
|
|
@ -135,8 +135,7 @@ type StateDB struct {
|
||||||
journal *journal
|
journal *journal
|
||||||
|
|
||||||
// State witness if cross validation is needed
|
// State witness if cross validation is needed
|
||||||
witness *stateless.Witness
|
witness *stateless.Witness
|
||||||
witnessStats *stateless.WitnessStats
|
|
||||||
|
|
||||||
// Measurements gathered during execution for debugging purposes
|
// Measurements gathered during execution for debugging purposes
|
||||||
AccountReads time.Duration
|
AccountReads time.Duration
|
||||||
|
|
@ -201,13 +200,12 @@ func NewWithReader(root common.Hash, db Database, reader Reader) (*StateDB, erro
|
||||||
// StartPrefetcher initializes a new trie prefetcher to pull in nodes from the
|
// StartPrefetcher initializes a new trie prefetcher to pull in nodes from the
|
||||||
// state trie concurrently while the state is mutated so that when we reach the
|
// state trie concurrently while the state is mutated so that when we reach the
|
||||||
// commit phase, most of the needed data is already hot.
|
// commit phase, most of the needed data is already hot.
|
||||||
func (s *StateDB) StartPrefetcher(namespace string, witness *stateless.Witness, witnessStats *stateless.WitnessStats) {
|
func (s *StateDB) StartPrefetcher(namespace string, witness *stateless.Witness) {
|
||||||
// Terminate any previously running prefetcher
|
// Terminate any previously running prefetcher
|
||||||
s.StopPrefetcher()
|
s.StopPrefetcher()
|
||||||
|
|
||||||
// Enable witness collection if requested
|
// Enable witness collection if requested
|
||||||
s.witness = witness
|
s.witness = witness
|
||||||
s.witnessStats = witnessStats
|
|
||||||
|
|
||||||
// With the switch to the Proof-of-Stake consensus algorithm, block production
|
// With the switch to the Proof-of-Stake consensus algorithm, block production
|
||||||
// rewards are now handled at the consensus layer. Consequently, a block may
|
// rewards are now handled at the consensus layer. Consequently, a block may
|
||||||
|
|
@ -743,6 +741,41 @@ func (s *StateDB) GetRefund() uint64 {
|
||||||
return s.refund
|
return s.refund
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type removedAccountWithBalance struct {
|
||||||
|
address common.Address
|
||||||
|
balance *uint256.Int
|
||||||
|
}
|
||||||
|
|
||||||
|
// EmitLogsForBurnAccounts emits the eth burn logs for accounts scheduled for
|
||||||
|
// removal which still have positive balance. The purpose of this function is
|
||||||
|
// to handle a corner case of EIP-7708 where a self-destructed account might
|
||||||
|
// still receive funds between sending/burning its previous balance and actual
|
||||||
|
// removal. In this case the burning of these remaining balances still need to
|
||||||
|
// be logged.
|
||||||
|
// Specification EIP-7708: https://eips.ethereum.org/EIPS/eip-7708
|
||||||
|
//
|
||||||
|
// This function should only be invoked at the transaction boundary, specifically
|
||||||
|
// before the Finalise.
|
||||||
|
func (s *StateDB) EmitLogsForBurnAccounts() {
|
||||||
|
var list []removedAccountWithBalance
|
||||||
|
for addr := range s.journal.dirties {
|
||||||
|
if obj, exist := s.stateObjects[addr]; exist && obj.selfDestructed && !obj.Balance().IsZero() {
|
||||||
|
list = append(list, removedAccountWithBalance{
|
||||||
|
address: obj.address,
|
||||||
|
balance: obj.Balance(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if list != nil {
|
||||||
|
sort.Slice(list, func(i, j int) bool {
|
||||||
|
return list[i].address.Cmp(list[j].address) < 0
|
||||||
|
})
|
||||||
|
}
|
||||||
|
for _, acct := range list {
|
||||||
|
s.AddLog(types.EthBurnLog(acct.address, acct.balance))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Finalise finalises the state by removing the destructed objects and clears
|
// Finalise finalises the state by removing the destructed objects and clears
|
||||||
// the journal as well as the refunds. Finalise, however, will not push any updates
|
// the journal as well as the refunds. Finalise, however, will not push any updates
|
||||||
// into the tries just yet. Only IntermediateRoot or Commit will do that.
|
// into the tries just yet. Only IntermediateRoot or Commit will do that.
|
||||||
|
|
@ -824,32 +857,65 @@ func (s *StateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash {
|
||||||
workers errgroup.Group
|
workers errgroup.Group
|
||||||
)
|
)
|
||||||
if s.db.TrieDB().IsVerkle() {
|
if s.db.TrieDB().IsVerkle() {
|
||||||
// Whilst MPT storage tries are independent, Verkle has one single trie
|
// Bypass per-account updateTrie() for binary trie. In binary trie mode
|
||||||
// for all the accounts and all the storage slots merged together. The
|
// there is only one unified trie (OpenStorageTrie returns self), so the
|
||||||
// former can thus be simply parallelized, but updating the latter will
|
// per-account trie setup in updateTrie() (getPrefetchedTrie, getTrie,
|
||||||
// need concurrency support within the trie itself. That's a TODO for a
|
// prefetcher.used) is redundant overhead. Apply all storage updates
|
||||||
// later time.
|
// directly in a single pass.
|
||||||
workers.SetLimit(1)
|
for addr, op := range s.mutations {
|
||||||
}
|
if op.applied || op.isDelete() {
|
||||||
for addr, op := range s.mutations {
|
continue
|
||||||
if op.applied || op.isDelete() {
|
}
|
||||||
continue
|
obj := s.stateObjects[addr]
|
||||||
|
if len(obj.uncommittedStorage) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for key, origin := range obj.uncommittedStorage {
|
||||||
|
value, exist := obj.pendingStorage[key]
|
||||||
|
if value == origin || !exist {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (value != common.Hash{}) {
|
||||||
|
if err := s.trie.UpdateStorage(addr, key[:], common.TrimLeftZeroes(value[:])); err != nil {
|
||||||
|
s.setError(err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if err := s.trie.DeleteStorage(addr, key[:]); err != nil {
|
||||||
|
s.setError(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
obj := s.stateObjects[addr] // closure for the task runner below
|
// Clear uncommittedStorage and assign trie on each touched object.
|
||||||
workers.Go(func() error {
|
// obj.trie must be set because this path bypasses updateTrie(), which
|
||||||
if s.db.TrieDB().IsVerkle() {
|
// is where obj.trie normally gets lazily loaded via getTrie().
|
||||||
obj.updateTrie()
|
for addr, op := range s.mutations {
|
||||||
} else {
|
if op.applied || op.isDelete() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
obj := s.stateObjects[addr]
|
||||||
|
if len(obj.uncommittedStorage) > 0 {
|
||||||
|
obj.uncommittedStorage = make(Storage)
|
||||||
|
}
|
||||||
|
obj.trie = s.trie
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for addr, op := range s.mutations {
|
||||||
|
if op.applied || op.isDelete() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
obj := s.stateObjects[addr] // closure for the task runner below
|
||||||
|
workers.Go(func() error {
|
||||||
obj.updateRoot()
|
obj.updateRoot()
|
||||||
|
|
||||||
// If witness building is enabled and the state object has a trie,
|
// If witness building is enabled and the state object has a trie,
|
||||||
// gather the witnesses for its specific storage trie
|
// gather the witnesses for its specific storage trie
|
||||||
if s.witness != nil && obj.trie != nil {
|
if s.witness != nil && obj.trie != nil {
|
||||||
s.witness.AddState(obj.trie.Witness())
|
s.witness.AddState(obj.trie.Witness(), obj.addrHash())
|
||||||
}
|
}
|
||||||
}
|
return nil
|
||||||
return nil
|
})
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
// If witness building is enabled, gather all the read-only accesses.
|
// If witness building is enabled, gather all the read-only accesses.
|
||||||
// Skip witness collection in Verkle mode, they will be gathered
|
// Skip witness collection in Verkle mode, they will be gathered
|
||||||
|
|
@ -862,17 +928,9 @@ func (s *StateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if trie := obj.getPrefetchedTrie(); trie != nil {
|
if trie := obj.getPrefetchedTrie(); trie != nil {
|
||||||
witness := trie.Witness()
|
s.witness.AddState(trie.Witness(), obj.addrHash())
|
||||||
s.witness.AddState(witness)
|
|
||||||
if s.witnessStats != nil {
|
|
||||||
s.witnessStats.Add(witness, obj.addrHash())
|
|
||||||
}
|
|
||||||
} else if obj.trie != nil {
|
} else if obj.trie != nil {
|
||||||
witness := obj.trie.Witness()
|
s.witness.AddState(obj.trie.Witness(), obj.addrHash())
|
||||||
s.witness.AddState(witness)
|
|
||||||
if s.witnessStats != nil {
|
|
||||||
s.witnessStats.Add(witness, obj.addrHash())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Pull in only-read and non-destructed trie witnesses
|
// Pull in only-read and non-destructed trie witnesses
|
||||||
|
|
@ -886,17 +944,9 @@ func (s *StateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if trie := obj.getPrefetchedTrie(); trie != nil {
|
if trie := obj.getPrefetchedTrie(); trie != nil {
|
||||||
witness := trie.Witness()
|
s.witness.AddState(trie.Witness(), obj.addrHash())
|
||||||
s.witness.AddState(witness)
|
|
||||||
if s.witnessStats != nil {
|
|
||||||
s.witnessStats.Add(witness, obj.addrHash())
|
|
||||||
}
|
|
||||||
} else if obj.trie != nil {
|
} else if obj.trie != nil {
|
||||||
witness := obj.trie.Witness()
|
s.witness.AddState(obj.trie.Witness(), obj.addrHash())
|
||||||
s.witness.AddState(witness)
|
|
||||||
if s.witnessStats != nil {
|
|
||||||
s.witnessStats.Add(witness, obj.addrHash())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -911,7 +961,7 @@ func (s *StateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash {
|
||||||
// only a single trie is used for state hashing. Replacing a non-nil verkle tree
|
// only a single trie is used for state hashing. Replacing a non-nil verkle tree
|
||||||
// here could result in losing uncommitted changes from storage.
|
// here could result in losing uncommitted changes from storage.
|
||||||
start = time.Now()
|
start = time.Now()
|
||||||
if s.prefetcher != nil {
|
if s.prefetcher != nil && !s.db.TrieDB().IsVerkle() {
|
||||||
if trie := s.prefetcher.trie(common.Hash{}, s.originalRoot); trie == nil {
|
if trie := s.prefetcher.trie(common.Hash{}, s.originalRoot); trie == nil {
|
||||||
log.Error("Failed to retrieve account pre-fetcher trie")
|
log.Error("Failed to retrieve account pre-fetcher trie")
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -969,11 +1019,7 @@ func (s *StateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash {
|
||||||
|
|
||||||
// If witness building is enabled, gather the account trie witness
|
// If witness building is enabled, gather the account trie witness
|
||||||
if s.witness != nil {
|
if s.witness != nil {
|
||||||
witness := s.trie.Witness()
|
s.witness.AddState(s.trie.Witness(), common.Hash{})
|
||||||
s.witness.AddState(witness)
|
|
||||||
if s.witnessStats != nil {
|
|
||||||
s.witnessStats.Add(witness, common.Hash{})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return hash
|
return hash
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -229,6 +229,10 @@ func (s *hookedStateDB) AddLog(log *types.Log) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *hookedStateDB) EmitLogsForBurnAccounts() {
|
||||||
|
s.inner.EmitLogsForBurnAccounts()
|
||||||
|
}
|
||||||
|
|
||||||
func (s *hookedStateDB) Finalise(deleteEmptyObjects bool) {
|
func (s *hookedStateDB) Finalise(deleteEmptyObjects bool) {
|
||||||
if s.hooks.OnBalanceChange == nil && s.hooks.OnNonceChangeV2 == nil && s.hooks.OnNonceChange == nil && s.hooks.OnCodeChangeV2 == nil && s.hooks.OnCodeChange == nil {
|
if s.hooks.OnBalanceChange == nil && s.hooks.OnNonceChangeV2 == nil && s.hooks.OnNonceChange == nil && s.hooks.OnCodeChangeV2 == nil && s.hooks.OnCodeChange == nil {
|
||||||
// Short circuit if no relevant hooks are set.
|
// Short circuit if no relevant hooks are set.
|
||||||
|
|
|
||||||
|
|
@ -583,6 +583,9 @@ func (st *stateTransition) execute() (*ExecutionResult, error) {
|
||||||
st.evm.AccessEvents.AddAccount(st.evm.Context.Coinbase, true, math.MaxUint64)
|
st.evm.AccessEvents.AddAccount(st.evm.Context.Coinbase, true, math.MaxUint64)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if rules.IsAmsterdam {
|
||||||
|
st.evm.StateDB.EmitLogsForBurnAccounts()
|
||||||
|
}
|
||||||
return &ExecutionResult{
|
return &ExecutionResult{
|
||||||
UsedGas: st.gasUsed(),
|
UsedGas: st.gasUsed(),
|
||||||
MaxUsedGas: peakGasUsed,
|
MaxUsedGas: peakGasUsed,
|
||||||
|
|
|
||||||
|
|
@ -54,6 +54,13 @@ func NewWitnessStats() *WitnessStats {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *WitnessStats) copy() *WitnessStats {
|
||||||
|
return &WitnessStats{
|
||||||
|
accountTrie: s.accountTrie.Copy(),
|
||||||
|
storageTrie: s.storageTrie.Copy(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (s *WitnessStats) init() {
|
func (s *WitnessStats) init() {
|
||||||
if s.accountTrie == nil {
|
if s.accountTrie == nil {
|
||||||
s.accountTrie = trie.NewLevelStats()
|
s.accountTrie = trie.NewLevelStats()
|
||||||
|
|
|
||||||
|
|
@ -42,12 +42,13 @@ type Witness struct {
|
||||||
Codes map[string]struct{} // Set of bytecodes ran or accessed
|
Codes map[string]struct{} // Set of bytecodes ran or accessed
|
||||||
State map[string]struct{} // Set of MPT state trie nodes (account and storage together)
|
State map[string]struct{} // Set of MPT state trie nodes (account and storage together)
|
||||||
|
|
||||||
chain HeaderReader // Chain reader to convert block hash ops to header proofs
|
chain HeaderReader // Chain reader to convert block hash ops to header proofs
|
||||||
lock sync.Mutex // Lock to allow concurrent state insertions
|
stats *WitnessStats // Optional statistics collector
|
||||||
|
lock sync.Mutex // Lock to allow concurrent state insertions
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewWitness creates an empty witness ready for population.
|
// NewWitness creates an empty witness ready for population.
|
||||||
func NewWitness(context *types.Header, chain HeaderReader) (*Witness, error) {
|
func NewWitness(context *types.Header, chain HeaderReader, enableStats bool) (*Witness, error) {
|
||||||
// When building witnesses, retrieve the parent header, which will *always*
|
// When building witnesses, retrieve the parent header, which will *always*
|
||||||
// be included to act as a trustless pre-root hash container
|
// be included to act as a trustless pre-root hash container
|
||||||
var headers []*types.Header
|
var headers []*types.Header
|
||||||
|
|
@ -59,13 +60,17 @@ func NewWitness(context *types.Header, chain HeaderReader) (*Witness, error) {
|
||||||
headers = append(headers, parent)
|
headers = append(headers, parent)
|
||||||
}
|
}
|
||||||
// Create the witness with a reconstructed gutted out block
|
// Create the witness with a reconstructed gutted out block
|
||||||
return &Witness{
|
w := &Witness{
|
||||||
context: context,
|
context: context,
|
||||||
Headers: headers,
|
Headers: headers,
|
||||||
Codes: make(map[string]struct{}),
|
Codes: make(map[string]struct{}),
|
||||||
State: make(map[string]struct{}),
|
State: make(map[string]struct{}),
|
||||||
chain: chain,
|
chain: chain,
|
||||||
}, nil
|
}
|
||||||
|
if enableStats {
|
||||||
|
w.stats = NewWitnessStats()
|
||||||
|
}
|
||||||
|
return w, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddBlockHash adds a "blockhash" to the witness with the designated offset from
|
// AddBlockHash adds a "blockhash" to the witness with the designated offset from
|
||||||
|
|
@ -87,8 +92,11 @@ func (w *Witness) AddCode(code []byte) {
|
||||||
w.Codes[string(code)] = struct{}{}
|
w.Codes[string(code)] = struct{}{}
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddState inserts a batch of MPT trie nodes into the witness.
|
// AddState inserts a batch of MPT trie nodes into the witness. The owner
|
||||||
func (w *Witness) AddState(nodes map[string][]byte) {
|
// identifies which trie the nodes belong to: the zero hash for the account
|
||||||
|
// trie, or the hashed address for a storage trie. This is used for optional
|
||||||
|
// statistics collection.
|
||||||
|
func (w *Witness) AddState(nodes map[string][]byte, owner common.Hash) {
|
||||||
if len(nodes) == 0 {
|
if len(nodes) == 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
@ -98,6 +106,17 @@ func (w *Witness) AddState(nodes map[string][]byte) {
|
||||||
for _, value := range nodes {
|
for _, value := range nodes {
|
||||||
w.State[string(value)] = struct{}{}
|
w.State[string(value)] = struct{}{}
|
||||||
}
|
}
|
||||||
|
if w.stats != nil {
|
||||||
|
w.stats.Add(nodes, owner)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReportMetrics reports the collected statistics to the global metrics registry.
|
||||||
|
func (w *Witness) ReportMetrics(blockNumber uint64) {
|
||||||
|
if w.stats == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
w.stats.ReportMetrics(blockNumber)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *Witness) AddKey() {
|
func (w *Witness) AddKey() {
|
||||||
|
|
@ -113,6 +132,9 @@ func (w *Witness) Copy() *Witness {
|
||||||
State: maps.Clone(w.State),
|
State: maps.Clone(w.State),
|
||||||
chain: w.chain,
|
chain: w.chain,
|
||||||
}
|
}
|
||||||
|
if w.stats != nil {
|
||||||
|
cpy.stats = w.stats.copy()
|
||||||
|
}
|
||||||
if w.context != nil {
|
if w.context != nil {
|
||||||
cpy.context = types.CopyHeader(w.context)
|
cpy.context = types.CopyHeader(w.context)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -426,7 +426,7 @@ const (
|
||||||
// NonceChangeNewContract is the nonce change of a newly created contract.
|
// NonceChangeNewContract is the nonce change of a newly created contract.
|
||||||
NonceChangeNewContract NonceChangeReason = 4
|
NonceChangeNewContract NonceChangeReason = 4
|
||||||
|
|
||||||
// NonceChangeTransaction is the nonce change due to a EIP-7702 authorization.
|
// NonceChangeAuthorization is the nonce change due to a EIP-7702 authorization.
|
||||||
NonceChangeAuthorization NonceChangeReason = 5
|
NonceChangeAuthorization NonceChangeReason = 5
|
||||||
|
|
||||||
// NonceChangeRevert is emitted when the nonce is reverted back to a previous value due to call failure.
|
// NonceChangeRevert is emitted when the nonce is reverted back to a previous value due to call failure.
|
||||||
|
|
|
||||||
|
|
@ -998,11 +998,7 @@ func (pool *LegacyPool) Status(hash common.Hash) txpool.TxStatus {
|
||||||
|
|
||||||
// Get returns a transaction if it is contained in the pool and nil otherwise.
|
// Get returns a transaction if it is contained in the pool and nil otherwise.
|
||||||
func (pool *LegacyPool) Get(hash common.Hash) *types.Transaction {
|
func (pool *LegacyPool) Get(hash common.Hash) *types.Transaction {
|
||||||
tx := pool.get(hash)
|
return pool.get(hash)
|
||||||
if tx == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return tx
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// get returns a transaction if it is contained in the pool and nil otherwise.
|
// get returns a transaction if it is contained in the pool and nil otherwise.
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,8 @@ package types
|
||||||
import (
|
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/params"
|
||||||
|
"github.com/holiman/uint256"
|
||||||
)
|
)
|
||||||
|
|
||||||
//go:generate go run ../../rlp/rlpgen -type Log -out gen_log_rlp.go
|
//go:generate go run ../../rlp/rlpgen -type Log -out gen_log_rlp.go
|
||||||
|
|
@ -62,3 +64,32 @@ type logMarshaling struct {
|
||||||
BlockTimestamp hexutil.Uint64
|
BlockTimestamp hexutil.Uint64
|
||||||
Index hexutil.Uint
|
Index hexutil.Uint
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// EthTransferLog creates and ETH transfer log according to EIP-7708.
|
||||||
|
// Specification: https://eips.ethereum.org/EIPS/eip-7708
|
||||||
|
func EthTransferLog(from, to common.Address, amount *uint256.Int) *Log {
|
||||||
|
amount32 := amount.Bytes32()
|
||||||
|
return &Log{
|
||||||
|
Address: params.SystemAddress,
|
||||||
|
Topics: []common.Hash{
|
||||||
|
params.EthTransferLogEvent,
|
||||||
|
common.BytesToHash(from.Bytes()),
|
||||||
|
common.BytesToHash(to.Bytes()),
|
||||||
|
},
|
||||||
|
Data: amount32[:],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// EthBurnLog creates an ETH burn log according to EIP-7708.
|
||||||
|
// Specification: https://eips.ethereum.org/EIPS/eip-7708
|
||||||
|
func EthBurnLog(from common.Address, amount *uint256.Int) *Log {
|
||||||
|
amount32 := amount.Bytes32()
|
||||||
|
return &Log{
|
||||||
|
Address: params.SystemAddress,
|
||||||
|
Topics: []common.Hash{
|
||||||
|
params.EthBurnLogEvent,
|
||||||
|
common.BytesToHash(from.Bytes()),
|
||||||
|
},
|
||||||
|
Data: amount32[:],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -35,7 +35,7 @@ type (
|
||||||
// CanTransferFunc is the signature of a transfer guard function
|
// CanTransferFunc is the signature of a transfer guard function
|
||||||
CanTransferFunc func(StateDB, common.Address, *uint256.Int) bool
|
CanTransferFunc func(StateDB, common.Address, *uint256.Int) bool
|
||||||
// TransferFunc is the signature of a transfer function
|
// TransferFunc is the signature of a transfer function
|
||||||
TransferFunc func(StateDB, common.Address, common.Address, *uint256.Int)
|
TransferFunc func(StateDB, common.Address, common.Address, *uint256.Int, *params.Rules)
|
||||||
// GetHashFunc returns the n'th block hash in the blockchain
|
// GetHashFunc returns the n'th block hash in the blockchain
|
||||||
// and is used by the BLOCKHASH EVM op code.
|
// and is used by the BLOCKHASH EVM op code.
|
||||||
GetHashFunc func(uint64) common.Hash
|
GetHashFunc func(uint64) common.Hash
|
||||||
|
|
@ -283,8 +283,9 @@ func (evm *EVM) Call(caller common.Address, addr common.Address, input []byte, g
|
||||||
// Calling this is required even for zero-value transfers,
|
// Calling this is required even for zero-value transfers,
|
||||||
// to ensure the state clearing mechanism is applied.
|
// to ensure the state clearing mechanism is applied.
|
||||||
if !syscall {
|
if !syscall {
|
||||||
evm.Context.Transfer(evm.StateDB, caller, addr, value)
|
evm.Context.Transfer(evm.StateDB, caller, addr, value, &evm.chainRules)
|
||||||
}
|
}
|
||||||
|
|
||||||
if isPrecompile {
|
if isPrecompile {
|
||||||
var stateDB StateDB
|
var stateDB StateDB
|
||||||
if evm.chainRules.IsAmsterdam {
|
if evm.chainRules.IsAmsterdam {
|
||||||
|
|
@ -567,7 +568,7 @@ func (evm *EVM) create(caller common.Address, code []byte, gas uint64, value *ui
|
||||||
}
|
}
|
||||||
gas = gas - consumed
|
gas = gas - consumed
|
||||||
}
|
}
|
||||||
evm.Context.Transfer(evm.StateDB, caller, address, value)
|
evm.Context.Transfer(evm.StateDB, caller, address, value, &evm.chainRules)
|
||||||
|
|
||||||
// Initialise a new contract and set the code that is to be used by the EVM.
|
// Initialise a new contract and set the code that is to be used by the EVM.
|
||||||
// The contract is a scoped environment for this execution context only.
|
// The contract is a scoped environment for this execution context only.
|
||||||
|
|
|
||||||
|
|
@ -49,6 +49,5 @@ func callGas(isEip150 bool, availableGas, base uint64, callCost *uint256.Int) (u
|
||||||
if !callCost.IsUint64() {
|
if !callCost.IsUint64() {
|
||||||
return 0, ErrGasUintOverflow
|
return 0, ErrGasUintOverflow
|
||||||
}
|
}
|
||||||
|
|
||||||
return callCost.Uint64(), nil
|
return callCost.Uint64(), nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -373,7 +373,32 @@ func gasExpEIP158(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memor
|
||||||
return gas, nil
|
return gas, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func gasCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
|
var (
|
||||||
|
gasCall = makeCallVariantGasCost(gasCallIntrinsic)
|
||||||
|
gasCallCode = makeCallVariantGasCost(gasCallCodeIntrinsic)
|
||||||
|
gasDelegateCall = makeCallVariantGasCost(gasDelegateCallIntrinsic)
|
||||||
|
gasStaticCall = makeCallVariantGasCost(gasStaticCallIntrinsic)
|
||||||
|
)
|
||||||
|
|
||||||
|
func makeCallVariantGasCost(intrinsicFunc gasFunc) gasFunc {
|
||||||
|
return func(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
|
||||||
|
intrinsic, err := intrinsicFunc(evm, contract, stack, mem, memorySize)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
evm.callGasTemp, err = callGas(evm.chainRules.IsEIP150, contract.Gas, intrinsic, stack.Back(0))
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
gas, overflow := math.SafeAdd(intrinsic, evm.callGasTemp)
|
||||||
|
if overflow {
|
||||||
|
return 0, ErrGasUintOverflow
|
||||||
|
}
|
||||||
|
return gas, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func gasCallIntrinsic(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
|
||||||
var (
|
var (
|
||||||
gas uint64
|
gas uint64
|
||||||
transfersValue = !stack.Back(2).IsZero()
|
transfersValue = !stack.Back(2).IsZero()
|
||||||
|
|
@ -382,38 +407,40 @@ func gasCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize
|
||||||
if evm.readOnly && transfersValue {
|
if evm.readOnly && transfersValue {
|
||||||
return 0, ErrWriteProtection
|
return 0, ErrWriteProtection
|
||||||
}
|
}
|
||||||
|
// Stateless check
|
||||||
if evm.chainRules.IsEIP158 {
|
|
||||||
if transfersValue && evm.StateDB.Empty(address) {
|
|
||||||
gas += params.CallNewAccountGas
|
|
||||||
}
|
|
||||||
} else if !evm.StateDB.Exist(address) {
|
|
||||||
gas += params.CallNewAccountGas
|
|
||||||
}
|
|
||||||
if transfersValue && !evm.chainRules.IsEIP4762 {
|
|
||||||
gas += params.CallValueTransferGas
|
|
||||||
}
|
|
||||||
memoryGas, err := memoryGasCost(mem, memorySize)
|
memoryGas, err := memoryGasCost(mem, memorySize)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
|
var transferGas uint64
|
||||||
|
if transfersValue && !evm.chainRules.IsEIP4762 {
|
||||||
|
transferGas = params.CallValueTransferGas
|
||||||
|
}
|
||||||
var overflow bool
|
var overflow bool
|
||||||
if gas, overflow = math.SafeAdd(gas, memoryGas); overflow {
|
if gas, overflow = math.SafeAdd(memoryGas, transferGas); overflow {
|
||||||
return 0, ErrGasUintOverflow
|
return 0, ErrGasUintOverflow
|
||||||
}
|
}
|
||||||
|
// Terminate the gas measurement if the leftover gas is not sufficient,
|
||||||
evm.callGasTemp, err = callGas(evm.chainRules.IsEIP150, contract.Gas, gas, stack.Back(0))
|
// it can effectively prevent accessing the states in the following steps.
|
||||||
if err != nil {
|
if contract.Gas < gas {
|
||||||
return 0, err
|
return 0, ErrOutOfGas
|
||||||
}
|
}
|
||||||
if gas, overflow = math.SafeAdd(gas, evm.callGasTemp); overflow {
|
// Stateful check
|
||||||
|
var stateGas uint64
|
||||||
|
if evm.chainRules.IsEIP158 {
|
||||||
|
if transfersValue && evm.StateDB.Empty(address) {
|
||||||
|
stateGas += params.CallNewAccountGas
|
||||||
|
}
|
||||||
|
} else if !evm.StateDB.Exist(address) {
|
||||||
|
stateGas += params.CallNewAccountGas
|
||||||
|
}
|
||||||
|
if gas, overflow = math.SafeAdd(gas, stateGas); overflow {
|
||||||
return 0, ErrGasUintOverflow
|
return 0, ErrGasUintOverflow
|
||||||
}
|
}
|
||||||
|
|
||||||
return gas, nil
|
return gas, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func gasCallCode(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
|
func gasCallCodeIntrinsic(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
|
||||||
memoryGas, err := memoryGasCost(mem, memorySize)
|
memoryGas, err := memoryGasCost(mem, memorySize)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
|
|
@ -428,46 +455,15 @@ func gasCallCode(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memory
|
||||||
if gas, overflow = math.SafeAdd(gas, memoryGas); overflow {
|
if gas, overflow = math.SafeAdd(gas, memoryGas); overflow {
|
||||||
return 0, ErrGasUintOverflow
|
return 0, ErrGasUintOverflow
|
||||||
}
|
}
|
||||||
evm.callGasTemp, err = callGas(evm.chainRules.IsEIP150, contract.Gas, gas, stack.Back(0))
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
if gas, overflow = math.SafeAdd(gas, evm.callGasTemp); overflow {
|
|
||||||
return 0, ErrGasUintOverflow
|
|
||||||
}
|
|
||||||
return gas, nil
|
return gas, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func gasDelegateCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
|
func gasDelegateCallIntrinsic(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
|
||||||
gas, err := memoryGasCost(mem, memorySize)
|
return memoryGasCost(mem, memorySize)
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
evm.callGasTemp, err = callGas(evm.chainRules.IsEIP150, contract.Gas, gas, stack.Back(0))
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
var overflow bool
|
|
||||||
if gas, overflow = math.SafeAdd(gas, evm.callGasTemp); overflow {
|
|
||||||
return 0, ErrGasUintOverflow
|
|
||||||
}
|
|
||||||
return gas, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func gasStaticCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
|
func gasStaticCallIntrinsic(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
|
||||||
gas, err := memoryGasCost(mem, memorySize)
|
return memoryGasCost(mem, memorySize)
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
evm.callGasTemp, err = callGas(evm.chainRules.IsEIP150, contract.Gas, gas, stack.Back(0))
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
var overflow bool
|
|
||||||
if gas, overflow = math.SafeAdd(gas, evm.callGasTemp); overflow {
|
|
||||||
return 0, ErrGasUintOverflow
|
|
||||||
}
|
|
||||||
return gas, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func gasSelfdestruct(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
|
func gasSelfdestruct(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
|
||||||
|
|
|
||||||
|
|
@ -94,7 +94,7 @@ func TestEIP2200(t *testing.T) {
|
||||||
|
|
||||||
vmctx := BlockContext{
|
vmctx := BlockContext{
|
||||||
CanTransfer: func(StateDB, common.Address, *uint256.Int) bool { return true },
|
CanTransfer: func(StateDB, common.Address, *uint256.Int) bool { return true },
|
||||||
Transfer: func(StateDB, common.Address, common.Address, *uint256.Int) {},
|
Transfer: func(StateDB, common.Address, common.Address, *uint256.Int, *params.Rules) {},
|
||||||
}
|
}
|
||||||
evm := NewEVM(vmctx, statedb, params.AllEthashProtocolChanges, Config{ExtraEips: []int{2200}})
|
evm := NewEVM(vmctx, statedb, params.AllEthashProtocolChanges, Config{ExtraEips: []int{2200}})
|
||||||
|
|
||||||
|
|
@ -144,7 +144,7 @@ func TestCreateGas(t *testing.T) {
|
||||||
statedb.Finalise(true)
|
statedb.Finalise(true)
|
||||||
vmctx := BlockContext{
|
vmctx := BlockContext{
|
||||||
CanTransfer: func(StateDB, common.Address, *uint256.Int) bool { return true },
|
CanTransfer: func(StateDB, common.Address, *uint256.Int) bool { return true },
|
||||||
Transfer: func(StateDB, common.Address, common.Address, *uint256.Int) {},
|
Transfer: func(StateDB, common.Address, common.Address, *uint256.Int, *params.Rules) {},
|
||||||
BlockNumber: big.NewInt(0),
|
BlockNumber: big.NewInt(0),
|
||||||
}
|
}
|
||||||
config := Config{}
|
config := Config{}
|
||||||
|
|
|
||||||
|
|
@ -934,6 +934,13 @@ func opSelfdestruct6780(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, erro
|
||||||
evm.StateDB.SubBalance(this, balance, tracing.BalanceDecreaseSelfdestruct)
|
evm.StateDB.SubBalance(this, balance, tracing.BalanceDecreaseSelfdestruct)
|
||||||
evm.StateDB.AddBalance(beneficiary, balance, tracing.BalanceIncreaseSelfdestruct)
|
evm.StateDB.AddBalance(beneficiary, balance, tracing.BalanceIncreaseSelfdestruct)
|
||||||
}
|
}
|
||||||
|
if evm.chainRules.IsAmsterdam && !balance.IsZero() {
|
||||||
|
if this != beneficiary {
|
||||||
|
evm.StateDB.AddLog(types.EthTransferLog(this, beneficiary, balance))
|
||||||
|
} else if newContract {
|
||||||
|
evm.StateDB.AddLog(types.EthBurnLog(this, balance))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if tracer := evm.Config.Tracer; tracer != nil {
|
if tracer := evm.Config.Tracer; tracer != nil {
|
||||||
if tracer.OnEnter != nil {
|
if tracer.OnEnter != nil {
|
||||||
|
|
@ -1086,9 +1093,6 @@ func makeLog(size int) executionFunc {
|
||||||
Address: scope.Contract.Address(),
|
Address: scope.Contract.Address(),
|
||||||
Topics: topics,
|
Topics: topics,
|
||||||
Data: d,
|
Data: d,
|
||||||
// This is a non-consensus field, but assigned here because
|
|
||||||
// core/state doesn't know the current block number.
|
|
||||||
BlockNumber: evm.Context.BlockNumber.Uint64(),
|
|
||||||
})
|
})
|
||||||
|
|
||||||
return nil, nil
|
return nil, nil
|
||||||
|
|
|
||||||
|
|
@ -87,6 +87,7 @@ type StateDB interface {
|
||||||
Snapshot() int
|
Snapshot() int
|
||||||
|
|
||||||
AddLog(*types.Log)
|
AddLog(*types.Log)
|
||||||
|
EmitLogsForBurnAccounts()
|
||||||
AddPreimage(common.Hash, []byte)
|
AddPreimage(common.Hash, []byte)
|
||||||
|
|
||||||
Witness() *stateless.Witness
|
Witness() *stateless.Witness
|
||||||
|
|
|
||||||
|
|
@ -40,7 +40,7 @@ var loopInterruptTests = []string{
|
||||||
func TestLoopInterrupt(t *testing.T) {
|
func TestLoopInterrupt(t *testing.T) {
|
||||||
address := common.BytesToAddress([]byte("contract"))
|
address := common.BytesToAddress([]byte("contract"))
|
||||||
vmctx := BlockContext{
|
vmctx := BlockContext{
|
||||||
Transfer: func(StateDB, common.Address, common.Address, *uint256.Int) {},
|
Transfer: func(StateDB, common.Address, common.Address, *uint256.Int, *params.Rules) {},
|
||||||
}
|
}
|
||||||
|
|
||||||
for i, tt := range loopInterruptTests {
|
for i, tt := range loopInterruptTests {
|
||||||
|
|
|
||||||
|
|
@ -256,10 +256,10 @@ func makeSelfdestructGasFn(refundsEnabled bool) gasFunc {
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
innerGasCallEIP7702 = makeCallVariantGasCallEIP7702(gasCall)
|
innerGasCallEIP7702 = makeCallVariantGasCallEIP7702(gasCallIntrinsic)
|
||||||
gasDelegateCallEIP7702 = makeCallVariantGasCallEIP7702(gasDelegateCall)
|
gasDelegateCallEIP7702 = makeCallVariantGasCallEIP7702(gasDelegateCallIntrinsic)
|
||||||
gasStaticCallEIP7702 = makeCallVariantGasCallEIP7702(gasStaticCall)
|
gasStaticCallEIP7702 = makeCallVariantGasCallEIP7702(gasStaticCallIntrinsic)
|
||||||
gasCallCodeEIP7702 = makeCallVariantGasCallEIP7702(gasCallCode)
|
gasCallCodeEIP7702 = makeCallVariantGasCallEIP7702(gasCallCodeIntrinsic)
|
||||||
)
|
)
|
||||||
|
|
||||||
func gasCallEIP7702(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
|
func gasCallEIP7702(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
|
||||||
|
|
@ -274,62 +274,85 @@ func gasCallEIP7702(evm *EVM, contract *Contract, stack *Stack, mem *Memory, mem
|
||||||
return innerGasCallEIP7702(evm, contract, stack, mem, memorySize)
|
return innerGasCallEIP7702(evm, contract, stack, mem, memorySize)
|
||||||
}
|
}
|
||||||
|
|
||||||
func makeCallVariantGasCallEIP7702(oldCalculator gasFunc) gasFunc {
|
func makeCallVariantGasCallEIP7702(intrinsicFunc gasFunc) gasFunc {
|
||||||
return func(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
|
return func(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
|
||||||
var (
|
var (
|
||||||
total uint64 // total dynamic gas used
|
eip2929Cost uint64
|
||||||
addr = common.Address(stack.Back(1).Bytes20())
|
eip7702Cost uint64
|
||||||
|
addr = common.Address(stack.Back(1).Bytes20())
|
||||||
)
|
)
|
||||||
|
// Perform EIP-2929 checks (stateless), checking address presence
|
||||||
// Check slot presence in the access list
|
// in the accessList and charge the cold access accordingly.
|
||||||
if !evm.StateDB.AddressInAccessList(addr) {
|
if !evm.StateDB.AddressInAccessList(addr) {
|
||||||
evm.StateDB.AddAddressToAccessList(addr)
|
evm.StateDB.AddAddressToAccessList(addr)
|
||||||
// The WarmStorageReadCostEIP2929 (100) is already deducted in the form of a constant cost, so
|
|
||||||
// the cost to charge for cold access, if any, is Cold - Warm
|
// The WarmStorageReadCostEIP2929 (100) is already deducted in the form
|
||||||
coldCost := params.ColdAccountAccessCostEIP2929 - params.WarmStorageReadCostEIP2929
|
// of a constant cost, so the cost to charge for cold access, if any,
|
||||||
// Charge the remaining difference here already, to correctly calculate available
|
// is Cold - Warm
|
||||||
// gas for call
|
eip2929Cost = params.ColdAccountAccessCostEIP2929 - params.WarmStorageReadCostEIP2929
|
||||||
if !contract.UseGas(coldCost, evm.Config.Tracer, tracing.GasChangeCallStorageColdAccess) {
|
|
||||||
|
// Charge the remaining difference here already, to correctly calculate
|
||||||
|
// available gas for call
|
||||||
|
if !contract.UseGas(eip2929Cost, evm.Config.Tracer, tracing.GasChangeCallStorageColdAccess) {
|
||||||
return 0, ErrOutOfGas
|
return 0, ErrOutOfGas
|
||||||
}
|
}
|
||||||
total += coldCost
|
}
|
||||||
|
|
||||||
|
// Perform the intrinsic cost calculation including:
|
||||||
|
//
|
||||||
|
// - transfer value
|
||||||
|
// - memory expansion
|
||||||
|
// - create new account
|
||||||
|
intrinsicCost, err := intrinsicFunc(evm, contract, stack, mem, memorySize)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
// Terminate the gas measurement if the leftover gas is not sufficient,
|
||||||
|
// it can effectively prevent accessing the states in the following steps.
|
||||||
|
// It's an essential safeguard before any stateful check.
|
||||||
|
if contract.Gas < intrinsicCost {
|
||||||
|
return 0, ErrOutOfGas
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if code is a delegation and if so, charge for resolution.
|
// Check if code is a delegation and if so, charge for resolution.
|
||||||
if target, ok := types.ParseDelegation(evm.StateDB.GetCode(addr)); ok {
|
if target, ok := types.ParseDelegation(evm.StateDB.GetCode(addr)); ok {
|
||||||
var cost uint64
|
|
||||||
if evm.StateDB.AddressInAccessList(target) {
|
if evm.StateDB.AddressInAccessList(target) {
|
||||||
cost = params.WarmStorageReadCostEIP2929
|
eip7702Cost = params.WarmStorageReadCostEIP2929
|
||||||
} else {
|
} else {
|
||||||
evm.StateDB.AddAddressToAccessList(target)
|
evm.StateDB.AddAddressToAccessList(target)
|
||||||
cost = params.ColdAccountAccessCostEIP2929
|
eip7702Cost = params.ColdAccountAccessCostEIP2929
|
||||||
}
|
}
|
||||||
if !contract.UseGas(cost, evm.Config.Tracer, tracing.GasChangeCallStorageColdAccess) {
|
if !contract.UseGas(eip7702Cost, evm.Config.Tracer, tracing.GasChangeCallStorageColdAccess) {
|
||||||
return 0, ErrOutOfGas
|
return 0, ErrOutOfGas
|
||||||
}
|
}
|
||||||
total += cost
|
|
||||||
}
|
}
|
||||||
|
// Calculate the gas budget for the nested call. The costs defined by
|
||||||
// Now call the old calculator, which takes into account
|
// EIP-2929 and EIP-7702 have already been applied.
|
||||||
// - create new account
|
evm.callGasTemp, err = callGas(evm.chainRules.IsEIP150, contract.Gas, intrinsicCost, stack.Back(0))
|
||||||
// - transfer value
|
|
||||||
// - memory expansion
|
|
||||||
// - 63/64ths rule
|
|
||||||
old, err := oldCalculator(evm, contract, stack, mem, memorySize)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return old, err
|
return 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Temporarily add the gas charge back to the contract and return value. By
|
// Temporarily add the gas charge back to the contract and return value. By
|
||||||
// adding it to the return, it will be charged outside of this function, as
|
// adding it to the return, it will be charged outside of this function, as
|
||||||
// part of the dynamic gas. This will ensure it is correctly reported to
|
// part of the dynamic gas. This will ensure it is correctly reported to
|
||||||
// tracers.
|
// tracers.
|
||||||
contract.Gas += total
|
contract.Gas += eip2929Cost + eip7702Cost
|
||||||
|
|
||||||
var overflow bool
|
// Aggregate the gas costs from all components, including EIP-2929, EIP-7702,
|
||||||
if total, overflow = math.SafeAdd(old, total); overflow {
|
// the CALL opcode itself, and the cost incurred by nested calls.
|
||||||
|
var (
|
||||||
|
overflow bool
|
||||||
|
totalCost uint64
|
||||||
|
)
|
||||||
|
if totalCost, overflow = math.SafeAdd(eip2929Cost, eip7702Cost); overflow {
|
||||||
return 0, ErrGasUintOverflow
|
return 0, ErrGasUintOverflow
|
||||||
}
|
}
|
||||||
return total, nil
|
if totalCost, overflow = math.SafeAdd(totalCost, intrinsicCost); overflow {
|
||||||
|
return 0, ErrGasUintOverflow
|
||||||
|
}
|
||||||
|
if totalCost, overflow = math.SafeAdd(totalCost, evm.callGasTemp); overflow {
|
||||||
|
return 0, ErrGasUintOverflow
|
||||||
|
}
|
||||||
|
return totalCost, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,7 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/consensus"
|
"github.com/ethereum/go-ethereum/consensus"
|
||||||
"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/pruner"
|
"github.com/ethereum/go-ethereum/core/state/pruner"
|
||||||
"github.com/ethereum/go-ethereum/core/txpool"
|
"github.com/ethereum/go-ethereum/core/txpool"
|
||||||
|
|
@ -175,7 +176,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, _, err := core.LoadChainConfig(chainDb, config.Genesis)
|
chainConfig, genesisHash, err := core.LoadChainConfig(chainDb, config.Genesis)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -220,6 +221,10 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) {
|
||||||
rawdb.WriteDatabaseVersion(chainDb, core.BlockChainVersion)
|
rawdb.WriteDatabaseVersion(chainDb, core.BlockChainVersion)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
histPolicy, err := history.NewPolicy(config.HistoryMode, genesisHash)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
var (
|
var (
|
||||||
options = &core.BlockChainConfig{
|
options = &core.BlockChainConfig{
|
||||||
TrieCleanLimit: config.TrieCleanCache,
|
TrieCleanLimit: config.TrieCleanCache,
|
||||||
|
|
@ -233,7 +238,7 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) {
|
||||||
TrienodeHistory: config.TrienodeHistory,
|
TrienodeHistory: config.TrienodeHistory,
|
||||||
NodeFullValueCheckpoint: config.NodeFullValueCheckpoint,
|
NodeFullValueCheckpoint: config.NodeFullValueCheckpoint,
|
||||||
StateScheme: scheme,
|
StateScheme: scheme,
|
||||||
ChainHistoryMode: config.HistoryMode,
|
HistoryPolicy: histPolicy,
|
||||||
TxLookupLimit: int64(min(config.TransactionHistory, math.MaxInt64)),
|
TxLookupLimit: int64(min(config.TransactionHistory, math.MaxInt64)),
|
||||||
VmConfig: vm.Config{
|
VmConfig: vm.Config{
|
||||||
EnablePreimageRecording: config.EnablePreimageRecording,
|
EnablePreimageRecording: config.EnablePreimageRecording,
|
||||||
|
|
|
||||||
|
|
@ -390,7 +390,7 @@ func (f *Filter) rangeLogs(ctx context.Context, firstBlock, lastBlock uint64) ([
|
||||||
}
|
}
|
||||||
|
|
||||||
if firstBlock > lastBlock {
|
if firstBlock > lastBlock {
|
||||||
return nil, nil
|
return nil, errInvalidBlockRange
|
||||||
}
|
}
|
||||||
mb := f.sys.backend.NewMatcherBackend()
|
mb := f.sys.backend.NewMatcherBackend()
|
||||||
defer mb.Close()
|
defer mb.Close()
|
||||||
|
|
|
||||||
|
|
@ -357,7 +357,8 @@ func testFilters(t *testing.T, history uint64, noHistory bool) {
|
||||||
want: `[{"address":"0xff00000000000000000000000000000000000000","topics":["0x0000000000000000000000000000000000000000000000000000746f70696333"],"data":"0x","blockNumber":"0x3e7","transactionHash":"0x53e3675800c6908424b61b35a44e51ca4c73ca603e58a65b32c67968b4f42200","transactionIndex":"0x0","blockHash":"0x2e4620a2b426b0612ec6cad9603f466723edaed87f98c9137405dd4f7a2409ff","blockTimestamp":"0x2706","logIndex":"0x0","removed":false}]`,
|
want: `[{"address":"0xff00000000000000000000000000000000000000","topics":["0x0000000000000000000000000000000000000000000000000000746f70696333"],"data":"0x","blockNumber":"0x3e7","transactionHash":"0x53e3675800c6908424b61b35a44e51ca4c73ca603e58a65b32c67968b4f42200","transactionIndex":"0x0","blockHash":"0x2e4620a2b426b0612ec6cad9603f466723edaed87f98c9137405dd4f7a2409ff","blockTimestamp":"0x2706","logIndex":"0x0","removed":false}]`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
f: sys.NewRangeFilter(int64(rpc.LatestBlockNumber), int64(rpc.FinalizedBlockNumber), nil, nil, 0),
|
f: sys.NewRangeFilter(int64(rpc.LatestBlockNumber), int64(rpc.FinalizedBlockNumber), nil, nil, 0),
|
||||||
|
err: errInvalidBlockRange.Error(),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
f: sys.NewRangeFilter(int64(rpc.SafeBlockNumber), int64(rpc.LatestBlockNumber), nil, nil, 0),
|
f: sys.NewRangeFilter(int64(rpc.SafeBlockNumber), int64(rpc.LatestBlockNumber), nil, nil, 0),
|
||||||
|
|
|
||||||
|
|
@ -141,7 +141,7 @@ func txValidationError(err error) *invalidTxError {
|
||||||
return &invalidTxError{Message: err.Error(), Code: errCodeIntrinsicGas}
|
return &invalidTxError{Message: err.Error(), Code: errCodeIntrinsicGas}
|
||||||
case errors.Is(err, core.ErrInsufficientFundsForTransfer):
|
case errors.Is(err, core.ErrInsufficientFundsForTransfer):
|
||||||
return &invalidTxError{Message: err.Error(), Code: errCodeInsufficientFunds}
|
return &invalidTxError{Message: err.Error(), Code: errCodeInsufficientFunds}
|
||||||
case errors.Is(err, core.ErrMaxInitCodeSizeExceeded):
|
case errors.Is(err, vm.ErrMaxInitCodeSizeExceeded):
|
||||||
return &invalidTxError{Message: err.Error(), Code: errCodeMaxInitCodeSizeExceeded}
|
return &invalidTxError{Message: err.Error(), Code: errCodeMaxInitCodeSizeExceeded}
|
||||||
}
|
}
|
||||||
return &invalidTxError{
|
return &invalidTxError{
|
||||||
|
|
|
||||||
|
|
@ -350,6 +350,7 @@ func (miner *Miner) BuildTestingPayload(args *BuildPayloadArgs, transactions []*
|
||||||
random: args.Random,
|
random: args.Random,
|
||||||
withdrawals: args.Withdrawals,
|
withdrawals: args.Withdrawals,
|
||||||
beaconRoot: args.BeaconRoot,
|
beaconRoot: args.BeaconRoot,
|
||||||
|
slotNum: args.SlotNum,
|
||||||
noTxs: empty,
|
noTxs: empty,
|
||||||
forceOverrides: true,
|
forceOverrides: true,
|
||||||
overrideExtraData: extraData,
|
overrideExtraData: extraData,
|
||||||
|
|
|
||||||
|
|
@ -75,7 +75,7 @@ type environment struct {
|
||||||
witness *stateless.Witness
|
witness *stateless.Witness
|
||||||
}
|
}
|
||||||
|
|
||||||
// txFits reports whether the transaction fits into the block size limit.
|
// txFitsSize reports whether the transaction fits into the block size limit.
|
||||||
func (env *environment) txFitsSize(tx *types.Transaction) bool {
|
func (env *environment) txFitsSize(tx *types.Transaction) bool {
|
||||||
return env.size+tx.Size() < params.MaxBlockSize-maxBlockSizeBufferZone
|
return env.size+tx.Size() < params.MaxBlockSize-maxBlockSizeBufferZone
|
||||||
}
|
}
|
||||||
|
|
@ -330,12 +330,12 @@ func (miner *Miner) makeEnv(parent *types.Header, header *types.Header, coinbase
|
||||||
}
|
}
|
||||||
var bundle *stateless.Witness
|
var bundle *stateless.Witness
|
||||||
if witness {
|
if witness {
|
||||||
bundle, err = stateless.NewWitness(header, miner.chain)
|
bundle, err = stateless.NewWitness(header, miner.chain, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
state.StartPrefetcher("miner", bundle, nil)
|
state.StartPrefetcher("miner", bundle)
|
||||||
// Note the passed coinbase may be different with header.Coinbase.
|
// Note the passed coinbase may be different with header.Coinbase.
|
||||||
return &environment{
|
return &environment{
|
||||||
signer: types.MakeSigner(miner.chainConfig, header.Number, header.Time),
|
signer: types.MakeSigner(miner.chainConfig, header.Number, header.Time),
|
||||||
|
|
|
||||||
|
|
@ -35,7 +35,7 @@ function coverbuild {
|
||||||
sed -i -e 's/TestFuzzCorpus/Test'$function'Corpus/' ./"${function,,}"_test.go
|
sed -i -e 's/TestFuzzCorpus/Test'$function'Corpus/' ./"${function,,}"_test.go
|
||||||
|
|
||||||
cat << DOG > $OUT/$fuzzer
|
cat << DOG > $OUT/$fuzzer
|
||||||
#/bin/sh
|
#!/bin/sh
|
||||||
|
|
||||||
cd $OUT/$path
|
cd $OUT/$path
|
||||||
go test -run Test${function}Corpus -v $tags -coverprofile \$1 -coverpkg $coverpkg
|
go test -run Test${function}Corpus -v $tags -coverprofile \$1 -coverpkg $coverpkg
|
||||||
|
|
|
||||||
67
p2p/dial.go
67
p2p/dial.go
|
|
@ -76,6 +76,7 @@ var (
|
||||||
errSelf = errors.New("is self")
|
errSelf = errors.New("is self")
|
||||||
errAlreadyDialing = errors.New("already dialing")
|
errAlreadyDialing = errors.New("already dialing")
|
||||||
errAlreadyConnected = errors.New("already connected")
|
errAlreadyConnected = errors.New("already connected")
|
||||||
|
errPendingInbound = errors.New("peer has pending inbound connection")
|
||||||
errRecentlyDialed = errors.New("recently dialed")
|
errRecentlyDialed = errors.New("recently dialed")
|
||||||
errNetRestrict = errors.New("not contained in netrestrict list")
|
errNetRestrict = errors.New("not contained in netrestrict list")
|
||||||
errNoPort = errors.New("node does not provide TCP port")
|
errNoPort = errors.New("node does not provide TCP port")
|
||||||
|
|
@ -104,12 +105,15 @@ type dialScheduler struct {
|
||||||
remStaticCh chan *enode.Node
|
remStaticCh chan *enode.Node
|
||||||
addPeerCh chan *conn
|
addPeerCh chan *conn
|
||||||
remPeerCh chan *conn
|
remPeerCh chan *conn
|
||||||
|
addPendingCh chan enode.ID
|
||||||
|
remPendingCh chan enode.ID
|
||||||
|
|
||||||
// Everything below here belongs to loop and
|
// Everything below here belongs to loop and
|
||||||
// should only be accessed by code on the loop goroutine.
|
// should only be accessed by code on the loop goroutine.
|
||||||
dialing map[enode.ID]*dialTask // active tasks
|
dialing map[enode.ID]*dialTask // active tasks
|
||||||
peers map[enode.ID]struct{} // all connected peers
|
peers map[enode.ID]struct{} // all connected peers
|
||||||
dialPeers int // current number of dialed peers
|
pendingInbound map[enode.ID]struct{} // in-progress inbound connections
|
||||||
|
dialPeers int // current number of dialed peers
|
||||||
|
|
||||||
// The static map tracks all static dial tasks. The subset of usable static dial tasks
|
// The static map tracks all static dial tasks. The subset of usable static dial tasks
|
||||||
// (i.e. those passing checkDial) is kept in staticPool. The scheduler prefers
|
// (i.e. those passing checkDial) is kept in staticPool. The scheduler prefers
|
||||||
|
|
@ -163,19 +167,22 @@ func (cfg dialConfig) withDefaults() dialConfig {
|
||||||
func newDialScheduler(config dialConfig, it enode.Iterator, setupFunc dialSetupFunc) *dialScheduler {
|
func newDialScheduler(config dialConfig, it enode.Iterator, setupFunc dialSetupFunc) *dialScheduler {
|
||||||
cfg := config.withDefaults()
|
cfg := config.withDefaults()
|
||||||
d := &dialScheduler{
|
d := &dialScheduler{
|
||||||
dialConfig: cfg,
|
dialConfig: cfg,
|
||||||
historyTimer: mclock.NewAlarm(cfg.clock),
|
historyTimer: mclock.NewAlarm(cfg.clock),
|
||||||
setupFunc: setupFunc,
|
setupFunc: setupFunc,
|
||||||
dnsLookupFunc: net.DefaultResolver.LookupNetIP,
|
dnsLookupFunc: net.DefaultResolver.LookupNetIP,
|
||||||
dialing: make(map[enode.ID]*dialTask),
|
dialing: make(map[enode.ID]*dialTask),
|
||||||
static: make(map[enode.ID]*dialTask),
|
static: make(map[enode.ID]*dialTask),
|
||||||
peers: make(map[enode.ID]struct{}),
|
peers: make(map[enode.ID]struct{}),
|
||||||
doneCh: make(chan *dialTask),
|
pendingInbound: make(map[enode.ID]struct{}),
|
||||||
nodesIn: make(chan *enode.Node),
|
doneCh: make(chan *dialTask),
|
||||||
addStaticCh: make(chan *enode.Node),
|
nodesIn: make(chan *enode.Node),
|
||||||
remStaticCh: make(chan *enode.Node),
|
addStaticCh: make(chan *enode.Node),
|
||||||
addPeerCh: make(chan *conn),
|
remStaticCh: make(chan *enode.Node),
|
||||||
remPeerCh: make(chan *conn),
|
addPeerCh: make(chan *conn),
|
||||||
|
remPeerCh: make(chan *conn),
|
||||||
|
addPendingCh: make(chan enode.ID),
|
||||||
|
remPendingCh: make(chan enode.ID),
|
||||||
}
|
}
|
||||||
d.lastStatsLog = d.clock.Now()
|
d.lastStatsLog = d.clock.Now()
|
||||||
d.ctx, d.cancel = context.WithCancel(context.Background())
|
d.ctx, d.cancel = context.WithCancel(context.Background())
|
||||||
|
|
@ -223,6 +230,22 @@ func (d *dialScheduler) peerRemoved(c *conn) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// inboundPending notifies the scheduler about a pending inbound connection.
|
||||||
|
func (d *dialScheduler) inboundPending(id enode.ID) {
|
||||||
|
select {
|
||||||
|
case d.addPendingCh <- id:
|
||||||
|
case <-d.ctx.Done():
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// inboundCompleted notifies the scheduler that an inbound connection completed or failed.
|
||||||
|
func (d *dialScheduler) inboundCompleted(id enode.ID) {
|
||||||
|
select {
|
||||||
|
case d.remPendingCh <- id:
|
||||||
|
case <-d.ctx.Done():
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// loop is the main loop of the dialer.
|
// loop is the main loop of the dialer.
|
||||||
func (d *dialScheduler) loop(it enode.Iterator) {
|
func (d *dialScheduler) loop(it enode.Iterator) {
|
||||||
var (
|
var (
|
||||||
|
|
@ -276,6 +299,15 @@ loop:
|
||||||
delete(d.peers, c.node.ID())
|
delete(d.peers, c.node.ID())
|
||||||
d.updateStaticPool(c.node.ID())
|
d.updateStaticPool(c.node.ID())
|
||||||
|
|
||||||
|
case id := <-d.addPendingCh:
|
||||||
|
d.pendingInbound[id] = struct{}{}
|
||||||
|
d.log.Trace("Marked node as pending inbound", "id", id)
|
||||||
|
|
||||||
|
case id := <-d.remPendingCh:
|
||||||
|
delete(d.pendingInbound, id)
|
||||||
|
d.updateStaticPool(id)
|
||||||
|
d.log.Trace("Unmarked node as pending inbound", "id", id)
|
||||||
|
|
||||||
case node := <-d.addStaticCh:
|
case node := <-d.addStaticCh:
|
||||||
id := node.ID()
|
id := node.ID()
|
||||||
_, exists := d.static[id]
|
_, exists := d.static[id]
|
||||||
|
|
@ -390,6 +422,9 @@ func (d *dialScheduler) checkDial(n *enode.Node) error {
|
||||||
if _, ok := d.peers[n.ID()]; ok {
|
if _, ok := d.peers[n.ID()]; ok {
|
||||||
return errAlreadyConnected
|
return errAlreadyConnected
|
||||||
}
|
}
|
||||||
|
if _, ok := d.pendingInbound[n.ID()]; ok {
|
||||||
|
return errPendingInbound
|
||||||
|
}
|
||||||
if d.netRestrict != nil && !d.netRestrict.ContainsAddr(n.IPAddr()) {
|
if d.netRestrict != nil && !d.netRestrict.ContainsAddr(n.IPAddr()) {
|
||||||
return errNetRestrict
|
return errNetRestrict
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -423,6 +423,82 @@ func TestDialSchedDNSHostname(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This test checks that nodes with pending inbound connections are not dialed.
|
||||||
|
func TestDialSchedPendingInbound(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
config := dialConfig{
|
||||||
|
maxActiveDials: 5,
|
||||||
|
maxDialPeers: 4,
|
||||||
|
}
|
||||||
|
runDialTest(t, config, []dialTestRound{
|
||||||
|
// 2 peers are connected, leaving 2 dial slots.
|
||||||
|
// Node 0x03 has a pending inbound connection.
|
||||||
|
// Discovered nodes 0x03, 0x04, 0x05 but only 0x04 and 0x05 should be dialed.
|
||||||
|
{
|
||||||
|
peersAdded: []*conn{
|
||||||
|
{flags: dynDialedConn, node: newNode(uintID(0x01), "127.0.0.1:30303")},
|
||||||
|
{flags: dynDialedConn, node: newNode(uintID(0x02), "127.0.0.2:30303")},
|
||||||
|
},
|
||||||
|
update: func(d *dialScheduler) {
|
||||||
|
d.inboundPending(uintID(0x03))
|
||||||
|
},
|
||||||
|
discovered: []*enode.Node{
|
||||||
|
newNode(uintID(0x03), "127.0.0.3:30303"), // not dialed because pending inbound
|
||||||
|
newNode(uintID(0x04), "127.0.0.4:30303"),
|
||||||
|
newNode(uintID(0x05), "127.0.0.5:30303"),
|
||||||
|
},
|
||||||
|
wantNewDials: []*enode.Node{
|
||||||
|
newNode(uintID(0x04), "127.0.0.4:30303"),
|
||||||
|
newNode(uintID(0x05), "127.0.0.5:30303"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// Pending inbound connection for 0x03 completes successfully.
|
||||||
|
// Node 0x03 becomes a connected peer.
|
||||||
|
// One dial slot remains, node 0x06 is dialed.
|
||||||
|
{
|
||||||
|
update: func(d *dialScheduler) {
|
||||||
|
// Pending inbound completes
|
||||||
|
d.inboundCompleted(uintID(0x03))
|
||||||
|
},
|
||||||
|
peersAdded: []*conn{
|
||||||
|
{flags: inboundConn, node: newNode(uintID(0x03), "127.0.0.3:30303")},
|
||||||
|
},
|
||||||
|
succeeded: []enode.ID{
|
||||||
|
uintID(0x04),
|
||||||
|
},
|
||||||
|
failed: []enode.ID{
|
||||||
|
uintID(0x05),
|
||||||
|
},
|
||||||
|
discovered: []*enode.Node{
|
||||||
|
newNode(uintID(0x03), "127.0.0.3:30303"), // not dialed, now connected
|
||||||
|
newNode(uintID(0x06), "127.0.0.6:30303"),
|
||||||
|
},
|
||||||
|
wantNewDials: []*enode.Node{
|
||||||
|
newNode(uintID(0x06), "127.0.0.6:30303"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// Inbound peer 0x03 disconnects.
|
||||||
|
// Another pending inbound starts for 0x07.
|
||||||
|
// Only 0x03 should be dialed, not 0x07.
|
||||||
|
{
|
||||||
|
peersRemoved: []enode.ID{
|
||||||
|
uintID(0x03),
|
||||||
|
},
|
||||||
|
update: func(d *dialScheduler) {
|
||||||
|
d.inboundPending(uintID(0x07))
|
||||||
|
},
|
||||||
|
discovered: []*enode.Node{
|
||||||
|
newNode(uintID(0x03), "127.0.0.3:30303"),
|
||||||
|
newNode(uintID(0x07), "127.0.0.7:30303"), // not dialed because pending inbound
|
||||||
|
},
|
||||||
|
wantNewDials: []*enode.Node{
|
||||||
|
newNode(uintID(0x03), "127.0.0.3:30303"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// -------
|
// -------
|
||||||
// Code below here is the framework for the tests above.
|
// Code below here is the framework for the tests above.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -686,8 +686,11 @@ running:
|
||||||
// Ensure that the trusted flag is set before checking against MaxPeers.
|
// Ensure that the trusted flag is set before checking against MaxPeers.
|
||||||
c.flags |= trustedConn
|
c.flags |= trustedConn
|
||||||
}
|
}
|
||||||
// TODO: track in-progress inbound node IDs (pre-Peer) to avoid dialing them.
|
err := srv.postHandshakeChecks(peers, inboundCount, c)
|
||||||
c.cont <- srv.postHandshakeChecks(peers, inboundCount, c)
|
if err == nil && c.flags&inboundConn != 0 {
|
||||||
|
srv.dialsched.inboundPending(c.node.ID())
|
||||||
|
}
|
||||||
|
c.cont <- err
|
||||||
|
|
||||||
case c := <-srv.checkpointAddPeer:
|
case c := <-srv.checkpointAddPeer:
|
||||||
// At this point the connection is past the protocol handshake.
|
// At this point the connection is past the protocol handshake.
|
||||||
|
|
@ -870,6 +873,11 @@ func (srv *Server) checkInboundConn(remoteIP netip.Addr) error {
|
||||||
// or the handshakes have failed.
|
// or the handshakes have failed.
|
||||||
func (srv *Server) SetupConn(fd net.Conn, flags connFlag, dialDest *enode.Node) error {
|
func (srv *Server) SetupConn(fd net.Conn, flags connFlag, dialDest *enode.Node) error {
|
||||||
c := &conn{fd: fd, flags: flags, cont: make(chan error)}
|
c := &conn{fd: fd, flags: flags, cont: make(chan error)}
|
||||||
|
defer func() {
|
||||||
|
if c.is(inboundConn) && c.node != nil {
|
||||||
|
srv.dialsched.inboundCompleted(c.node.ID())
|
||||||
|
}
|
||||||
|
}()
|
||||||
if dialDest == nil {
|
if dialDest == nil {
|
||||||
c.transport = srv.newTransport(fd, nil)
|
c.transport = srv.newTransport(fd, nil)
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -222,3 +222,11 @@ var (
|
||||||
ConsolidationQueueAddress = common.HexToAddress("0x0000BBdDc7CE488642fb579F8B00f3a590007251")
|
ConsolidationQueueAddress = common.HexToAddress("0x0000BBdDc7CE488642fb579F8B00f3a590007251")
|
||||||
ConsolidationQueueCode = common.FromHex("3373fffffffffffffffffffffffffffffffffffffffe1460d35760115f54807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1461019a57600182026001905f5b5f82111560685781019083028483029004916001019190604d565b9093900492505050366060146088573661019a573461019a575f5260205ff35b341061019a57600154600101600155600354806004026004013381556001015f358155600101602035815560010160403590553360601b5f5260605f60143760745fa0600101600355005b6003546002548082038060021160e7575060025b5f5b8181146101295782810160040260040181607402815460601b815260140181600101548152602001816002015481526020019060030154905260010160e9565b910180921461013b5790600255610146565b90505f6002555f6003555b5f54807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff141561017357505f5b6001546001828201116101885750505f61018e565b01600190035b5f555f6001556074025ff35b5f5ffd")
|
ConsolidationQueueCode = common.FromHex("3373fffffffffffffffffffffffffffffffffffffffe1460d35760115f54807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1461019a57600182026001905f5b5f82111560685781019083028483029004916001019190604d565b9093900492505050366060146088573661019a573461019a575f5260205ff35b341061019a57600154600101600155600354806004026004013381556001015f358155600101602035815560010160403590553360601b5f5260605f60143760745fa0600101600355005b6003546002548082038060021160e7575060025b5f5b8181146101295782810160040260040181607402815460601b815260140181600101548152602001816002015481526020019060030154905260010160e9565b910180921461013b5790600255610146565b90505f6002555f6003555b5f54807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff141561017357505f5b6001546001828201116101885750505f61018e565b01600190035b5f555f6001556074025ff35b5f5ffd")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// System log events.
|
||||||
|
var (
|
||||||
|
// EIP-7708 - System logs emitted for ETH transfer and burn
|
||||||
|
EthTransferLogEvent = common.HexToHash("0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef") // keccak256('Transfer(address,address,uint256)')
|
||||||
|
EthBurnLogEvent = common.HexToHash("0xcc16f5dbb4873280815c1ee09dbd06736cffcc184412cf7a71a0fdb75d397ca5") // keccak256('Burn(address,uint256)')
|
||||||
|
|
||||||
|
)
|
||||||
|
|
|
||||||
|
|
@ -366,6 +366,16 @@ func (w *EncoderBuffer) AppendToBytes(dst []byte) []byte {
|
||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Size returns the total size of the content that was encoded up to this point.
|
||||||
|
// Note this does not count the size of any lists which are still 'open' (i.e. for
|
||||||
|
// which ListEnd has not been called yet).
|
||||||
|
func (w EncoderBuffer) Size() int {
|
||||||
|
if w.buf == nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return w.buf.size()
|
||||||
|
}
|
||||||
|
|
||||||
// Write appends b directly to the encoder output.
|
// Write appends b directly to the encoder output.
|
||||||
func (w EncoderBuffer) Write(b []byte) (int, error) {
|
func (w EncoderBuffer) Write(b []byte) (int, error) {
|
||||||
return w.buf.Write(b)
|
return w.buf.Write(b)
|
||||||
|
|
|
||||||
|
|
@ -507,6 +507,39 @@ func TestEncodeToReaderReturnToPool(t *testing.T) {
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestEncoderBufferSize(t *testing.T) {
|
||||||
|
var output bytes.Buffer
|
||||||
|
eb := NewEncoderBuffer(&output)
|
||||||
|
|
||||||
|
assertSize := func(state string, expectedSize int) {
|
||||||
|
t.Helper()
|
||||||
|
if s := eb.Size(); s != expectedSize {
|
||||||
|
t.Fatalf("wrong size %s: %d", state, s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assertSize("empty buffer", 0)
|
||||||
|
outerList := eb.List()
|
||||||
|
assertSize("after outer List()", 0)
|
||||||
|
eb.WriteString("abc")
|
||||||
|
assertSize("after string write", 4)
|
||||||
|
innerList := eb.List()
|
||||||
|
assertSize("after inner List()", 4)
|
||||||
|
eb.WriteUint64(1)
|
||||||
|
eb.WriteUint64(2)
|
||||||
|
assertSize("after inner list writes", 6)
|
||||||
|
eb.ListEnd(innerList)
|
||||||
|
assertSize("after end of inner list", 7)
|
||||||
|
eb.ListEnd(outerList)
|
||||||
|
assertSize("after end of outer list", 8)
|
||||||
|
eb.Flush()
|
||||||
|
assertSize("after Flush()", 0)
|
||||||
|
|
||||||
|
if output.Len() != 8 {
|
||||||
|
t.Fatalf("wrong final output size %d", output.Len())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var sink interface{}
|
var sink interface{}
|
||||||
|
|
||||||
func BenchmarkIntsize(b *testing.B) {
|
func BenchmarkIntsize(b *testing.B) {
|
||||||
|
|
|
||||||
12
rlp/raw.go
12
rlp/raw.go
|
|
@ -168,6 +168,18 @@ func (r *RawList[T]) AppendRaw(b []byte) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AppendList appends all items from another RawList to this list.
|
||||||
|
func (r *RawList[T]) AppendList(other *RawList[T]) {
|
||||||
|
if other.enc == nil || other.length == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if r.enc == nil {
|
||||||
|
r.enc = make([]byte, 9)
|
||||||
|
}
|
||||||
|
r.enc = append(r.enc, other.Content()...)
|
||||||
|
r.length += other.length
|
||||||
|
}
|
||||||
|
|
||||||
// StringSize returns the encoded size of a string.
|
// StringSize returns the encoded size of a string.
|
||||||
func StringSize(s string) uint64 {
|
func StringSize(s string) uint64 {
|
||||||
switch n := len(s); n {
|
switch n := len(s); n {
|
||||||
|
|
|
||||||
|
|
@ -246,6 +246,54 @@ func TestRawListAppendRaw(t *testing.T) {
|
||||||
t.Fatalf("wrong Len %d after invalid appends, want 2", rl.Len())
|
t.Fatalf("wrong Len %d after invalid appends, want 2", rl.Len())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
func TestRawListAppendList(t *testing.T) {
|
||||||
|
var rl1 RawList[uint64]
|
||||||
|
if err := rl1.Append(uint64(1)); err != nil {
|
||||||
|
t.Fatal("append 1 failed:", err)
|
||||||
|
}
|
||||||
|
if err := rl1.Append(uint64(2)); err != nil {
|
||||||
|
t.Fatal("append 2 failed:", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var rl2 RawList[uint64]
|
||||||
|
if err := rl2.Append(uint64(3)); err != nil {
|
||||||
|
t.Fatal("append 3 failed:", err)
|
||||||
|
}
|
||||||
|
if err := rl2.Append(uint64(4)); err != nil {
|
||||||
|
t.Fatal("append 4 failed:", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
rl1.AppendList(&rl2)
|
||||||
|
|
||||||
|
if rl1.Len() != 4 {
|
||||||
|
t.Fatalf("wrong Len %d, want 4", rl1.Len())
|
||||||
|
}
|
||||||
|
if rl1.Size() != 5 {
|
||||||
|
t.Fatalf("wrong Size %d, want 5", rl1.Size())
|
||||||
|
}
|
||||||
|
|
||||||
|
items, err := rl1.Items()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("Items failed:", err)
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(items, []uint64{1, 2, 3, 4}) {
|
||||||
|
t.Fatalf("wrong items: %v", items)
|
||||||
|
}
|
||||||
|
|
||||||
|
var empty RawList[uint64]
|
||||||
|
prevLen := rl1.Len()
|
||||||
|
rl1.AppendList(&empty)
|
||||||
|
|
||||||
|
if rl1.Len() != prevLen {
|
||||||
|
t.Fatalf("appending empty list changed Len: got %d, want %d", rl1.Len(), prevLen)
|
||||||
|
}
|
||||||
|
|
||||||
|
empty.AppendList(&rl1)
|
||||||
|
|
||||||
|
if empty.Len() != 4 {
|
||||||
|
t.Fatalf("wrong Len %d, want 4", empty.Len())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestRawListDecodeInvalid(t *testing.T) {
|
func TestRawListDecodeInvalid(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
|
|
|
||||||
|
|
@ -17,12 +17,33 @@
|
||||||
package bintrie
|
package bintrie
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/sha256"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"math/bits"
|
||||||
|
"runtime"
|
||||||
|
"sync"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// parallelDepth returns the tree depth below which Hash() spawns goroutines.
|
||||||
|
func parallelDepth() int {
|
||||||
|
return min(bits.Len(uint(runtime.NumCPU())), 8)
|
||||||
|
}
|
||||||
|
|
||||||
|
// isDirty reports whether a BinaryNode child needs rehashing.
|
||||||
|
func isDirty(n BinaryNode) bool {
|
||||||
|
switch v := n.(type) {
|
||||||
|
case *InternalNode:
|
||||||
|
return v.mustRecompute
|
||||||
|
case *StemNode:
|
||||||
|
return v.mustRecompute
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func keyToPath(depth int, key []byte) ([]byte, error) {
|
func keyToPath(depth int, key []byte) ([]byte, error) {
|
||||||
if depth > 31*8 {
|
if depth > 31*8 {
|
||||||
return nil, errors.New("node too deep")
|
return nil, errors.New("node too deep")
|
||||||
|
|
@ -124,6 +145,29 @@ func (bt *InternalNode) Hash() common.Hash {
|
||||||
return bt.hash
|
return bt.hash
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// At shallow depths, parallelize when both children need rehashing:
|
||||||
|
// hash left subtree in a goroutine, right subtree inline, then combine.
|
||||||
|
// Skip goroutine overhead when only one child is dirty (common case
|
||||||
|
// for narrow state updates that touch a single path through the trie).
|
||||||
|
if bt.depth < parallelDepth() && isDirty(bt.left) && isDirty(bt.right) {
|
||||||
|
var input [64]byte
|
||||||
|
var lh common.Hash
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
wg.Add(1)
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
lh = bt.left.Hash()
|
||||||
|
}()
|
||||||
|
rh := bt.right.Hash()
|
||||||
|
copy(input[32:], rh[:])
|
||||||
|
wg.Wait()
|
||||||
|
copy(input[:32], lh[:])
|
||||||
|
bt.hash = sha256.Sum256(input[:])
|
||||||
|
bt.mustRecompute = false
|
||||||
|
return bt.hash
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deeper nodes: sequential using pooled hasher (goroutine overhead > hash cost)
|
||||||
h := newSha256()
|
h := newSha256()
|
||||||
defer returnSha256(h)
|
defer returnSha256(h)
|
||||||
if bt.left != nil {
|
if bt.left != nil {
|
||||||
|
|
|
||||||
|
|
@ -119,10 +119,17 @@ func (it *binaryNodeIterator) Next(descend bool) bool {
|
||||||
return it.Next(descend)
|
return it.Next(descend)
|
||||||
case HashedNode:
|
case HashedNode:
|
||||||
// resolve the node
|
// resolve the node
|
||||||
data, err := it.trie.nodeResolver(it.Path(), common.Hash(node))
|
resolverPath := it.Path()
|
||||||
|
data, err := it.trie.nodeResolver(resolverPath, common.Hash(node))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
if data == nil {
|
||||||
|
// Empty/nil node — treat as Empty, backtrack
|
||||||
|
it.current = Empty{}
|
||||||
|
it.stack[len(it.stack)-1].Node = it.current
|
||||||
|
return it.Next(descend)
|
||||||
|
}
|
||||||
it.current, err = DeserializeNodeWithHash(data, len(it.stack)-1, common.Hash(node))
|
it.current, err = DeserializeNodeWithHash(data, len(it.stack)-1, common.Hash(node))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
|
|
@ -130,16 +137,25 @@ func (it *binaryNodeIterator) Next(descend bool) bool {
|
||||||
|
|
||||||
// update the stack and parent with the resolved node
|
// update the stack and parent with the resolved node
|
||||||
it.stack[len(it.stack)-1].Node = it.current
|
it.stack[len(it.stack)-1].Node = it.current
|
||||||
parent := &it.stack[len(it.stack)-2]
|
if len(it.stack) >= 2 {
|
||||||
if parent.Index == 0 {
|
parent := &it.stack[len(it.stack)-2]
|
||||||
parent.Node.(*InternalNode).left = it.current
|
if parent.Index == 0 {
|
||||||
} else {
|
parent.Node.(*InternalNode).left = it.current
|
||||||
parent.Node.(*InternalNode).right = it.current
|
} else {
|
||||||
|
parent.Node.(*InternalNode).right = it.current
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return it.Next(descend)
|
return it.Next(descend)
|
||||||
case Empty:
|
case Empty:
|
||||||
// do nothing
|
// Empty node - go back to parent and continue
|
||||||
return false
|
if len(it.stack) <= 1 {
|
||||||
|
it.lastErr = errIteratorEnd
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
it.stack = it.stack[:len(it.stack)-1]
|
||||||
|
it.current = it.stack[len(it.stack)-1].Node
|
||||||
|
it.stack[len(it.stack)-1].Index++
|
||||||
|
return it.Next(descend)
|
||||||
default:
|
default:
|
||||||
panic("invalid node type")
|
panic("invalid node type")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
239
trie/bintrie/iterator_test.go
Normal file
239
trie/bintrie/iterator_test.go
Normal file
|
|
@ -0,0 +1,239 @@
|
||||||
|
// Copyright 2026 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 bintrie
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/ethereum/go-ethereum/trie"
|
||||||
|
)
|
||||||
|
|
||||||
|
// makeTrie creates a BinaryTrie populated with the given key-value pairs.
|
||||||
|
func makeTrie(t *testing.T, entries [][2]common.Hash) *BinaryTrie {
|
||||||
|
t.Helper()
|
||||||
|
tr := &BinaryTrie{
|
||||||
|
root: NewBinaryNode(),
|
||||||
|
tracer: trie.NewPrevalueTracer(),
|
||||||
|
}
|
||||||
|
for _, kv := range entries {
|
||||||
|
var err error
|
||||||
|
tr.root, err = tr.root.Insert(kv[0][:], kv[1][:], nil, 0)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return tr
|
||||||
|
}
|
||||||
|
|
||||||
|
// countLeaves iterates the trie and returns the number of leaves visited.
|
||||||
|
func countLeaves(t *testing.T, tr *BinaryTrie) int {
|
||||||
|
t.Helper()
|
||||||
|
it, err := newBinaryNodeIterator(tr, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
leaves := 0
|
||||||
|
for it.Next(true) {
|
||||||
|
if it.Leaf() {
|
||||||
|
leaves++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if it.Error() != nil {
|
||||||
|
t.Fatalf("iterator error: %v", it.Error())
|
||||||
|
}
|
||||||
|
return leaves
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestIteratorEmptyTrie verifies that iterating over an empty trie returns
|
||||||
|
// no nodes and reports no error.
|
||||||
|
func TestIteratorEmptyTrie(t *testing.T) {
|
||||||
|
tr := &BinaryTrie{
|
||||||
|
root: Empty{},
|
||||||
|
tracer: trie.NewPrevalueTracer(),
|
||||||
|
}
|
||||||
|
it, err := newBinaryNodeIterator(tr, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if it.Next(true) {
|
||||||
|
t.Fatal("expected no iteration over empty trie")
|
||||||
|
}
|
||||||
|
if it.Error() != nil {
|
||||||
|
t.Fatalf("unexpected error: %v", it.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestIteratorSingleStem verifies iteration over a trie with a single stem
|
||||||
|
// node containing multiple values.
|
||||||
|
func TestIteratorSingleStem(t *testing.T) {
|
||||||
|
tr := makeTrie(t, [][2]common.Hash{
|
||||||
|
{common.HexToHash("0000000000000000000000000000000000000000000000000000000000000003"), oneKey},
|
||||||
|
{common.HexToHash("0000000000000000000000000000000000000000000000000000000000000007"), oneKey},
|
||||||
|
{common.HexToHash("00000000000000000000000000000000000000000000000000000000000000FF"), oneKey},
|
||||||
|
})
|
||||||
|
if leaves := countLeaves(t, tr); leaves != 3 {
|
||||||
|
t.Fatalf("expected 3 leaves, got %d", leaves)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestIteratorTwoStems verifies iteration over a trie with two stems
|
||||||
|
// separated by internal nodes, ensuring all leaves from both stems are visited.
|
||||||
|
func TestIteratorTwoStems(t *testing.T) {
|
||||||
|
tr := makeTrie(t, [][2]common.Hash{
|
||||||
|
{common.HexToHash("0000000000000000000000000000000000000000000000000000000000000001"), oneKey},
|
||||||
|
{common.HexToHash("0000000000000000000000000000000000000000000000000000000000000002"), oneKey},
|
||||||
|
{common.HexToHash("8000000000000000000000000000000000000000000000000000000000000001"), oneKey},
|
||||||
|
{common.HexToHash("8000000000000000000000000000000000000000000000000000000000000002"), oneKey},
|
||||||
|
})
|
||||||
|
if leaves := countLeaves(t, tr); leaves != 4 {
|
||||||
|
t.Fatalf("expected 4 leaves, got %d", leaves)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestIteratorLeafKeyAndBlob verifies that the iterator returns correct
|
||||||
|
// leaf keys and values.
|
||||||
|
func TestIteratorLeafKeyAndBlob(t *testing.T) {
|
||||||
|
key := common.HexToHash("0000000000000000000000000000000000000000000000000000000000000005")
|
||||||
|
val := common.HexToHash("00000000000000000000000000000000000000000000000000000000deadbeef")
|
||||||
|
tr := makeTrie(t, [][2]common.Hash{{key, val}})
|
||||||
|
|
||||||
|
it, err := newBinaryNodeIterator(tr, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
found := false
|
||||||
|
for it.Next(true) {
|
||||||
|
if it.Leaf() {
|
||||||
|
found = true
|
||||||
|
if !bytes.Equal(it.LeafKey(), key[:]) {
|
||||||
|
t.Fatalf("leaf key mismatch: got %x, want %x", it.LeafKey(), key)
|
||||||
|
}
|
||||||
|
if !bytes.Equal(it.LeafBlob(), val[:]) {
|
||||||
|
t.Fatalf("leaf blob mismatch: got %x, want %x", it.LeafBlob(), val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
t.Fatal("expected to find a leaf")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestIteratorEmptyNodeBacktrack is a regression test for the Empty node
|
||||||
|
// backtracking bug. Before the fix, encountering an Empty child during
|
||||||
|
// iteration would terminate the walk prematurely instead of backtracking
|
||||||
|
// to the parent and continuing with the next sibling.
|
||||||
|
func TestIteratorEmptyNodeBacktrack(t *testing.T) {
|
||||||
|
tr := makeTrie(t, [][2]common.Hash{
|
||||||
|
{common.HexToHash("0000000000000000000000000000000000000000000000000000000000000001"), oneKey},
|
||||||
|
{common.HexToHash("8000000000000000000000000000000000000000000000000000000000000001"), oneKey},
|
||||||
|
})
|
||||||
|
|
||||||
|
if _, ok := tr.root.(*InternalNode); !ok {
|
||||||
|
t.Fatalf("expected InternalNode root, got %T", tr.root)
|
||||||
|
}
|
||||||
|
if leaves := countLeaves(t, tr); leaves != 2 {
|
||||||
|
t.Fatalf("expected 2 leaves, got %d (Empty backtrack bug?)", leaves)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestIteratorHashedNodeNilData is a regression test for the nil-data guard.
|
||||||
|
// When nodeResolver encounters a zero-hash HashedNode, it returns (nil, nil).
|
||||||
|
// The iterator should treat this as Empty and continue rather than panicking.
|
||||||
|
func TestIteratorHashedNodeNilData(t *testing.T) {
|
||||||
|
tr := makeTrie(t, [][2]common.Hash{
|
||||||
|
{common.HexToHash("0000000000000000000000000000000000000000000000000000000000000001"), oneKey},
|
||||||
|
{common.HexToHash("8000000000000000000000000000000000000000000000000000000000000001"), oneKey},
|
||||||
|
})
|
||||||
|
|
||||||
|
root, ok := tr.root.(*InternalNode)
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("expected InternalNode root, got %T", tr.root)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Replace right child with a zero-hash HashedNode. nodeResolver
|
||||||
|
// short-circuits on common.Hash{} and returns (nil, nil), which
|
||||||
|
// triggers the nil-data guard in the iterator.
|
||||||
|
root.right = HashedNode(common.Hash{})
|
||||||
|
|
||||||
|
// Should not panic; the zero-hash right child should be treated as Empty.
|
||||||
|
if leaves := countLeaves(t, tr); leaves != 1 {
|
||||||
|
t.Fatalf("expected 1 leaf (zero-hash right node skipped), got %d", leaves)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestIteratorManyStems verifies iteration correctness with many stems,
|
||||||
|
// producing a deep tree structure.
|
||||||
|
func TestIteratorManyStems(t *testing.T) {
|
||||||
|
entries := make([][2]common.Hash, 16)
|
||||||
|
for i := range entries {
|
||||||
|
var key common.Hash
|
||||||
|
key[0] = byte(i << 4)
|
||||||
|
key[31] = 1
|
||||||
|
entries[i] = [2]common.Hash{key, oneKey}
|
||||||
|
}
|
||||||
|
tr := makeTrie(t, entries)
|
||||||
|
if leaves := countLeaves(t, tr); leaves != 16 {
|
||||||
|
t.Fatalf("expected 16 leaves, got %d", leaves)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestIteratorDeepTree verifies iteration over a trie with stems that share
|
||||||
|
// a long common prefix, producing many intermediate InternalNodes.
|
||||||
|
func TestIteratorDeepTree(t *testing.T) {
|
||||||
|
tr := makeTrie(t, [][2]common.Hash{
|
||||||
|
{common.HexToHash("0000000000C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0"), oneKey},
|
||||||
|
{common.HexToHash("0000000000E00000000000000000000000000000000000000000000000000000"), twoKey},
|
||||||
|
})
|
||||||
|
if leaves := countLeaves(t, tr); leaves != 2 {
|
||||||
|
t.Fatalf("expected 2 leaves in deep tree, got %d", leaves)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestIteratorNodeCount verifies the total number of Next(true) calls
|
||||||
|
// for a known tree structure.
|
||||||
|
func TestIteratorNodeCount(t *testing.T) {
|
||||||
|
tr := makeTrie(t, [][2]common.Hash{
|
||||||
|
{common.HexToHash("0000000000000000000000000000000000000000000000000000000000000001"), oneKey},
|
||||||
|
{common.HexToHash("8000000000000000000000000000000000000000000000000000000000000001"), oneKey},
|
||||||
|
})
|
||||||
|
|
||||||
|
it, err := newBinaryNodeIterator(tr, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
total := 0
|
||||||
|
leaves := 0
|
||||||
|
for it.Next(true) {
|
||||||
|
total++
|
||||||
|
if it.Leaf() {
|
||||||
|
leaves++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if leaves != 2 {
|
||||||
|
t.Fatalf("expected 2 leaves, got %d", leaves)
|
||||||
|
}
|
||||||
|
// Root(InternalNode) + leaf1 (from left StemNode) + leaf2 (from right StemNode) = 3
|
||||||
|
// StemNodes are not returned as separate steps; the iterator advances
|
||||||
|
// directly to the first non-nil value within the stem.
|
||||||
|
if total != 3 {
|
||||||
|
t.Fatalf("expected 3 total nodes, got %d", total)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -36,6 +36,18 @@ func NewLevelStats() *LevelStats {
|
||||||
return &LevelStats{}
|
return &LevelStats{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Copy returns a deep copy of the statistics.
|
||||||
|
func (s *LevelStats) Copy() *LevelStats {
|
||||||
|
cpy := NewLevelStats()
|
||||||
|
for i := range s.level {
|
||||||
|
cpy.level[i].short.Store(s.level[i].short.Load())
|
||||||
|
cpy.level[i].full.Store(s.level[i].full.Load())
|
||||||
|
cpy.level[i].value.Store(s.level[i].value.Load())
|
||||||
|
cpy.level[i].size.Store(s.level[i].size.Load())
|
||||||
|
}
|
||||||
|
return cpy
|
||||||
|
}
|
||||||
|
|
||||||
// MaxDepth iterates each level and finds the deepest level with at least one
|
// MaxDepth iterates each level and finds the deepest level with at least one
|
||||||
// trie node.
|
// trie node.
|
||||||
func (s *LevelStats) MaxDepth() int {
|
func (s *LevelStats) MaxDepth() int {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue