mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-06-12 09:51:36 +00:00
Merge branch 'ethereum:master' into master
This commit is contained in:
commit
301bed656d
179 changed files with 16550 additions and 10707 deletions
|
|
@ -438,14 +438,11 @@ func (s *serverWithLimits) fail(desc string) {
|
||||||
// failLocked calculates the dynamic failure delay and applies it.
|
// failLocked calculates the dynamic failure delay and applies it.
|
||||||
func (s *serverWithLimits) failLocked(desc string) {
|
func (s *serverWithLimits) failLocked(desc string) {
|
||||||
log.Debug("Server error", "description", desc)
|
log.Debug("Server error", "description", desc)
|
||||||
s.failureDelay *= 2
|
|
||||||
now := s.clock.Now()
|
now := s.clock.Now()
|
||||||
if now > s.failureDelayEnd {
|
if now > s.failureDelayEnd {
|
||||||
s.failureDelay *= math.Pow(2, -float64(now-s.failureDelayEnd)/float64(maxFailureDelay))
|
s.failureDelay *= math.Pow(2, -float64(now-s.failureDelayEnd)/float64(maxFailureDelay))
|
||||||
}
|
}
|
||||||
if s.failureDelay < float64(minFailureDelay) {
|
s.failureDelay = max(min(s.failureDelay*2, float64(maxFailureDelay)), float64(minFailureDelay))
|
||||||
s.failureDelay = float64(minFailureDelay)
|
|
||||||
}
|
|
||||||
s.failureDelayEnd = now + mclock.AbsTime(s.failureDelay)
|
s.failureDelayEnd = now + mclock.AbsTime(s.failureDelay)
|
||||||
s.delay(time.Duration(s.failureDelay))
|
s.delay(time.Duration(s.failureDelay))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -62,7 +62,6 @@ const (
|
||||||
ssNeedParent // cp header slot %32 != 0, need parent to check epoch boundary
|
ssNeedParent // cp header slot %32 != 0, need parent to check epoch boundary
|
||||||
ssParentRequested // cp parent header requested
|
ssParentRequested // cp parent header requested
|
||||||
ssPrintStatus // has all necessary info, print log message if init still not successful
|
ssPrintStatus // has all necessary info, print log message if init still not successful
|
||||||
ssDone // log message printed, no more action required
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type serverState struct {
|
type serverState struct {
|
||||||
|
|
@ -180,7 +179,8 @@ func (s *CheckpointInit) Process(requester request.Requester, events []request.E
|
||||||
default:
|
default:
|
||||||
log.Error("blsync: checkpoint not available, but reported as finalized; specified checkpoint hash might be too old", "server", server.Name())
|
log.Error("blsync: checkpoint not available, but reported as finalized; specified checkpoint hash might be too old", "server", server.Name())
|
||||||
}
|
}
|
||||||
s.serverState[server] = serverState{state: ssDone}
|
s.serverState[server] = serverState{state: ssDefault}
|
||||||
|
requester.Fail(server, "checkpoint init failed")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
22
build/ci.go
22
build/ci.go
|
|
@ -108,17 +108,21 @@ var (
|
||||||
Tags: "ziren",
|
Tags: "ziren",
|
||||||
Env: map[string]string{"GOMIPS": "softfloat", "CGO_ENABLED": "0"},
|
Env: map[string]string{"GOMIPS": "softfloat", "CGO_ENABLED": "0"},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Name: "womir",
|
||||||
|
GOOS: "wasip1",
|
||||||
|
GOARCH: "wasm",
|
||||||
|
Tags: "womir",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
Name: "wasm-js",
|
Name: "wasm-js",
|
||||||
GOOS: "js",
|
GOOS: "js",
|
||||||
GOARCH: "wasm",
|
GOARCH: "wasm",
|
||||||
Tags: "example",
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "wasm-wasi",
|
Name: "wasm-wasi",
|
||||||
GOOS: "wasip1",
|
GOOS: "wasip1",
|
||||||
GOARCH: "wasm",
|
GOARCH: "wasm",
|
||||||
Tags: "example",
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "example",
|
Name: "example",
|
||||||
|
|
@ -168,11 +172,11 @@ var (
|
||||||
|
|
||||||
// Distros for which packages are created
|
// Distros for which packages are created
|
||||||
debDistros = []string{
|
debDistros = []string{
|
||||||
"xenial", // 16.04, EOL: 04/2026
|
"xenial", // 16.04, EOL: 04/2026
|
||||||
"bionic", // 18.04, EOL: 04/2028
|
"bionic", // 18.04, EOL: 04/2028
|
||||||
"focal", // 20.04, EOL: 04/2030
|
"focal", // 20.04, EOL: 04/2030
|
||||||
"jammy", // 22.04, EOL: 04/2032
|
"jammy", // 22.04, EOL: 04/2032
|
||||||
"noble", // 24.04, EOL: 04/2034
|
"noble", // 24.04, EOL: 04/2034
|
||||||
}
|
}
|
||||||
|
|
||||||
// This is where the tests should be unpacked.
|
// This is where the tests should be unpacked.
|
||||||
|
|
@ -310,7 +314,7 @@ func doInstallKeeper(cmdline []string) {
|
||||||
args := slices.Clone(gobuild.Args)
|
args := slices.Clone(gobuild.Args)
|
||||||
args = append(args, "-o", executablePath(outputName))
|
args = append(args, "-o", executablePath(outputName))
|
||||||
args = append(args, ".")
|
args = append(args, ".")
|
||||||
build.MustRun(&exec.Cmd{Path: gobuild.Path, Args: args, Env: gobuild.Env})
|
build.MustRun(&exec.Cmd{Path: gobuild.Path, Args: args, Env: gobuild.Env, Dir: gobuild.Dir})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1201,7 +1205,7 @@ func doWindowsInstaller(cmdline []string) {
|
||||||
var (
|
var (
|
||||||
arch = flag.String("arch", runtime.GOARCH, "Architecture for cross build packaging")
|
arch = flag.String("arch", runtime.GOARCH, "Architecture for cross build packaging")
|
||||||
signer = flag.String("signer", "", `Environment variable holding the signing key (e.g. WINDOWS_SIGNING_KEY)`)
|
signer = flag.String("signer", "", `Environment variable holding the signing key (e.g. WINDOWS_SIGNING_KEY)`)
|
||||||
signify = flag.String("signify key", "", `Environment variable holding the signify signing key (e.g. WINDOWS_SIGNIFY_KEY)`)
|
signify = flag.String("signify", "", `Environment variable holding the signify signing key (e.g. WINDOWS_SIGNIFY_KEY)`)
|
||||||
upload = flag.String("upload", "", `Destination to upload the archives (usually "gethstore/builds")`)
|
upload = flag.String("upload", "", `Destination to upload the archives (usually "gethstore/builds")`)
|
||||||
workdir = flag.String("workdir", "", `Output directory for packages (uses temp dir if unset)`)
|
workdir = flag.String("workdir", "", `Output directory for packages (uses temp dir if unset)`)
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -51,6 +51,12 @@ type Chain struct {
|
||||||
state map[common.Address]state.DumpAccount // state of head block
|
state map[common.Address]state.DumpAccount // state of head block
|
||||||
senders map[common.Address]*senderInfo
|
senders map[common.Address]*senderInfo
|
||||||
config *params.ChainConfig
|
config *params.ChainConfig
|
||||||
|
|
||||||
|
txInfo txInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
type txInfo struct {
|
||||||
|
LargeReceiptBlock *uint64 `json:"tx-largereceipt"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewChain takes the given chain.rlp file, and decodes and returns
|
// NewChain takes the given chain.rlp file, and decodes and returns
|
||||||
|
|
@ -74,12 +80,20 @@ func NewChain(dir string) (*Chain, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var txInfo txInfo
|
||||||
|
err = common.LoadJSON(filepath.Join(dir, "txinfo.json"), &txInfo)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
return &Chain{
|
return &Chain{
|
||||||
genesis: gen,
|
genesis: gen,
|
||||||
blocks: blocks,
|
blocks: blocks,
|
||||||
state: state,
|
state: state,
|
||||||
senders: accounts,
|
senders: accounts,
|
||||||
config: gen.Config,
|
config: gen.Config,
|
||||||
|
txInfo: txInfo,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -66,9 +66,10 @@ func (s *Suite) dialAs(key *ecdsa.PrivateKey) (*Conn, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
conn.caps = []p2p.Cap{
|
conn.caps = []p2p.Cap{
|
||||||
|
{Name: "eth", Version: 70},
|
||||||
{Name: "eth", Version: 69},
|
{Name: "eth", Version: 69},
|
||||||
}
|
}
|
||||||
conn.ourHighestProtoVersion = 69
|
conn.ourHighestProtoVersion = 70
|
||||||
return &conn, nil
|
return &conn, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -335,10 +336,12 @@ loop:
|
||||||
if have, want := msg.ForkID, chain.ForkID(); !reflect.DeepEqual(have, want) {
|
if have, want := msg.ForkID, chain.ForkID(); !reflect.DeepEqual(have, want) {
|
||||||
return fmt.Errorf("wrong fork ID in status: have %v, want %v", have, want)
|
return fmt.Errorf("wrong fork ID in status: have %v, want %v", have, want)
|
||||||
}
|
}
|
||||||
if have, want := msg.ProtocolVersion, c.ourHighestProtoVersion; have != uint32(want) {
|
for _, cap := range c.caps {
|
||||||
return fmt.Errorf("wrong protocol version: have %v, want %v", have, want)
|
if cap.Name == "eth" && cap.Version == uint(msg.ProtocolVersion) {
|
||||||
|
break loop
|
||||||
|
}
|
||||||
}
|
}
|
||||||
break loop
|
return fmt.Errorf("wrong protocol version: have %v, want %v", msg.ProtocolVersion, c.caps)
|
||||||
case discMsg:
|
case discMsg:
|
||||||
var msg []p2p.DiscReason
|
var msg []p2p.DiscReason
|
||||||
if rlp.DecodeBytes(data, &msg); len(msg) == 0 {
|
if rlp.DecodeBytes(data, &msg); len(msg) == 0 {
|
||||||
|
|
|
||||||
|
|
@ -87,9 +87,9 @@ func (s *Suite) TestSnapGetAccountRange(t *utesting.T) {
|
||||||
root: root,
|
root: root,
|
||||||
startingHash: zero,
|
startingHash: zero,
|
||||||
limitHash: ffHash,
|
limitHash: ffHash,
|
||||||
expAccounts: 67,
|
expAccounts: 68,
|
||||||
expFirst: firstKey,
|
expFirst: firstKey,
|
||||||
expLast: common.HexToHash("0x622e662246601dd04f996289ce8b85e86db7bb15bb17f86487ec9d543ddb6f9a"),
|
expLast: common.HexToHash("0x59312f89c13e9e24c1cb8b103aa39a9b2800348d97a92c2c9e2a78fa02b70025"),
|
||||||
desc: "In this test, we request the entire state range, but limit the response to 4000 bytes.",
|
desc: "In this test, we request the entire state range, but limit the response to 4000 bytes.",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -97,9 +97,9 @@ func (s *Suite) TestSnapGetAccountRange(t *utesting.T) {
|
||||||
root: root,
|
root: root,
|
||||||
startingHash: zero,
|
startingHash: zero,
|
||||||
limitHash: ffHash,
|
limitHash: ffHash,
|
||||||
expAccounts: 49,
|
expAccounts: 50,
|
||||||
expFirst: firstKey,
|
expFirst: firstKey,
|
||||||
expLast: common.HexToHash("0x445cb5c1278fdce2f9cbdb681bdd76c52f8e50e41dbd9e220242a69ba99ac099"),
|
expLast: common.HexToHash("0x4615e5f5df5b25349a00ad313c6cd0436b6c08ee5826e33a018661997f85ebaa"),
|
||||||
desc: "In this test, we request the entire state range, but limit the response to 3000 bytes.",
|
desc: "In this test, we request the entire state range, but limit the response to 3000 bytes.",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -107,9 +107,9 @@ func (s *Suite) TestSnapGetAccountRange(t *utesting.T) {
|
||||||
root: root,
|
root: root,
|
||||||
startingHash: zero,
|
startingHash: zero,
|
||||||
limitHash: ffHash,
|
limitHash: ffHash,
|
||||||
expAccounts: 34,
|
expAccounts: 35,
|
||||||
expFirst: firstKey,
|
expFirst: firstKey,
|
||||||
expLast: common.HexToHash("0x2ef46ebd2073cecde499c2e8df028ad79a26d57bfaa812c4c6f7eb4c9617b913"),
|
expLast: common.HexToHash("0x2de4bdbddcfbb9c3e195dae6b45f9c38daff897e926764bf34887fb0db5c3284"),
|
||||||
desc: "In this test, we request the entire state range, but limit the response to 2000 bytes.",
|
desc: "In this test, we request the entire state range, but limit the response to 2000 bytes.",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -178,9 +178,9 @@ The server should return the first available account.`,
|
||||||
root: root,
|
root: root,
|
||||||
startingHash: firstKey,
|
startingHash: firstKey,
|
||||||
limitHash: ffHash,
|
limitHash: ffHash,
|
||||||
expAccounts: 67,
|
expAccounts: 68,
|
||||||
expFirst: firstKey,
|
expFirst: firstKey,
|
||||||
expLast: common.HexToHash("0x622e662246601dd04f996289ce8b85e86db7bb15bb17f86487ec9d543ddb6f9a"),
|
expLast: common.HexToHash("0x59312f89c13e9e24c1cb8b103aa39a9b2800348d97a92c2c9e2a78fa02b70025"),
|
||||||
desc: `In this test, startingHash is exactly the first available account key.
|
desc: `In this test, startingHash is exactly the first available account key.
|
||||||
The server should return the first available account of the state as the first item.`,
|
The server should return the first available account of the state as the first item.`,
|
||||||
},
|
},
|
||||||
|
|
@ -189,9 +189,9 @@ The server should return the first available account of the state as the first i
|
||||||
root: root,
|
root: root,
|
||||||
startingHash: hashAdd(firstKey, 1),
|
startingHash: hashAdd(firstKey, 1),
|
||||||
limitHash: ffHash,
|
limitHash: ffHash,
|
||||||
expAccounts: 67,
|
expAccounts: 68,
|
||||||
expFirst: secondKey,
|
expFirst: secondKey,
|
||||||
expLast: common.HexToHash("0x66192e4c757fba1cdc776e6737008f42d50370d3cd801db3624274283bf7cd63"),
|
expLast: common.HexToHash("0x59a7c8818f1c16b298a054020dc7c3f403a970d1d1db33f9478b1c36e3a2e509"),
|
||||||
desc: `In this test, startingHash is after the first available key.
|
desc: `In this test, startingHash is after the first available key.
|
||||||
The server should return the second account of the state as the first item.`,
|
The server should return the second account of the state as the first item.`,
|
||||||
},
|
},
|
||||||
|
|
@ -227,9 +227,9 @@ server to return no data because genesis is older than 127 blocks.`,
|
||||||
root: s.chain.RootAt(int(s.chain.Head().Number().Uint64()) - 127),
|
root: s.chain.RootAt(int(s.chain.Head().Number().Uint64()) - 127),
|
||||||
startingHash: zero,
|
startingHash: zero,
|
||||||
limitHash: ffHash,
|
limitHash: ffHash,
|
||||||
expAccounts: 66,
|
expAccounts: 68,
|
||||||
expFirst: firstKey,
|
expFirst: firstKey,
|
||||||
expLast: common.HexToHash("0x729953a43ed6c913df957172680a17e5735143ad767bda8f58ac84ec62fbec5e"),
|
expLast: common.HexToHash("0x683b6c03cc32afe5db8cb96050f711fdaff8f8ff44c7587a9a848f921d02815e"),
|
||||||
desc: `This test requests data at a state root that is 127 blocks old.
|
desc: `This test requests data at a state root that is 127 blocks old.
|
||||||
We expect the server to have this state available.`,
|
We expect the server to have this state available.`,
|
||||||
},
|
},
|
||||||
|
|
@ -658,8 +658,8 @@ The server should reject the request.`,
|
||||||
// It's a bit unfortunate these are hard-coded, but the result depends on
|
// It's a bit unfortunate these are hard-coded, but the result depends on
|
||||||
// a lot of aspects of the state trie and can't be guessed in a simple
|
// a lot of aspects of the state trie and can't be guessed in a simple
|
||||||
// way. So you'll have to update this when the test chain is changed.
|
// way. So you'll have to update this when the test chain is changed.
|
||||||
common.HexToHash("0x5bdc0d6057b35642a16d27223ea5454e5a17a400e28f7328971a5f2a87773b76"),
|
common.HexToHash("0x4bdecec09691ad38113eebee2df94fadefdff5841c0f182bae1be3c8a6d60bf3"),
|
||||||
common.HexToHash("0x0a76c9812ca90ffed8ee4d191e683f93386b6e50cfe3679c0760d27510aa7fc5"),
|
common.HexToHash("0x4178696465d4514ff5924ef8c28ce64d41a669634b63184c2c093e252d6b4bc4"),
|
||||||
empty, empty, empty, empty, empty, empty, empty, empty, empty, empty, empty, empty,
|
empty, empty, empty, empty, empty, empty, empty, empty, empty, empty, empty, empty,
|
||||||
empty, empty, empty, empty, empty, empty, empty, empty, empty, empty, empty, empty,
|
empty, empty, empty, empty, empty, empty, empty, empty, empty, empty, empty, empty,
|
||||||
empty, empty, empty, empty, empty, empty, empty, empty, empty, empty, empty, empty,
|
empty, empty, empty, empty, empty, empty, empty, empty, empty, empty, empty, empty,
|
||||||
|
|
@ -679,8 +679,8 @@ The server should reject the request.`,
|
||||||
// be updated when the test chain is changed.
|
// be updated when the test chain is changed.
|
||||||
expHashes: []common.Hash{
|
expHashes: []common.Hash{
|
||||||
empty,
|
empty,
|
||||||
common.HexToHash("0x0a76c9812ca90ffed8ee4d191e683f93386b6e50cfe3679c0760d27510aa7fc5"),
|
common.HexToHash("0x4178696465d4514ff5924ef8c28ce64d41a669634b63184c2c093e252d6b4bc4"),
|
||||||
common.HexToHash("0x5bdc0d6057b35642a16d27223ea5454e5a17a400e28f7328971a5f2a87773b76"),
|
common.HexToHash("0x4bdecec09691ad38113eebee2df94fadefdff5841c0f182bae1be3c8a6d60bf3"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,7 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/p2p"
|
"github.com/ethereum/go-ethereum/p2p"
|
||||||
"github.com/ethereum/go-ethereum/p2p/enode"
|
"github.com/ethereum/go-ethereum/p2p/enode"
|
||||||
"github.com/ethereum/go-ethereum/rlp"
|
"github.com/ethereum/go-ethereum/rlp"
|
||||||
|
"github.com/ethereum/go-ethereum/trie"
|
||||||
"github.com/holiman/uint256"
|
"github.com/holiman/uint256"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -83,6 +84,7 @@ func (s *Suite) EthTests() []utesting.Test {
|
||||||
// get history
|
// get history
|
||||||
{Name: "GetBlockBodies", Fn: s.TestGetBlockBodies},
|
{Name: "GetBlockBodies", Fn: s.TestGetBlockBodies},
|
||||||
{Name: "GetReceipts", Fn: s.TestGetReceipts},
|
{Name: "GetReceipts", Fn: s.TestGetReceipts},
|
||||||
|
{Name: "GetLargeReceipts", Fn: s.TestGetLargeReceipts},
|
||||||
// test transactions
|
// test transactions
|
||||||
{Name: "LargeTxRequest", Fn: s.TestLargeTxRequest, Slow: true},
|
{Name: "LargeTxRequest", Fn: s.TestLargeTxRequest, Slow: true},
|
||||||
{Name: "Transaction", Fn: s.TestTransaction},
|
{Name: "Transaction", Fn: s.TestTransaction},
|
||||||
|
|
@ -429,6 +431,9 @@ func (s *Suite) TestGetReceipts(t *utesting.T) {
|
||||||
// Find some blocks containing receipts.
|
// Find some blocks containing receipts.
|
||||||
var hashes = make([]common.Hash, 0, 3)
|
var hashes = make([]common.Hash, 0, 3)
|
||||||
for i := range s.chain.Len() {
|
for i := range s.chain.Len() {
|
||||||
|
if s.chain.txInfo.LargeReceiptBlock != nil && uint64(i) == *s.chain.txInfo.LargeReceiptBlock {
|
||||||
|
continue
|
||||||
|
}
|
||||||
block := s.chain.GetBlock(i)
|
block := s.chain.GetBlock(i)
|
||||||
if len(block.Transactions()) > 0 {
|
if len(block.Transactions()) > 0 {
|
||||||
hashes = append(hashes, block.Hash())
|
hashes = append(hashes, block.Hash())
|
||||||
|
|
@ -437,25 +442,121 @@ func (s *Suite) TestGetReceipts(t *utesting.T) {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if conn.negotiatedProtoVersion < eth.ETH70 {
|
||||||
|
// Create block bodies request.
|
||||||
|
req := ð.GetReceiptsPacket69{
|
||||||
|
RequestId: 66,
|
||||||
|
GetReceiptsRequest: (eth.GetReceiptsRequest)(hashes),
|
||||||
|
}
|
||||||
|
if err := conn.Write(ethProto, eth.GetReceiptsMsg, req); err != nil {
|
||||||
|
t.Fatalf("could not write to connection: %v", err)
|
||||||
|
}
|
||||||
|
// Wait for response.
|
||||||
|
resp := new(eth.ReceiptsPacket69)
|
||||||
|
if err := conn.ReadMsg(ethProto, eth.ReceiptsMsg, &resp); err != nil {
|
||||||
|
t.Fatalf("error reading block receipts msg: %v", err)
|
||||||
|
}
|
||||||
|
if got, want := resp.RequestId, req.RequestId; got != want {
|
||||||
|
t.Fatalf("unexpected request id in respond", got, want)
|
||||||
|
}
|
||||||
|
if resp.List.Len() != len(req.GetReceiptsRequest) {
|
||||||
|
t.Fatalf("wrong receipts in response: expected %d receipts, got %d", len(req.GetReceiptsRequest), resp.List.Len())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Create block bodies request.
|
||||||
|
req := ð.GetReceiptsPacket70{
|
||||||
|
RequestId: 66,
|
||||||
|
FirstBlockReceiptIndex: 0,
|
||||||
|
GetReceiptsRequest: (eth.GetReceiptsRequest)(hashes),
|
||||||
|
}
|
||||||
|
if err := conn.Write(ethProto, eth.GetReceiptsMsg, req); err != nil {
|
||||||
|
t.Fatalf("could not write to connection: %v", err)
|
||||||
|
}
|
||||||
|
// Wait for response.
|
||||||
|
resp := new(eth.ReceiptsPacket70)
|
||||||
|
if err := conn.ReadMsg(ethProto, eth.ReceiptsMsg, &resp); err != nil {
|
||||||
|
t.Fatalf("error reading block receipts msg: %v", err)
|
||||||
|
}
|
||||||
|
if got, want := resp.RequestId, req.RequestId; got != want {
|
||||||
|
t.Fatalf("unexpected request id in respond", got, want)
|
||||||
|
}
|
||||||
|
if resp.List.Len() != len(req.GetReceiptsRequest) {
|
||||||
|
t.Fatalf("wrong receipts in response: expected %d receipts, got %d", len(req.GetReceiptsRequest), resp.List.Len())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Create receipts request.
|
func (s *Suite) TestGetLargeReceipts(t *utesting.T) {
|
||||||
req := ð.GetReceiptsPacket{
|
t.Log(`This test sends GetReceipts requests to the node for large receipt (>10MiB) in the test chain.
|
||||||
RequestId: 66,
|
This test is meaningful only if the client supports protocol version ETH70 or higher
|
||||||
GetReceiptsRequest: (eth.GetReceiptsRequest)(hashes),
|
and LargeReceiptBlock is configured in txInfo.json.`)
|
||||||
|
conn, err := s.dialAndPeer(nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("peering failed: %v", err)
|
||||||
}
|
}
|
||||||
if err := conn.Write(ethProto, eth.GetReceiptsMsg, req); err != nil {
|
defer conn.Close()
|
||||||
t.Fatalf("could not write to connection: %v", err)
|
|
||||||
|
if conn.negotiatedProtoVersion < eth.ETH70 || s.chain.txInfo.LargeReceiptBlock == nil {
|
||||||
|
return
|
||||||
}
|
}
|
||||||
// Wait for response.
|
|
||||||
resp := new(eth.ReceiptsPacket)
|
// Find block with large receipt.
|
||||||
if err := conn.ReadMsg(ethProto, eth.ReceiptsMsg, &resp); err != nil {
|
// Place the large receipt block hash in the middle of the query
|
||||||
t.Fatalf("error reading block bodies msg: %v", err)
|
start := max(int(*s.chain.txInfo.LargeReceiptBlock)-2, 0)
|
||||||
|
end := min(*s.chain.txInfo.LargeReceiptBlock+2, uint64(len(s.chain.blocks)))
|
||||||
|
|
||||||
|
var blocks []common.Hash
|
||||||
|
var receiptHashes []common.Hash
|
||||||
|
var receipts []*eth.ReceiptList
|
||||||
|
|
||||||
|
for i := uint64(start); i < end; i++ {
|
||||||
|
block := s.chain.GetBlock(int(i))
|
||||||
|
blocks = append(blocks, block.Hash())
|
||||||
|
receiptHashes = append(receiptHashes, block.Header().ReceiptHash)
|
||||||
|
receipts = append(receipts, ð.ReceiptList{})
|
||||||
}
|
}
|
||||||
if got, want := resp.RequestId, req.RequestId; got != want {
|
|
||||||
t.Fatalf("unexpected request id in respond", got, want)
|
incomplete := false
|
||||||
|
lastBlock := 0
|
||||||
|
|
||||||
|
for incomplete || lastBlock != len(blocks)-1 {
|
||||||
|
// Create get receipt request.
|
||||||
|
req := ð.GetReceiptsPacket70{
|
||||||
|
RequestId: 66,
|
||||||
|
FirstBlockReceiptIndex: uint64(receipts[lastBlock].Derivable().Len()),
|
||||||
|
GetReceiptsRequest: blocks[lastBlock:],
|
||||||
|
}
|
||||||
|
if err := conn.Write(ethProto, eth.GetReceiptsMsg, req); err != nil {
|
||||||
|
t.Fatalf("could not write to connection: %v", err)
|
||||||
|
}
|
||||||
|
// Wait for response.
|
||||||
|
resp := new(eth.ReceiptsPacket70)
|
||||||
|
if err := conn.ReadMsg(ethProto, eth.ReceiptsMsg, &resp); err != nil {
|
||||||
|
t.Fatalf("error reading block receipts msg: %v", err)
|
||||||
|
}
|
||||||
|
if got, want := resp.RequestId, req.RequestId; got != want {
|
||||||
|
t.Fatalf("unexpected request id in respond, want: %d, got: %d", got, want)
|
||||||
|
}
|
||||||
|
|
||||||
|
receiptLists, _ := resp.List.Items()
|
||||||
|
for i, rc := range receiptLists {
|
||||||
|
receipts[lastBlock+i].Append(rc)
|
||||||
|
}
|
||||||
|
lastBlock += len(receiptLists) - 1
|
||||||
|
|
||||||
|
incomplete = resp.LastBlockIncomplete
|
||||||
}
|
}
|
||||||
if resp.List.Len() != len(req.GetReceiptsRequest) {
|
|
||||||
t.Fatalf("wrong receipts in response: expected %d receipts, got %d", len(req.GetReceiptsRequest), resp.List.Len())
|
hasher := trie.NewStackTrie(nil)
|
||||||
|
hashes := make([]common.Hash, len(receipts))
|
||||||
|
for i := range receipts {
|
||||||
|
hashes[i] = types.DeriveSha(receipts[i].Derivable(), hasher)
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, hash := range hashes {
|
||||||
|
if receiptHashes[i] != hash {
|
||||||
|
t.Fatalf("wrong receipt root: want %x, got %x", receiptHashes[i], hash)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
BIN
cmd/devp2p/internal/ethtest/testdata/chain.rlp
vendored
BIN
cmd/devp2p/internal/ethtest/testdata/chain.rlp
vendored
Binary file not shown.
|
|
@ -37,7 +37,7 @@
|
||||||
"nonce": "0x0",
|
"nonce": "0x0",
|
||||||
"timestamp": "0x0",
|
"timestamp": "0x0",
|
||||||
"extraData": "0x68697665636861696e",
|
"extraData": "0x68697665636861696e",
|
||||||
"gasLimit": "0x23f3e20",
|
"gasLimit": "0x11e1a300",
|
||||||
"difficulty": "0x20000",
|
"difficulty": "0x20000",
|
||||||
"mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
|
"mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
|
||||||
"coinbase": "0x0000000000000000000000000000000000000000",
|
"coinbase": "0x0000000000000000000000000000000000000000",
|
||||||
|
|
@ -119,6 +119,10 @@
|
||||||
"balance": "0x1",
|
"balance": "0x1",
|
||||||
"nonce": "0x1"
|
"nonce": "0x1"
|
||||||
},
|
},
|
||||||
|
"8dcd17433742f4c0ca53122ab541d0ba67fc27ff": {
|
||||||
|
"code": "0x6202e6306000a0",
|
||||||
|
"balance": "0x0"
|
||||||
|
},
|
||||||
"c7b99a164efd027a93f147376cc7da7c67c6bbe0": {
|
"c7b99a164efd027a93f147376cc7da7c67c6bbe0": {
|
||||||
"balance": "0xc097ce7bc90715b34b9f1000000000"
|
"balance": "0xc097ce7bc90715b34b9f1000000000"
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -1,24 +1,24 @@
|
||||||
{
|
{
|
||||||
"parentHash": "0x65151b101682b54cd08ba226f640c14c86176865ff9bfc57e0147dadaeac34bb",
|
"parentHash": "0x7e80093a491eba0e5b2c1895837902f64f514100221801318fe391e1e09c96a6",
|
||||||
"sha3Uncles": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347",
|
"sha3Uncles": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347",
|
||||||
"miner": "0x0000000000000000000000000000000000000000",
|
"miner": "0x0000000000000000000000000000000000000000",
|
||||||
"stateRoot": "0xce423ebc60fc7764a43f09f1fe3ae61eef25e3eb8d09b1108f7e7eb77dfff5e6",
|
"stateRoot": "0x8fcfb02cfca007773bd55bc1c3e50a3c8612a59c87ce057e5957e8bf17c1728b",
|
||||||
"transactionsRoot": "0x7ec1ae3989efa75d7bcc766e5e2443afa8a89a5fda42ebba90050e7e702980f7",
|
"transactionsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
|
||||||
"receiptsRoot": "0xfe160832b1ca85f38c6674cb0aae3a24693bc49be56e2ecdf3698b71a794de86",
|
"receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
|
||||||
"logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
|
"logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
|
||||||
"difficulty": "0x0",
|
"difficulty": "0x0",
|
||||||
"number": "0x258",
|
"number": "0x258",
|
||||||
"gasLimit": "0x23f3e20",
|
"gasLimit": "0x11e1a300",
|
||||||
"gasUsed": "0x19d36",
|
"gasUsed": "0x0",
|
||||||
"timestamp": "0x1770",
|
"timestamp": "0x1770",
|
||||||
"extraData": "0x",
|
"extraData": "0x",
|
||||||
"mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
|
"mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
|
||||||
"nonce": "0x0000000000000000",
|
"nonce": "0x0000000000000000",
|
||||||
"baseFeePerGas": "0x7",
|
"baseFeePerGas": "0x7",
|
||||||
"withdrawalsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
|
"withdrawalsRoot": "0x92abfda39de7df7d705c5a8f30386802ad59d31e782a06d5c5b0f9a260056cf0",
|
||||||
"blobGasUsed": "0x0",
|
"blobGasUsed": "0x0",
|
||||||
"excessBlobGas": "0x0",
|
"excessBlobGas": "0x0",
|
||||||
"parentBeaconBlockRoot": "0xf5003fc8f92358e790a114bce93ce1d9c283c85e1787f8d7d56714d3489b49e6",
|
"parentBeaconBlockRoot": "0xf5003fc8f92358e790a114bce93ce1d9c283c85e1787f8d7d56714d3489b49e6",
|
||||||
"requestsHash": "0xe3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
"requestsHash": "0xe3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
||||||
"hash": "0xce8d86ba17a2ec303155f0e264c58a4b8f94ce3436274cf1924f91acdb7502d0"
|
"hash": "0x44e3809c9a3cda717f00aea3a9da336d149612c8d5657fbc0028176ef8d94d2a"
|
||||||
}
|
}
|
||||||
|
|
@ -4,9 +4,9 @@
|
||||||
"method": "engine_forkchoiceUpdatedV3",
|
"method": "engine_forkchoiceUpdatedV3",
|
||||||
"params": [
|
"params": [
|
||||||
{
|
{
|
||||||
"headBlockHash": "0xce8d86ba17a2ec303155f0e264c58a4b8f94ce3436274cf1924f91acdb7502d0",
|
"headBlockHash": "0x44e3809c9a3cda717f00aea3a9da336d149612c8d5657fbc0028176ef8d94d2a",
|
||||||
"safeBlockHash": "0xce8d86ba17a2ec303155f0e264c58a4b8f94ce3436274cf1924f91acdb7502d0",
|
"safeBlockHash": "0x44e3809c9a3cda717f00aea3a9da336d149612c8d5657fbc0028176ef8d94d2a",
|
||||||
"finalizedBlockHash": "0xce8d86ba17a2ec303155f0e264c58a4b8f94ce3436274cf1924f91acdb7502d0"
|
"finalizedBlockHash": "0x44e3809c9a3cda717f00aea3a9da336d149612c8d5657fbc0028176ef8d94d2a"
|
||||||
},
|
},
|
||||||
null
|
null
|
||||||
]
|
]
|
||||||
|
|
|
||||||
4210
cmd/devp2p/internal/ethtest/testdata/headstate.json
vendored
4210
cmd/devp2p/internal/ethtest/testdata/headstate.json
vendored
File diff suppressed because it is too large
Load diff
10313
cmd/devp2p/internal/ethtest/testdata/newpayload.json
vendored
10313
cmd/devp2p/internal/ethtest/testdata/newpayload.json
vendored
File diff suppressed because it is too large
Load diff
2943
cmd/devp2p/internal/ethtest/testdata/txinfo.json
vendored
2943
cmd/devp2p/internal/ethtest/testdata/txinfo.json
vendored
File diff suppressed because it is too large
Load diff
|
|
@ -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}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -365,10 +365,6 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig,
|
||||||
// Set requestsHash on block.
|
// Set requestsHash on block.
|
||||||
h := types.CalcRequestsHash(requests)
|
h := types.CalcRequestsHash(requests)
|
||||||
execRs.RequestsHash = &h
|
execRs.RequestsHash = &h
|
||||||
for i := range requests {
|
|
||||||
// remove prefix
|
|
||||||
requests[i] = requests[i][1:]
|
|
||||||
}
|
|
||||||
execRs.Requests = requests
|
execRs.Requests = requests
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
408
cmd/geth/bintrie_convert.go
Normal file
408
cmd/geth/bintrie_convert.go
Normal file
|
|
@ -0,0 +1,408 @@
|
||||||
|
// Copyright 2026 The go-ethereum Authors
|
||||||
|
// This file is part of go-ethereum.
|
||||||
|
//
|
||||||
|
// go-ethereum is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// go-ethereum is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"runtime"
|
||||||
|
"runtime/debug"
|
||||||
|
"slices"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/cmd/utils"
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||||
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
|
"github.com/ethereum/go-ethereum/ethdb"
|
||||||
|
"github.com/ethereum/go-ethereum/log"
|
||||||
|
"github.com/ethereum/go-ethereum/rlp"
|
||||||
|
"github.com/ethereum/go-ethereum/trie"
|
||||||
|
"github.com/ethereum/go-ethereum/trie/bintrie"
|
||||||
|
"github.com/ethereum/go-ethereum/trie/trienode"
|
||||||
|
"github.com/ethereum/go-ethereum/triedb"
|
||||||
|
"github.com/ethereum/go-ethereum/triedb/pathdb"
|
||||||
|
"github.com/urfave/cli/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
deleteSourceFlag = &cli.BoolFlag{
|
||||||
|
Name: "delete-source",
|
||||||
|
Usage: "Delete MPT trie nodes after conversion",
|
||||||
|
}
|
||||||
|
memoryLimitFlag = &cli.Uint64Flag{
|
||||||
|
Name: "memory-limit",
|
||||||
|
Usage: "Max heap allocation in MB before forcing a commit cycle",
|
||||||
|
Value: 16384,
|
||||||
|
}
|
||||||
|
|
||||||
|
bintrieCommand = &cli.Command{
|
||||||
|
Name: "bintrie",
|
||||||
|
Usage: "A set of commands for binary trie operations",
|
||||||
|
Description: "",
|
||||||
|
Subcommands: []*cli.Command{
|
||||||
|
{
|
||||||
|
Name: "convert",
|
||||||
|
Usage: "Convert MPT state to binary trie",
|
||||||
|
ArgsUsage: "[state-root]",
|
||||||
|
Action: convertToBinaryTrie,
|
||||||
|
Flags: slices.Concat([]cli.Flag{
|
||||||
|
deleteSourceFlag,
|
||||||
|
memoryLimitFlag,
|
||||||
|
}, utils.NetworkFlags, utils.DatabaseFlags),
|
||||||
|
Description: `
|
||||||
|
geth bintrie convert [--delete-source] [--memory-limit MB] [state-root]
|
||||||
|
|
||||||
|
Reads all state from the Merkle Patricia Trie and writes it into a Binary Trie,
|
||||||
|
operating offline. Memory-safe via periodic commit-and-reload cycles.
|
||||||
|
|
||||||
|
The optional state-root argument specifies which state root to convert.
|
||||||
|
If omitted, the head block's state root is used.
|
||||||
|
|
||||||
|
Flags:
|
||||||
|
--delete-source Delete MPT trie nodes after successful conversion
|
||||||
|
--memory-limit Max heap allocation in MB before forcing a commit (default: 16384)
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
type conversionStats struct {
|
||||||
|
accounts uint64
|
||||||
|
slots uint64
|
||||||
|
codes uint64
|
||||||
|
commits uint64
|
||||||
|
start time.Time
|
||||||
|
lastReport time.Time
|
||||||
|
lastMemChk time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *conversionStats) report(force bool) {
|
||||||
|
if !force && time.Since(s.lastReport) < 8*time.Second {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
elapsed := time.Since(s.start).Seconds()
|
||||||
|
acctRate := float64(0)
|
||||||
|
if elapsed > 0 {
|
||||||
|
acctRate = float64(s.accounts) / elapsed
|
||||||
|
}
|
||||||
|
log.Info("Conversion progress",
|
||||||
|
"accounts", s.accounts,
|
||||||
|
"slots", s.slots,
|
||||||
|
"codes", s.codes,
|
||||||
|
"commits", s.commits,
|
||||||
|
"accounts/sec", fmt.Sprintf("%.0f", acctRate),
|
||||||
|
"elapsed", common.PrettyDuration(time.Since(s.start)),
|
||||||
|
)
|
||||||
|
s.lastReport = time.Now()
|
||||||
|
}
|
||||||
|
|
||||||
|
func convertToBinaryTrie(ctx *cli.Context) error {
|
||||||
|
if ctx.NArg() > 1 {
|
||||||
|
return errors.New("too many arguments")
|
||||||
|
}
|
||||||
|
stack, _ := makeConfigNode(ctx)
|
||||||
|
defer stack.Close()
|
||||||
|
|
||||||
|
chaindb := utils.MakeChainDatabase(ctx, stack, false)
|
||||||
|
defer chaindb.Close()
|
||||||
|
|
||||||
|
headBlock := rawdb.ReadHeadBlock(chaindb)
|
||||||
|
if headBlock == nil {
|
||||||
|
return errors.New("no head block found")
|
||||||
|
}
|
||||||
|
var (
|
||||||
|
root common.Hash
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
if ctx.NArg() == 1 {
|
||||||
|
root, err = parseRoot(ctx.Args().First())
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("invalid state root: %w", err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
root = headBlock.Root()
|
||||||
|
}
|
||||||
|
log.Info("Starting MPT to binary trie conversion", "root", root, "block", headBlock.NumberU64())
|
||||||
|
|
||||||
|
srcTriedb := utils.MakeTrieDatabase(ctx, stack, chaindb, true, true, false)
|
||||||
|
defer srcTriedb.Close()
|
||||||
|
|
||||||
|
destTriedb := triedb.NewDatabase(chaindb, &triedb.Config{
|
||||||
|
IsVerkle: true,
|
||||||
|
PathDB: &pathdb.Config{
|
||||||
|
JournalDirectory: stack.ResolvePath("triedb-bintrie"),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
defer destTriedb.Close()
|
||||||
|
|
||||||
|
binTrie, err := bintrie.NewBinaryTrie(types.EmptyBinaryHash, destTriedb)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to create binary trie: %w", err)
|
||||||
|
}
|
||||||
|
memLimit := ctx.Uint64(memoryLimitFlag.Name) * 1024 * 1024
|
||||||
|
|
||||||
|
currentRoot, err := runConversionLoop(chaindb, srcTriedb, destTriedb, binTrie, root, memLimit)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
log.Info("Conversion complete", "binaryRoot", currentRoot)
|
||||||
|
|
||||||
|
if ctx.Bool(deleteSourceFlag.Name) {
|
||||||
|
log.Info("Deleting source MPT data")
|
||||||
|
if err := deleteMPTData(chaindb, srcTriedb, root); err != nil {
|
||||||
|
return fmt.Errorf("MPT deletion failed: %w", err)
|
||||||
|
}
|
||||||
|
log.Info("Source MPT data deleted")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func runConversionLoop(chaindb ethdb.Database, srcTriedb *triedb.Database, destTriedb *triedb.Database, binTrie *bintrie.BinaryTrie, root common.Hash, memLimit uint64) (common.Hash, error) {
|
||||||
|
currentRoot := types.EmptyBinaryHash
|
||||||
|
stats := &conversionStats{
|
||||||
|
start: time.Now(),
|
||||||
|
lastReport: time.Now(),
|
||||||
|
lastMemChk: time.Now(),
|
||||||
|
}
|
||||||
|
|
||||||
|
srcTrie, err := trie.NewStateTrie(trie.StateTrieID(root), srcTriedb)
|
||||||
|
if err != nil {
|
||||||
|
return common.Hash{}, fmt.Errorf("failed to open source trie: %w", err)
|
||||||
|
}
|
||||||
|
acctIt, err := srcTrie.NodeIterator(nil)
|
||||||
|
if err != nil {
|
||||||
|
return common.Hash{}, fmt.Errorf("failed to create account iterator: %w", err)
|
||||||
|
}
|
||||||
|
accIter := trie.NewIterator(acctIt)
|
||||||
|
|
||||||
|
for accIter.Next() {
|
||||||
|
var acc types.StateAccount
|
||||||
|
if err := rlp.DecodeBytes(accIter.Value, &acc); err != nil {
|
||||||
|
return common.Hash{}, fmt.Errorf("invalid account RLP: %w", err)
|
||||||
|
}
|
||||||
|
addrBytes := srcTrie.GetKey(accIter.Key)
|
||||||
|
if addrBytes == nil {
|
||||||
|
return common.Hash{}, fmt.Errorf("missing preimage for account hash %x (run with --cache.preimages)", accIter.Key)
|
||||||
|
}
|
||||||
|
addr := common.BytesToAddress(addrBytes)
|
||||||
|
|
||||||
|
var code []byte
|
||||||
|
codeHash := common.BytesToHash(acc.CodeHash)
|
||||||
|
if codeHash != types.EmptyCodeHash {
|
||||||
|
code = rawdb.ReadCode(chaindb, codeHash)
|
||||||
|
if code == nil {
|
||||||
|
return common.Hash{}, fmt.Errorf("missing code for hash %x (account %x)", codeHash, addr)
|
||||||
|
}
|
||||||
|
stats.codes++
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := binTrie.UpdateAccount(addr, &acc, len(code)); err != nil {
|
||||||
|
return common.Hash{}, fmt.Errorf("failed to update account %x: %w", addr, err)
|
||||||
|
}
|
||||||
|
if len(code) > 0 {
|
||||||
|
if err := binTrie.UpdateContractCode(addr, codeHash, code); err != nil {
|
||||||
|
return common.Hash{}, fmt.Errorf("failed to update code for %x: %w", addr, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if acc.Root != types.EmptyRootHash {
|
||||||
|
addrHash := common.BytesToHash(accIter.Key)
|
||||||
|
storageTrie, err := trie.NewStateTrie(trie.StorageTrieID(root, addrHash, acc.Root), srcTriedb)
|
||||||
|
if err != nil {
|
||||||
|
return common.Hash{}, fmt.Errorf("failed to open storage trie for %x: %w", addr, err)
|
||||||
|
}
|
||||||
|
storageNodeIt, err := storageTrie.NodeIterator(nil)
|
||||||
|
if err != nil {
|
||||||
|
return common.Hash{}, fmt.Errorf("failed to create storage iterator for %x: %w", addr, err)
|
||||||
|
}
|
||||||
|
storageIter := trie.NewIterator(storageNodeIt)
|
||||||
|
|
||||||
|
slotCount := uint64(0)
|
||||||
|
for storageIter.Next() {
|
||||||
|
slotKey := storageTrie.GetKey(storageIter.Key)
|
||||||
|
if slotKey == nil {
|
||||||
|
return common.Hash{}, fmt.Errorf("missing preimage for storage key %x (account %x)", storageIter.Key, addr)
|
||||||
|
}
|
||||||
|
_, content, _, err := rlp.Split(storageIter.Value)
|
||||||
|
if err != nil {
|
||||||
|
return common.Hash{}, fmt.Errorf("invalid storage RLP for key %x (account %x): %w", slotKey, addr, err)
|
||||||
|
}
|
||||||
|
if err := binTrie.UpdateStorage(addr, slotKey, content); err != nil {
|
||||||
|
return common.Hash{}, fmt.Errorf("failed to update storage %x/%x: %w", addr, slotKey, err)
|
||||||
|
}
|
||||||
|
stats.slots++
|
||||||
|
slotCount++
|
||||||
|
|
||||||
|
if slotCount%10000 == 0 {
|
||||||
|
binTrie, currentRoot, err = maybeCommit(binTrie, currentRoot, destTriedb, memLimit, stats)
|
||||||
|
if err != nil {
|
||||||
|
return common.Hash{}, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if storageIter.Err != nil {
|
||||||
|
return common.Hash{}, fmt.Errorf("storage iteration error for %x: %w", addr, storageIter.Err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stats.accounts++
|
||||||
|
stats.report(false)
|
||||||
|
|
||||||
|
if stats.accounts%1000 == 0 {
|
||||||
|
binTrie, currentRoot, err = maybeCommit(binTrie, currentRoot, destTriedb, memLimit, stats)
|
||||||
|
if err != nil {
|
||||||
|
return common.Hash{}, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if accIter.Err != nil {
|
||||||
|
return common.Hash{}, fmt.Errorf("account iteration error: %w", accIter.Err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, currentRoot, err = commitBinaryTrie(binTrie, currentRoot, destTriedb)
|
||||||
|
if err != nil {
|
||||||
|
return common.Hash{}, fmt.Errorf("final commit failed: %w", err)
|
||||||
|
}
|
||||||
|
stats.commits++
|
||||||
|
stats.report(true)
|
||||||
|
return currentRoot, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func maybeCommit(bt *bintrie.BinaryTrie, currentRoot common.Hash, destDB *triedb.Database, memLimit uint64, stats *conversionStats) (*bintrie.BinaryTrie, common.Hash, error) {
|
||||||
|
if time.Since(stats.lastMemChk) < 5*time.Second {
|
||||||
|
return bt, currentRoot, nil
|
||||||
|
}
|
||||||
|
stats.lastMemChk = time.Now()
|
||||||
|
|
||||||
|
var m runtime.MemStats
|
||||||
|
runtime.ReadMemStats(&m)
|
||||||
|
if m.Alloc < memLimit {
|
||||||
|
return bt, currentRoot, nil
|
||||||
|
}
|
||||||
|
log.Info("Memory limit reached, committing", "alloc", common.StorageSize(m.Alloc), "limit", common.StorageSize(memLimit))
|
||||||
|
|
||||||
|
bt, currentRoot, err := commitBinaryTrie(bt, currentRoot, destDB)
|
||||||
|
if err != nil {
|
||||||
|
return nil, common.Hash{}, err
|
||||||
|
}
|
||||||
|
stats.commits++
|
||||||
|
stats.report(true)
|
||||||
|
return bt, currentRoot, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func commitBinaryTrie(bt *bintrie.BinaryTrie, currentRoot common.Hash, destDB *triedb.Database) (*bintrie.BinaryTrie, common.Hash, error) {
|
||||||
|
newRoot, nodeSet := bt.Commit(false)
|
||||||
|
if nodeSet != nil {
|
||||||
|
merged := trienode.NewWithNodeSet(nodeSet)
|
||||||
|
if err := destDB.Update(newRoot, currentRoot, 0, merged, triedb.NewStateSet()); err != nil {
|
||||||
|
return nil, common.Hash{}, fmt.Errorf("triedb update failed: %w", err)
|
||||||
|
}
|
||||||
|
if err := destDB.Commit(newRoot, false); err != nil {
|
||||||
|
return nil, common.Hash{}, fmt.Errorf("triedb commit failed: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
runtime.GC()
|
||||||
|
debug.FreeOSMemory()
|
||||||
|
|
||||||
|
bt, err := bintrie.NewBinaryTrie(newRoot, destDB)
|
||||||
|
if err != nil {
|
||||||
|
return nil, common.Hash{}, fmt.Errorf("failed to reload binary trie: %w", err)
|
||||||
|
}
|
||||||
|
return bt, newRoot, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func deleteMPTData(chaindb ethdb.Database, srcTriedb *triedb.Database, root common.Hash) error {
|
||||||
|
isPathDB := srcTriedb.Scheme() == rawdb.PathScheme
|
||||||
|
|
||||||
|
srcTrie, err := trie.NewStateTrie(trie.StateTrieID(root), srcTriedb)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to open source trie for deletion: %w", err)
|
||||||
|
}
|
||||||
|
acctIt, err := srcTrie.NodeIterator(nil)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to create account iterator for deletion: %w", err)
|
||||||
|
}
|
||||||
|
batch := chaindb.NewBatch()
|
||||||
|
deleted := 0
|
||||||
|
|
||||||
|
for acctIt.Next(true) {
|
||||||
|
if isPathDB {
|
||||||
|
rawdb.DeleteAccountTrieNode(batch, acctIt.Path())
|
||||||
|
} else {
|
||||||
|
node := acctIt.Hash()
|
||||||
|
if node != (common.Hash{}) {
|
||||||
|
rawdb.DeleteLegacyTrieNode(batch, node)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
deleted++
|
||||||
|
|
||||||
|
if acctIt.Leaf() {
|
||||||
|
var acc types.StateAccount
|
||||||
|
if err := rlp.DecodeBytes(acctIt.LeafBlob(), &acc); err != nil {
|
||||||
|
return fmt.Errorf("invalid account during deletion: %w", err)
|
||||||
|
}
|
||||||
|
if acc.Root != types.EmptyRootHash {
|
||||||
|
addrHash := common.BytesToHash(acctIt.LeafKey())
|
||||||
|
storageTrie, err := trie.NewStateTrie(trie.StorageTrieID(root, addrHash, acc.Root), srcTriedb)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to open storage trie for deletion: %w", err)
|
||||||
|
}
|
||||||
|
storageIt, err := storageTrie.NodeIterator(nil)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to create storage iterator for deletion: %w", err)
|
||||||
|
}
|
||||||
|
for storageIt.Next(true) {
|
||||||
|
if isPathDB {
|
||||||
|
rawdb.DeleteStorageTrieNode(batch, addrHash, storageIt.Path())
|
||||||
|
} else {
|
||||||
|
node := storageIt.Hash()
|
||||||
|
if node != (common.Hash{}) {
|
||||||
|
rawdb.DeleteLegacyTrieNode(batch, node)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
deleted++
|
||||||
|
if batch.ValueSize() >= ethdb.IdealBatchSize {
|
||||||
|
if err := batch.Write(); err != nil {
|
||||||
|
return fmt.Errorf("batch write failed: %w", err)
|
||||||
|
}
|
||||||
|
batch.Reset()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if storageIt.Error() != nil {
|
||||||
|
return fmt.Errorf("storage deletion iterator error: %w", storageIt.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if batch.ValueSize() >= ethdb.IdealBatchSize {
|
||||||
|
if err := batch.Write(); err != nil {
|
||||||
|
return fmt.Errorf("batch write failed: %w", err)
|
||||||
|
}
|
||||||
|
batch.Reset()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if acctIt.Error() != nil {
|
||||||
|
return fmt.Errorf("account deletion iterator error: %w", acctIt.Error())
|
||||||
|
}
|
||||||
|
if batch.ValueSize() > 0 {
|
||||||
|
if err := batch.Write(); err != nil {
|
||||||
|
return fmt.Errorf("final batch write failed: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
log.Info("MPT deletion complete", "nodesDeleted", deleted)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
229
cmd/geth/bintrie_convert_test.go
Normal file
229
cmd/geth/bintrie_convert_test.go
Normal file
|
|
@ -0,0 +1,229 @@
|
||||||
|
// Copyright 2026 The go-ethereum Authors
|
||||||
|
// This file is part of go-ethereum.
|
||||||
|
//
|
||||||
|
// go-ethereum is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// go-ethereum is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math"
|
||||||
|
"math/big"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/ethereum/go-ethereum/core"
|
||||||
|
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||||
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
|
"github.com/ethereum/go-ethereum/params"
|
||||||
|
"github.com/ethereum/go-ethereum/trie/bintrie"
|
||||||
|
"github.com/ethereum/go-ethereum/triedb"
|
||||||
|
"github.com/ethereum/go-ethereum/triedb/pathdb"
|
||||||
|
"github.com/holiman/uint256"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestBintrieConvert(t *testing.T) {
|
||||||
|
var (
|
||||||
|
addr1 = common.HexToAddress("0x1111111111111111111111111111111111111111")
|
||||||
|
addr2 = common.HexToAddress("0x2222222222222222222222222222222222222222")
|
||||||
|
slotKey1 = common.HexToHash("0x01")
|
||||||
|
slotKey2 = common.HexToHash("0x02")
|
||||||
|
slotVal1 = common.HexToHash("0xdeadbeef")
|
||||||
|
slotVal2 = common.HexToHash("0xcafebabe")
|
||||||
|
code = []byte{0x60, 0x42, 0x60, 0x00, 0x52, 0x60, 0x20, 0x60, 0x00, 0xf3}
|
||||||
|
)
|
||||||
|
|
||||||
|
chaindb := rawdb.NewMemoryDatabase()
|
||||||
|
|
||||||
|
srcTriedb := triedb.NewDatabase(chaindb, &triedb.Config{
|
||||||
|
Preimages: true,
|
||||||
|
PathDB: pathdb.Defaults,
|
||||||
|
})
|
||||||
|
|
||||||
|
gspec := &core.Genesis{
|
||||||
|
Config: params.TestChainConfig,
|
||||||
|
BaseFee: big.NewInt(params.InitialBaseFee),
|
||||||
|
Alloc: types.GenesisAlloc{
|
||||||
|
addr1: {
|
||||||
|
Balance: big.NewInt(1000000),
|
||||||
|
Nonce: 5,
|
||||||
|
},
|
||||||
|
addr2: {
|
||||||
|
Balance: big.NewInt(2000000),
|
||||||
|
Nonce: 10,
|
||||||
|
Code: code,
|
||||||
|
Storage: map[common.Hash]common.Hash{
|
||||||
|
slotKey1: slotVal1,
|
||||||
|
slotKey2: slotVal2,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
genesisBlock := gspec.MustCommit(chaindb, srcTriedb)
|
||||||
|
root := genesisBlock.Root()
|
||||||
|
t.Logf("Genesis root: %x", root)
|
||||||
|
srcTriedb.Close()
|
||||||
|
|
||||||
|
srcTriedb2 := triedb.NewDatabase(chaindb, &triedb.Config{
|
||||||
|
Preimages: true,
|
||||||
|
PathDB: &pathdb.Config{ReadOnly: true},
|
||||||
|
})
|
||||||
|
defer srcTriedb2.Close()
|
||||||
|
|
||||||
|
destTriedb := triedb.NewDatabase(chaindb, &triedb.Config{
|
||||||
|
IsVerkle: true,
|
||||||
|
PathDB: pathdb.Defaults,
|
||||||
|
})
|
||||||
|
defer destTriedb.Close()
|
||||||
|
|
||||||
|
bt, err := bintrie.NewBinaryTrie(types.EmptyBinaryHash, destTriedb)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to create binary trie: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
currentRoot, err := runConversionLoop(chaindb, srcTriedb2, destTriedb, bt, root, math.MaxUint64)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("conversion failed: %v", err)
|
||||||
|
}
|
||||||
|
t.Logf("Binary trie root: %x", currentRoot)
|
||||||
|
|
||||||
|
bt2, err := bintrie.NewBinaryTrie(currentRoot, destTriedb)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to reload binary trie: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
acc1, err := bt2.GetAccount(addr1)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to get account1: %v", err)
|
||||||
|
}
|
||||||
|
if acc1 == nil {
|
||||||
|
t.Fatal("account1 not found in binary trie")
|
||||||
|
}
|
||||||
|
if acc1.Nonce != 5 {
|
||||||
|
t.Errorf("account1 nonce: got %d, want 5", acc1.Nonce)
|
||||||
|
}
|
||||||
|
wantBal1 := uint256.NewInt(1000000)
|
||||||
|
if acc1.Balance.Cmp(wantBal1) != 0 {
|
||||||
|
t.Errorf("account1 balance: got %s, want %s", acc1.Balance, wantBal1)
|
||||||
|
}
|
||||||
|
|
||||||
|
acc2, err := bt2.GetAccount(addr2)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to get account2: %v", err)
|
||||||
|
}
|
||||||
|
if acc2 == nil {
|
||||||
|
t.Fatal("account2 not found in binary trie")
|
||||||
|
}
|
||||||
|
if acc2.Nonce != 10 {
|
||||||
|
t.Errorf("account2 nonce: got %d, want 10", acc2.Nonce)
|
||||||
|
}
|
||||||
|
wantBal2 := uint256.NewInt(2000000)
|
||||||
|
if acc2.Balance.Cmp(wantBal2) != 0 {
|
||||||
|
t.Errorf("account2 balance: got %s, want %s", acc2.Balance, wantBal2)
|
||||||
|
}
|
||||||
|
|
||||||
|
treeKey1 := bintrie.GetBinaryTreeKeyStorageSlot(addr2, slotKey1[:])
|
||||||
|
val1, err := bt2.GetWithHashedKey(treeKey1)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to get storage slot1: %v", err)
|
||||||
|
}
|
||||||
|
if len(val1) == 0 {
|
||||||
|
t.Fatal("storage slot1 not found")
|
||||||
|
}
|
||||||
|
got1 := common.BytesToHash(val1)
|
||||||
|
if got1 != slotVal1 {
|
||||||
|
t.Errorf("storage slot1: got %x, want %x", got1, slotVal1)
|
||||||
|
}
|
||||||
|
|
||||||
|
treeKey2 := bintrie.GetBinaryTreeKeyStorageSlot(addr2, slotKey2[:])
|
||||||
|
val2, err := bt2.GetWithHashedKey(treeKey2)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to get storage slot2: %v", err)
|
||||||
|
}
|
||||||
|
if len(val2) == 0 {
|
||||||
|
t.Fatal("storage slot2 not found")
|
||||||
|
}
|
||||||
|
got2 := common.BytesToHash(val2)
|
||||||
|
if got2 != slotVal2 {
|
||||||
|
t.Errorf("storage slot2: got %x, want %x", got2, slotVal2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBintrieConvertDeleteSource(t *testing.T) {
|
||||||
|
addr1 := common.HexToAddress("0x3333333333333333333333333333333333333333")
|
||||||
|
|
||||||
|
chaindb := rawdb.NewMemoryDatabase()
|
||||||
|
|
||||||
|
srcTriedb := triedb.NewDatabase(chaindb, &triedb.Config{
|
||||||
|
Preimages: true,
|
||||||
|
PathDB: pathdb.Defaults,
|
||||||
|
})
|
||||||
|
|
||||||
|
gspec := &core.Genesis{
|
||||||
|
Config: params.TestChainConfig,
|
||||||
|
BaseFee: big.NewInt(params.InitialBaseFee),
|
||||||
|
Alloc: types.GenesisAlloc{
|
||||||
|
addr1: {
|
||||||
|
Balance: big.NewInt(1000000),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
genesisBlock := gspec.MustCommit(chaindb, srcTriedb)
|
||||||
|
root := genesisBlock.Root()
|
||||||
|
srcTriedb.Close()
|
||||||
|
|
||||||
|
srcTriedb2 := triedb.NewDatabase(chaindb, &triedb.Config{
|
||||||
|
Preimages: true,
|
||||||
|
PathDB: &pathdb.Config{ReadOnly: true},
|
||||||
|
})
|
||||||
|
|
||||||
|
destTriedb := triedb.NewDatabase(chaindb, &triedb.Config{
|
||||||
|
IsVerkle: true,
|
||||||
|
PathDB: pathdb.Defaults,
|
||||||
|
})
|
||||||
|
|
||||||
|
bt, err := bintrie.NewBinaryTrie(types.EmptyBinaryHash, destTriedb)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to create binary trie: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
newRoot, err := runConversionLoop(chaindb, srcTriedb2, destTriedb, bt, root, math.MaxUint64)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("conversion failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := deleteMPTData(chaindb, srcTriedb2, root); err != nil {
|
||||||
|
t.Fatalf("deletion failed: %v", err)
|
||||||
|
}
|
||||||
|
srcTriedb2.Close()
|
||||||
|
|
||||||
|
bt2, err := bintrie.NewBinaryTrie(newRoot, destTriedb)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to reload binary trie after deletion: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
acc, err := bt2.GetAccount(addr1)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to get account after deletion: %v", err)
|
||||||
|
}
|
||||||
|
if acc == nil {
|
||||||
|
t.Fatal("account not found after MPT deletion")
|
||||||
|
}
|
||||||
|
wantBal := uint256.NewInt(1000000)
|
||||||
|
if acc.Balance.Cmp(wantBal) != 0 {
|
||||||
|
t.Errorf("balance after deletion: got %s, want %s", acc.Balance, wantBal)
|
||||||
|
}
|
||||||
|
destTriedb.Close()
|
||||||
|
}
|
||||||
|
|
@ -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.
|
||||||
|
|
|
||||||
|
|
@ -260,6 +260,8 @@ func init() {
|
||||||
utils.ShowDeprecated,
|
utils.ShowDeprecated,
|
||||||
// See snapshot.go
|
// See snapshot.go
|
||||||
snapshotCommand,
|
snapshotCommand,
|
||||||
|
// See bintrie_convert.go
|
||||||
|
bintrieCommand,
|
||||||
}
|
}
|
||||||
if logTestCommand != nil {
|
if logTestCommand != nil {
|
||||||
app.Commands = append(app.Commands, logTestCommand)
|
app.Commands = append(app.Commands, logTestCommand)
|
||||||
|
|
|
||||||
|
|
@ -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)) {
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@
|
||||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
//go:build example
|
//go:build example
|
||||||
|
// +build example
|
||||||
|
|
||||||
package main
|
package main
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -14,8 +14,8 @@
|
||||||
// You should have received a copy of the GNU Lesser General Public License
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
//go:build wasm
|
//go:build wasm && !womir
|
||||||
// +build wasm
|
// +build wasm,!womir
|
||||||
|
|
||||||
package main
|
package main
|
||||||
|
|
||||||
|
|
|
||||||
49
cmd/keeper/getpayload_womir.go
Normal file
49
cmd/keeper/getpayload_womir.go
Normal file
|
|
@ -0,0 +1,49 @@
|
||||||
|
// 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/>.
|
||||||
|
|
||||||
|
//go:build womir
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import "unsafe"
|
||||||
|
|
||||||
|
// These match the WOMIR guest-io imports (env module).
|
||||||
|
// Protocol: __hint_input prepares next item, __hint_buffer reads words.
|
||||||
|
// Each item has format: [byte_len_u32_le, ...data_words_padded_to_4bytes]
|
||||||
|
//
|
||||||
|
//go:wasmimport env __hint_input
|
||||||
|
func hintInput()
|
||||||
|
|
||||||
|
//go:wasmimport env __hint_buffer
|
||||||
|
func hintBuffer(ptr unsafe.Pointer, numWords uint32)
|
||||||
|
func readWord() uint32 {
|
||||||
|
var buf [4]byte
|
||||||
|
hintBuffer(unsafe.Pointer(&buf[0]), 1)
|
||||||
|
return uint32(buf[0]) | uint32(buf[1])<<8 | uint32(buf[2])<<16 | uint32(buf[3])<<24
|
||||||
|
}
|
||||||
|
func readBytes() []byte {
|
||||||
|
hintInput()
|
||||||
|
byteLen := readWord()
|
||||||
|
numWords := (byteLen + 3) / 4
|
||||||
|
data := make([]byte, numWords*4)
|
||||||
|
hintBuffer(unsafe.Pointer(&data[0]), numWords)
|
||||||
|
return data[:byteLen]
|
||||||
|
}
|
||||||
|
|
||||||
|
// getInput reads the RLP-encoded Payload from the WOMIR hint stream.
|
||||||
|
func getInput() []byte {
|
||||||
|
return readBytes()
|
||||||
|
}
|
||||||
|
|
@ -14,8 +14,8 @@
|
||||||
// You should have received a copy of the GNU Lesser General Public License
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
//go:build !example && !ziren && !wasm
|
//go:build !example && !ziren && !wasm && !womir
|
||||||
// +build !example,!ziren,!wasm
|
// +build !example,!ziren,!wasm,!womir
|
||||||
|
|
||||||
package main
|
package main
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -274,40 +274,66 @@ func ImportHistory(chain *core.BlockChain, dir string, network string, from func
|
||||||
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)
|
||||||
|
|
||||||
// validate against checksum file in directory
|
// Validate against checksum file in directory.
|
||||||
f, err := os.Open(path)
|
f, err := os.Open(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("open %s: %w", path, err)
|
return fmt.Errorf("open %s: %w", path, err)
|
||||||
}
|
}
|
||||||
defer f.Close()
|
defer f.Close()
|
||||||
|
|
||||||
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 != checksums[i] {
|
||||||
if got != want {
|
return fmt.Errorf("%s checksum mismatch: have %s want %s", file, got, checksums[i])
|
||||||
return fmt.Errorf("%s checksum mismatch: have %s want %s", file, got, want)
|
|
||||||
}
|
}
|
||||||
// Import all block data from Era1.
|
// Import all block data from Era1.
|
||||||
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)
|
||||||
}
|
}
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
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 {
|
||||||
|
|
@ -320,23 +346,18 @@ func ImportHistory(chain *core.BlockChain, dir string, network string, from func
|
||||||
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 {
|
||||||
imported++
|
return err
|
||||||
|
}
|
||||||
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)
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@
|
||||||
package beacon
|
package beacon
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/big"
|
"math/big"
|
||||||
|
|
@ -29,6 +30,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/internal/telemetry"
|
||||||
"github.com/ethereum/go-ethereum/params"
|
"github.com/ethereum/go-ethereum/params"
|
||||||
"github.com/ethereum/go-ethereum/trie"
|
"github.com/ethereum/go-ethereum/trie"
|
||||||
"github.com/holiman/uint256"
|
"github.com/holiman/uint256"
|
||||||
|
|
@ -273,12 +275,22 @@ func (beacon *Beacon) verifyHeader(chain consensus.ChainHeaderReader, header, pa
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Verify the existence / non-existence of Amsterdam-specific header fields
|
||||||
amsterdam := chain.Config().IsAmsterdam(header.Number, header.Time)
|
amsterdam := chain.Config().IsAmsterdam(header.Number, header.Time)
|
||||||
if amsterdam && header.SlotNumber == nil {
|
if amsterdam {
|
||||||
return errors.New("header is missing slotNumber")
|
if header.BlockAccessListHash == nil {
|
||||||
}
|
return errors.New("header is missing block access list hash")
|
||||||
if !amsterdam && header.SlotNumber != nil {
|
}
|
||||||
return fmt.Errorf("invalid slotNumber: have %d, expected nil", *header.SlotNumber)
|
if header.SlotNumber == nil {
|
||||||
|
return errors.New("header is missing slotNumber")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if header.BlockAccessListHash != nil {
|
||||||
|
return fmt.Errorf("invalid block access list hash: have %x, expected nil", *header.BlockAccessListHash)
|
||||||
|
}
|
||||||
|
if header.SlotNumber != nil {
|
||||||
|
return fmt.Errorf("invalid slotNumber: have %d, expected nil", *header.SlotNumber)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
@ -351,9 +363,17 @@ func (beacon *Beacon) Finalize(chain consensus.ChainHeaderReader, header *types.
|
||||||
|
|
||||||
// FinalizeAndAssemble implements consensus.Engine, setting the final state and
|
// FinalizeAndAssemble implements consensus.Engine, setting the final state and
|
||||||
// assembling the block.
|
// assembling the block.
|
||||||
func (beacon *Beacon) FinalizeAndAssemble(chain consensus.ChainHeaderReader, header *types.Header, state *state.StateDB, body *types.Body, receipts []*types.Receipt) (*types.Block, error) {
|
func (beacon *Beacon) FinalizeAndAssemble(ctx context.Context, chain consensus.ChainHeaderReader, header *types.Header, state *state.StateDB, body *types.Body, receipts []*types.Receipt) (result *types.Block, err error) {
|
||||||
|
ctx, _, spanEnd := telemetry.StartSpan(ctx, "consensus.beacon.FinalizeAndAssemble",
|
||||||
|
telemetry.Int64Attribute("block.number", int64(header.Number.Uint64())),
|
||||||
|
telemetry.Int64Attribute("txs.count", int64(len(body.Transactions))),
|
||||||
|
telemetry.Int64Attribute("withdrawals.count", int64(len(body.Withdrawals))),
|
||||||
|
)
|
||||||
|
defer spanEnd(&err)
|
||||||
|
|
||||||
if !beacon.IsPoSHeader(header) {
|
if !beacon.IsPoSHeader(header) {
|
||||||
return beacon.ethone.FinalizeAndAssemble(chain, header, state, body, receipts)
|
block, delegateErr := beacon.ethone.FinalizeAndAssemble(ctx, chain, header, state, body, receipts)
|
||||||
|
return block, delegateErr
|
||||||
}
|
}
|
||||||
shanghai := chain.Config().IsShanghai(header.Number, header.Time)
|
shanghai := chain.Config().IsShanghai(header.Number, header.Time)
|
||||||
if shanghai {
|
if shanghai {
|
||||||
|
|
@ -367,13 +387,20 @@ func (beacon *Beacon) FinalizeAndAssemble(chain consensus.ChainHeaderReader, hea
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Finalize and assemble the block.
|
// Finalize and assemble the block.
|
||||||
|
_, _, finalizeSpanEnd := telemetry.StartSpan(ctx, "consensus.beacon.Finalize")
|
||||||
beacon.Finalize(chain, header, state, body)
|
beacon.Finalize(chain, header, state, body)
|
||||||
|
finalizeSpanEnd(nil)
|
||||||
|
|
||||||
// Assign the final state root to header.
|
// Assign the final state root to header.
|
||||||
|
_, _, rootSpanEnd := telemetry.StartSpan(ctx, "consensus.beacon.IntermediateRoot")
|
||||||
header.Root = state.IntermediateRoot(true)
|
header.Root = state.IntermediateRoot(true)
|
||||||
|
rootSpanEnd(nil)
|
||||||
|
|
||||||
// Assemble the final block.
|
// Assemble the final block.
|
||||||
return types.NewBlock(header, body, receipts, trie.NewStackTrie(nil)), nil
|
_, _, blockSpanEnd := telemetry.StartSpan(ctx, "consensus.beacon.NewBlock")
|
||||||
|
block := types.NewBlock(header, body, receipts, trie.NewStackTrie(nil))
|
||||||
|
blockSpanEnd(nil)
|
||||||
|
return block, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Seal generates a new sealing request for the given input block and pushes
|
// Seal generates a new sealing request for the given input block and pushes
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ package clique
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
|
@ -581,7 +582,7 @@ func (c *Clique) Finalize(chain consensus.ChainHeaderReader, header *types.Heade
|
||||||
|
|
||||||
// FinalizeAndAssemble implements consensus.Engine, ensuring no uncles are set,
|
// FinalizeAndAssemble implements consensus.Engine, ensuring no uncles are set,
|
||||||
// nor block rewards given, and returns the final block.
|
// nor block rewards given, and returns the final block.
|
||||||
func (c *Clique) FinalizeAndAssemble(chain consensus.ChainHeaderReader, header *types.Header, state *state.StateDB, body *types.Body, receipts []*types.Receipt) (*types.Block, error) {
|
func (c *Clique) FinalizeAndAssemble(ctx context.Context, chain consensus.ChainHeaderReader, header *types.Header, state *state.StateDB, body *types.Body, receipts []*types.Receipt) (*types.Block, error) {
|
||||||
if len(body.Withdrawals) > 0 {
|
if len(body.Withdrawals) > 0 {
|
||||||
return nil, errors.New("clique does not support withdrawals")
|
return nil, errors.New("clique does not support withdrawals")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@
|
||||||
package consensus
|
package consensus
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"math/big"
|
"math/big"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
|
@ -92,7 +93,7 @@ type Engine interface {
|
||||||
//
|
//
|
||||||
// Note: The block header and state database might be updated to reflect any
|
// Note: The block header and state database might be updated to reflect any
|
||||||
// consensus rules that happen at finalization (e.g. block rewards).
|
// consensus rules that happen at finalization (e.g. block rewards).
|
||||||
FinalizeAndAssemble(chain ChainHeaderReader, header *types.Header, state *state.StateDB, body *types.Body, receipts []*types.Receipt) (*types.Block, error)
|
FinalizeAndAssemble(ctx context.Context, chain ChainHeaderReader, header *types.Header, state *state.StateDB, body *types.Body, receipts []*types.Receipt) (*types.Block, error)
|
||||||
|
|
||||||
// Seal generates a new sealing request for the given input block and pushes
|
// Seal generates a new sealing request for the given input block and pushes
|
||||||
// the result into the given channel.
|
// the result into the given channel.
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@
|
||||||
package ethash
|
package ethash
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/big"
|
"math/big"
|
||||||
|
|
@ -513,7 +514,7 @@ func (ethash *Ethash) Finalize(chain consensus.ChainHeaderReader, header *types.
|
||||||
|
|
||||||
// FinalizeAndAssemble implements consensus.Engine, accumulating the block and
|
// FinalizeAndAssemble implements consensus.Engine, accumulating the block and
|
||||||
// uncle rewards, setting the final state and assembling the block.
|
// uncle rewards, setting the final state and assembling the block.
|
||||||
func (ethash *Ethash) FinalizeAndAssemble(chain consensus.ChainHeaderReader, header *types.Header, state *state.StateDB, body *types.Body, receipts []*types.Receipt) (*types.Block, error) {
|
func (ethash *Ethash) FinalizeAndAssemble(ctx context.Context, chain consensus.ChainHeaderReader, header *types.Header, state *state.StateDB, body *types.Body, receipts []*types.Receipt) (*types.Block, error) {
|
||||||
if len(body.Withdrawals) > 0 {
|
if len(body.Withdrawals) > 0 {
|
||||||
return nil, errors.New("ethash does not support withdrawals")
|
return nil, errors.New("ethash does not support withdrawals")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -282,7 +282,7 @@ func (c *Console) AutoCompleteInput(line string, pos int) (string, []string, str
|
||||||
for ; start > 0; start-- {
|
for ; start > 0; start-- {
|
||||||
// Skip all methods and namespaces (i.e. including the dot)
|
// Skip all methods and namespaces (i.e. including the dot)
|
||||||
c := line[start]
|
c := line[start]
|
||||||
if c == '.' || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '1' && c <= '9') {
|
if c == '.' || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
// We've hit an unexpected character, autocomplete form here
|
// We've hit an unexpected character, autocomplete form here
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
@ -2628,6 +2583,7 @@ func (bc *BlockChain) reorg(oldHead *types.Header, newHead *types.Header) error
|
||||||
// as the txlookups should be changed atomically, and all subsequent
|
// as the txlookups should be changed atomically, and all subsequent
|
||||||
// reads should be blocked until the mutation is complete.
|
// reads should be blocked until the mutation is complete.
|
||||||
bc.txLookupLock.Lock()
|
bc.txLookupLock.Lock()
|
||||||
|
defer bc.txLookupLock.Unlock()
|
||||||
|
|
||||||
// Reorg can be executed, start reducing the chain's old blocks and appending
|
// Reorg can be executed, start reducing the chain's old blocks and appending
|
||||||
// the new blocks
|
// the new blocks
|
||||||
|
|
@ -2730,9 +2686,6 @@ func (bc *BlockChain) reorg(oldHead *types.Header, newHead *types.Header) error
|
||||||
// Reset the tx lookup cache to clear stale txlookup cache.
|
// Reset the tx lookup cache to clear stale txlookup cache.
|
||||||
bc.txLookupCache.Purge()
|
bc.txLookupCache.Purge()
|
||||||
|
|
||||||
// Release the tx-lookup lock after mutation.
|
|
||||||
bc.txLookupLock.Unlock()
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -296,6 +296,14 @@ func (bc *BlockChain) GetReceiptsRLP(hash common.Hash) rlp.RawValue {
|
||||||
return rawdb.ReadReceiptsRLP(bc.db, hash, number)
|
return rawdb.ReadReceiptsRLP(bc.db, hash, number)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (bc *BlockChain) GetAccessListRLP(hash common.Hash) rlp.RawValue {
|
||||||
|
number, ok := rawdb.ReadHeaderNumber(bc.db, hash)
|
||||||
|
if !ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return rawdb.ReadAccessListRLP(bc.db, hash, number)
|
||||||
|
}
|
||||||
|
|
||||||
// GetUnclesInChain retrieves all the uncles from a given block backwards until
|
// GetUnclesInChain retrieves all the uncles from a given block backwards until
|
||||||
// a specific distance is reached.
|
// a specific distance is reached.
|
||||||
func (bc *BlockChain) GetUnclesInChain(block *types.Block, length int) []*types.Header {
|
func (bc *BlockChain) GetUnclesInChain(block *types.Block, length int) []*types.Header {
|
||||||
|
|
@ -468,7 +476,7 @@ func (bc *BlockChain) TxIndexProgress() (TxIndexProgress, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// StateIndexProgress returns the historical state indexing progress.
|
// StateIndexProgress returns the historical state indexing progress.
|
||||||
func (bc *BlockChain) StateIndexProgress() (uint64, error) {
|
func (bc *BlockChain) StateIndexProgress() (uint64, uint64, error) {
|
||||||
return bc.triedb.IndexProgress()
|
return bc.triedb.IndexProgress()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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 (
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@
|
||||||
package core
|
package core
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/big"
|
"math/big"
|
||||||
|
|
||||||
|
|
@ -411,7 +412,7 @@ func GenerateChain(config *params.ChainConfig, parent *types.Block, engine conse
|
||||||
}
|
}
|
||||||
|
|
||||||
body := types.Body{Transactions: b.txs, Uncles: b.uncles, Withdrawals: b.withdrawals}
|
body := types.Body{Transactions: b.txs, Uncles: b.uncles, Withdrawals: b.withdrawals}
|
||||||
block, err := b.engine.FinalizeAndAssemble(cm, b.header, statedb, &body, b.receipts)
|
block, err := b.engine.FinalizeAndAssemble(context.Background(), cm, b.header, statedb, &body, b.receipts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -26,6 +26,7 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/consensus/misc/eip4844"
|
"github.com/ethereum/go-ethereum/consensus/misc/eip4844"
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
|
"github.com/ethereum/go-ethereum/core/types/bal"
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
"github.com/ethereum/go-ethereum/ethdb"
|
"github.com/ethereum/go-ethereum/ethdb"
|
||||||
"github.com/ethereum/go-ethereum/log"
|
"github.com/ethereum/go-ethereum/log"
|
||||||
|
|
@ -605,6 +606,55 @@ func DeleteReceipts(db ethdb.KeyValueWriter, hash common.Hash, number uint64) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// HasAccessList verifies the existence of a block access list for a block.
|
||||||
|
func HasAccessList(db ethdb.Reader, hash common.Hash, number uint64) bool {
|
||||||
|
has, _ := db.Has(accessListKey(number, hash))
|
||||||
|
return has
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadAccessListRLP retrieves the RLP-encoded block access list for a block from KV.
|
||||||
|
func ReadAccessListRLP(db ethdb.Reader, hash common.Hash, number uint64) rlp.RawValue {
|
||||||
|
data, _ := db.Get(accessListKey(number, hash))
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadAccessList retrieves and decodes the block access list for a block.
|
||||||
|
func ReadAccessList(db ethdb.Reader, hash common.Hash, number uint64) *bal.BlockAccessList {
|
||||||
|
data := ReadAccessListRLP(db, hash, number)
|
||||||
|
if len(data) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
b := new(bal.BlockAccessList)
|
||||||
|
if err := rlp.DecodeBytes(data, b); err != nil {
|
||||||
|
log.Error("Invalid BAL RLP", "hash", hash, "err", err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteAccessList RLP-encodes and stores a block access list in the active KV store.
|
||||||
|
func WriteAccessList(db ethdb.KeyValueWriter, hash common.Hash, number uint64, b *bal.BlockAccessList) {
|
||||||
|
bytes, err := rlp.EncodeToBytes(b)
|
||||||
|
if err != nil {
|
||||||
|
log.Crit("Failed to encode BAL", "err", err)
|
||||||
|
}
|
||||||
|
WriteAccessListRLP(db, hash, number, bytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteAccessListRLP stores a pre-encoded block access list in the active KV store.
|
||||||
|
func WriteAccessListRLP(db ethdb.KeyValueWriter, hash common.Hash, number uint64, encoded rlp.RawValue) {
|
||||||
|
if err := db.Put(accessListKey(number, hash), encoded); err != nil {
|
||||||
|
log.Crit("Failed to store BAL", "err", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteAccessList removes a block access list from the active KV store.
|
||||||
|
func DeleteAccessList(db ethdb.KeyValueWriter, hash common.Hash, number uint64) {
|
||||||
|
if err := db.Delete(accessListKey(number, hash)); err != nil {
|
||||||
|
log.Crit("Failed to delete BAL", "err", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ReceiptLogs is a barebone version of ReceiptForStorage which only keeps
|
// ReceiptLogs is a barebone version of ReceiptForStorage which only keeps
|
||||||
// the list of logs. When decoding a stored receipt into this object we
|
// the list of logs. When decoding a stored receipt into this object we
|
||||||
// avoid creating the bloom filter.
|
// avoid creating the bloom filter.
|
||||||
|
|
@ -659,13 +709,25 @@ func ReadBlock(db ethdb.Reader, hash common.Hash, number uint64) *types.Block {
|
||||||
if body == nil {
|
if body == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return types.NewBlockWithHeader(header).WithBody(*body)
|
block := types.NewBlockWithHeader(header).WithBody(*body)
|
||||||
|
|
||||||
|
// Best-effort assembly of the block access list from the database.
|
||||||
|
if header.BlockAccessListHash != nil {
|
||||||
|
al := ReadAccessList(db, hash, number)
|
||||||
|
block = block.WithAccessListUnsafe(al)
|
||||||
|
}
|
||||||
|
return block
|
||||||
}
|
}
|
||||||
|
|
||||||
// WriteBlock serializes a block into the database, header and body separately.
|
// WriteBlock serializes a block into the database, header and body separately.
|
||||||
func WriteBlock(db ethdb.KeyValueWriter, block *types.Block) {
|
func WriteBlock(db ethdb.KeyValueWriter, block *types.Block) {
|
||||||
WriteBody(db, block.Hash(), block.NumberU64(), block.Body())
|
hash, number := block.Hash(), block.NumberU64()
|
||||||
|
WriteBody(db, hash, number, block.Body())
|
||||||
WriteHeader(db, block.Header())
|
WriteHeader(db, block.Header())
|
||||||
|
|
||||||
|
if accessList := block.AccessList(); accessList != nil {
|
||||||
|
WriteAccessList(db, hash, number, accessList)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// WriteAncientBlocks writes entire block data into ancient store and returns the total written size.
|
// WriteAncientBlocks writes entire block data into ancient store and returns the total written size.
|
||||||
|
|
|
||||||
|
|
@ -27,10 +27,12 @@ import (
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
|
"github.com/ethereum/go-ethereum/core/types/bal"
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
"github.com/ethereum/go-ethereum/crypto/keccak"
|
"github.com/ethereum/go-ethereum/crypto/keccak"
|
||||||
"github.com/ethereum/go-ethereum/params"
|
"github.com/ethereum/go-ethereum/params"
|
||||||
"github.com/ethereum/go-ethereum/rlp"
|
"github.com/ethereum/go-ethereum/rlp"
|
||||||
|
"github.com/holiman/uint256"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Tests block header storage and retrieval operations.
|
// Tests block header storage and retrieval operations.
|
||||||
|
|
@ -899,3 +901,78 @@ func TestHeadersRLPStorage(t *testing.T) {
|
||||||
checkSequence(1, 1) // Only block 1
|
checkSequence(1, 1) // Only block 1
|
||||||
checkSequence(1, 2) // Genesis + block 1
|
checkSequence(1, 2) // Genesis + block 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func makeTestBAL(t *testing.T) (rlp.RawValue, *bal.BlockAccessList) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
cb := bal.NewConstructionBlockAccessList()
|
||||||
|
addr := common.HexToAddress("0x1111111111111111111111111111111111111111")
|
||||||
|
cb.AccountRead(addr)
|
||||||
|
cb.StorageRead(addr, common.BytesToHash([]byte{0x01}))
|
||||||
|
cb.StorageWrite(0, addr, common.BytesToHash([]byte{0x02}), common.BytesToHash([]byte{0xaa}))
|
||||||
|
cb.BalanceChange(0, addr, uint256.NewInt(100))
|
||||||
|
cb.NonceChange(addr, 0, 1)
|
||||||
|
|
||||||
|
var buf bytes.Buffer
|
||||||
|
if err := cb.EncodeRLP(&buf); err != nil {
|
||||||
|
t.Fatalf("failed to encode BAL: %v", err)
|
||||||
|
}
|
||||||
|
encoded := buf.Bytes()
|
||||||
|
|
||||||
|
var decoded bal.BlockAccessList
|
||||||
|
if err := rlp.DecodeBytes(encoded, &decoded); err != nil {
|
||||||
|
t.Fatalf("failed to decode BAL: %v", err)
|
||||||
|
}
|
||||||
|
return encoded, &decoded
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestBALStorage tests write/read/delete of BALs in the KV store.
|
||||||
|
func TestBALStorage(t *testing.T) {
|
||||||
|
db := NewMemoryDatabase()
|
||||||
|
|
||||||
|
hash := common.BytesToHash([]byte{0x03, 0x14})
|
||||||
|
number := uint64(42)
|
||||||
|
|
||||||
|
// Check that no BAL exists in a new database.
|
||||||
|
if HasAccessList(db, hash, number) {
|
||||||
|
t.Fatal("BAL found in new database")
|
||||||
|
}
|
||||||
|
if b := ReadAccessList(db, hash, number); b != nil {
|
||||||
|
t.Fatalf("non existent BAL returned: %v", b)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write a BAL and verify it can be read back.
|
||||||
|
encoded, testBAL := makeTestBAL(t)
|
||||||
|
WriteAccessList(db, hash, number, testBAL)
|
||||||
|
|
||||||
|
if !HasAccessList(db, hash, number) {
|
||||||
|
t.Fatal("HasAccessList returned false after write")
|
||||||
|
}
|
||||||
|
if blob := ReadAccessListRLP(db, hash, number); len(blob) == 0 {
|
||||||
|
t.Fatal("ReadAccessListRLP returned empty after write")
|
||||||
|
}
|
||||||
|
if b := ReadAccessList(db, hash, number); b == nil {
|
||||||
|
t.Fatal("ReadAccessList returned nil after write")
|
||||||
|
} else if b.Hash() != testBAL.Hash() {
|
||||||
|
t.Fatalf("BAL hash mismatch: got %x, want %x", b.Hash(), testBAL.Hash())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Also test WriteAccessListRLP with pre-encoded data.
|
||||||
|
hash2 := common.BytesToHash([]byte{0x03, 0x15})
|
||||||
|
WriteAccessListRLP(db, hash2, number, encoded)
|
||||||
|
if b := ReadAccessList(db, hash2, number); b == nil {
|
||||||
|
t.Fatal("ReadAccessList returned nil after WriteAccessListRLP")
|
||||||
|
} else if b.Hash() != testBAL.Hash() {
|
||||||
|
t.Fatalf("BAL hash mismatch after WriteAccessListRLP: got %x, want %x", b.Hash(), testBAL.Hash())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete the BAL and verify it's gone.
|
||||||
|
DeleteAccessList(db, hash, number)
|
||||||
|
|
||||||
|
if HasAccessList(db, hash, number) {
|
||||||
|
t.Fatal("HasAccessList returned true after delete")
|
||||||
|
}
|
||||||
|
if b := ReadAccessList(db, hash, number); b != nil {
|
||||||
|
t.Fatalf("deleted BAL returned: %v", b)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -413,6 +413,7 @@ func InspectDatabase(db ethdb.Database, keyPrefix, keyStart []byte) error {
|
||||||
tds stat
|
tds stat
|
||||||
numHashPairings stat
|
numHashPairings stat
|
||||||
hashNumPairings stat
|
hashNumPairings stat
|
||||||
|
blockAccessList stat
|
||||||
legacyTries stat
|
legacyTries stat
|
||||||
stateLookups stat
|
stateLookups stat
|
||||||
accountTries stat
|
accountTries stat
|
||||||
|
|
@ -478,12 +479,15 @@ func InspectDatabase(db ethdb.Database, keyPrefix, keyStart []byte) error {
|
||||||
bodies.add(size)
|
bodies.add(size)
|
||||||
case bytes.HasPrefix(key, blockReceiptsPrefix) && len(key) == (len(blockReceiptsPrefix)+8+common.HashLength):
|
case bytes.HasPrefix(key, blockReceiptsPrefix) && len(key) == (len(blockReceiptsPrefix)+8+common.HashLength):
|
||||||
receipts.add(size)
|
receipts.add(size)
|
||||||
case bytes.HasPrefix(key, headerPrefix) && bytes.HasSuffix(key, 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):
|
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)
|
||||||
|
case bytes.HasPrefix(key, accessListPrefix) && len(key) == len(accessListPrefix)+8+common.HashLength:
|
||||||
|
blockAccessList.add(size)
|
||||||
|
|
||||||
case IsLegacyTrieNode(key, it.Value()):
|
case IsLegacyTrieNode(key, it.Value()):
|
||||||
legacyTries.add(size)
|
legacyTries.add(size)
|
||||||
case bytes.HasPrefix(key, stateIDPrefix) && len(key) == len(stateIDPrefix)+common.HashLength:
|
case bytes.HasPrefix(key, stateIDPrefix) && len(key) == len(stateIDPrefix)+common.HashLength:
|
||||||
|
|
@ -625,6 +629,7 @@ func InspectDatabase(db ethdb.Database, keyPrefix, keyStart []byte) error {
|
||||||
{"Key-Value store", "Difficulties (deprecated)", tds.sizeString(), tds.countString()},
|
{"Key-Value store", "Difficulties (deprecated)", tds.sizeString(), tds.countString()},
|
||||||
{"Key-Value store", "Block number->hash", numHashPairings.sizeString(), numHashPairings.countString()},
|
{"Key-Value store", "Block number->hash", numHashPairings.sizeString(), numHashPairings.countString()},
|
||||||
{"Key-Value store", "Block hash->number", hashNumPairings.sizeString(), hashNumPairings.countString()},
|
{"Key-Value store", "Block hash->number", hashNumPairings.sizeString(), hashNumPairings.countString()},
|
||||||
|
{"Key-Value store", "Block accessList", blockAccessList.sizeString(), blockAccessList.countString()},
|
||||||
{"Key-Value store", "Transaction index", txLookups.sizeString(), txLookups.countString()},
|
{"Key-Value store", "Transaction index", txLookups.sizeString(), txLookups.countString()},
|
||||||
{"Key-Value store", "Log index filter-map rows", filterMapRows.sizeString(), filterMapRows.countString()},
|
{"Key-Value store", "Log index filter-map rows", filterMapRows.sizeString(), filterMapRows.countString()},
|
||||||
{"Key-Value store", "Log index last-block-of-map", filterMapLastBlock.sizeString(), filterMapLastBlock.countString()},
|
{"Key-Value store", "Log index last-block-of-map", filterMapLastBlock.sizeString(), filterMapLastBlock.countString()},
|
||||||
|
|
|
||||||
|
|
@ -26,13 +26,7 @@ func atomicRename(src, dest string) error {
|
||||||
if err := os.Rename(src, dest); err != nil {
|
if err := os.Rename(src, dest); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
dir, err := os.Open(filepath.Dir(src))
|
return syncDir(filepath.Dir(src))
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer dir.Close()
|
|
||||||
|
|
||||||
return dir.Sync()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// copyFrom copies data from 'srcPath' at offset 'offset' into 'destPath'.
|
// copyFrom copies data from 'srcPath' at offset 'offset' into 'destPath'.
|
||||||
|
|
|
||||||
49
core/rawdb/freezer_utils_unix.go
Normal file
49
core/rawdb/freezer_utils_unix.go
Normal file
|
|
@ -0,0 +1,49 @@
|
||||||
|
// Copyright 2022 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/>.
|
||||||
|
|
||||||
|
//go:build !windows
|
||||||
|
// +build !windows
|
||||||
|
|
||||||
|
package rawdb
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"os"
|
||||||
|
"syscall"
|
||||||
|
)
|
||||||
|
|
||||||
|
// syncDir ensures that the directory metadata (e.g. newly renamed files)
|
||||||
|
// is flushed to durable storage.
|
||||||
|
func syncDir(name string) error {
|
||||||
|
f, err := os.Open(name)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
// Some file systems do not support fsyncing directories (e.g. some FUSE
|
||||||
|
// mounts). Ignore EINVAL in those cases.
|
||||||
|
if err := f.Sync(); err != nil {
|
||||||
|
if errors.Is(err, os.ErrInvalid) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if patherr, ok := err.(*os.PathError); ok && patherr.Err == syscall.EINVAL {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
26
core/rawdb/freezer_utils_windows.go
Normal file
26
core/rawdb/freezer_utils_windows.go
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
// Copyright 2022 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/>.
|
||||||
|
|
||||||
|
//go:build windows
|
||||||
|
// +build windows
|
||||||
|
|
||||||
|
package rawdb
|
||||||
|
|
||||||
|
// syncDir is a no-op on Windows. Fsyncing a directory handle is not
|
||||||
|
// supported and returns "Access is denied".
|
||||||
|
func syncDir(name string) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
@ -112,6 +112,7 @@ var (
|
||||||
|
|
||||||
blockBodyPrefix = []byte("b") // blockBodyPrefix + num (uint64 big endian) + hash -> block body
|
blockBodyPrefix = []byte("b") // blockBodyPrefix + num (uint64 big endian) + hash -> block body
|
||||||
blockReceiptsPrefix = []byte("r") // blockReceiptsPrefix + num (uint64 big endian) + hash -> block receipts
|
blockReceiptsPrefix = []byte("r") // blockReceiptsPrefix + num (uint64 big endian) + hash -> block receipts
|
||||||
|
accessListPrefix = []byte("j") // accessListPrefix + num (uint64 big endian) + hash -> block access list
|
||||||
|
|
||||||
txLookupPrefix = []byte("l") // txLookupPrefix + hash -> transaction/receipt lookup metadata
|
txLookupPrefix = []byte("l") // txLookupPrefix + hash -> transaction/receipt lookup metadata
|
||||||
bloomBitsPrefix = []byte("B") // bloomBitsPrefix + bit (uint16 big endian) + section (uint64 big endian) + hash -> bloom bits
|
bloomBitsPrefix = []byte("B") // bloomBitsPrefix + bit (uint16 big endian) + section (uint64 big endian) + hash -> bloom bits
|
||||||
|
|
@ -214,6 +215,11 @@ func blockReceiptsKey(number uint64, hash common.Hash) []byte {
|
||||||
return append(append(blockReceiptsPrefix, encodeBlockNumber(number)...), hash.Bytes()...)
|
return append(append(blockReceiptsPrefix, encodeBlockNumber(number)...), hash.Bytes()...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// accessListKey = accessListPrefix + num (uint64 big endian) + hash
|
||||||
|
func accessListKey(number uint64, hash common.Hash) []byte {
|
||||||
|
return append(append(accessListPrefix, encodeBlockNumber(number)...), hash.Bytes()...)
|
||||||
|
}
|
||||||
|
|
||||||
// txLookupKey = txLookupPrefix + hash
|
// txLookupKey = txLookupPrefix + hash
|
||||||
func txLookupKey(hash common.Hash) []byte {
|
func txLookupKey(hash common.Hash) []byte {
|
||||||
return append(txLookupPrefix, hash.Bytes()...)
|
return append(txLookupPrefix, hash.Bytes()...)
|
||||||
|
|
|
||||||
|
|
@ -39,6 +39,10 @@ type Database interface {
|
||||||
// Reader returns a state reader associated with the specified state root.
|
// Reader returns a state reader associated with the specified state root.
|
||||||
Reader(root common.Hash) (Reader, error)
|
Reader(root common.Hash) (Reader, error)
|
||||||
|
|
||||||
|
// Iteratee returns a state iteratee associated with the specified state root,
|
||||||
|
// through which the account iterator and storage iterator can be created.
|
||||||
|
Iteratee(root common.Hash) (Iteratee, error)
|
||||||
|
|
||||||
// OpenTrie opens the main account trie.
|
// OpenTrie opens the main account trie.
|
||||||
OpenTrie(root common.Hash) (Trie, error)
|
OpenTrie(root common.Hash) (Trie, error)
|
||||||
|
|
||||||
|
|
@ -48,9 +52,6 @@ type Database interface {
|
||||||
// TrieDB returns the underlying trie database for managing trie nodes.
|
// TrieDB returns the underlying trie database for managing trie nodes.
|
||||||
TrieDB() *triedb.Database
|
TrieDB() *triedb.Database
|
||||||
|
|
||||||
// Snapshot returns the underlying state snapshot.
|
|
||||||
Snapshot() *snapshot.Tree
|
|
||||||
|
|
||||||
// Commit flushes all pending writes and finalizes the state transition,
|
// Commit flushes all pending writes and finalizes the state transition,
|
||||||
// committing the changes to the underlying storage. It returns an error
|
// committing the changes to the underlying storage. It returns an error
|
||||||
// if the commit fails.
|
// if the commit fails.
|
||||||
|
|
@ -310,6 +311,12 @@ func (db *CachingDB) Commit(update *stateUpdate) error {
|
||||||
return db.triedb.Update(update.root, update.originRoot, update.blockNumber, update.nodes, update.stateSet())
|
return db.triedb.Update(update.root, update.originRoot, update.blockNumber, update.nodes, update.stateSet())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Iteratee returns a state iteratee associated with the specified state root,
|
||||||
|
// through which the account iterator and storage iterator can be created.
|
||||||
|
func (db *CachingDB) Iteratee(root common.Hash) (Iteratee, error) {
|
||||||
|
return newStateIteratee(!db.triedb.IsVerkle(), root, db.triedb, db.snap)
|
||||||
|
}
|
||||||
|
|
||||||
// mustCopyTrie returns a deep-copied trie.
|
// mustCopyTrie returns a deep-copied trie.
|
||||||
func mustCopyTrie(t Trie) Trie {
|
func mustCopyTrie(t Trie) Trie {
|
||||||
switch t := t.(type) {
|
switch t := t.(type) {
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,6 @@ import (
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/core/state/snapshot"
|
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
"github.com/ethereum/go-ethereum/rlp"
|
"github.com/ethereum/go-ethereum/rlp"
|
||||||
|
|
@ -289,14 +288,15 @@ func (db *HistoricDB) TrieDB() *triedb.Database {
|
||||||
return db.triedb
|
return db.triedb
|
||||||
}
|
}
|
||||||
|
|
||||||
// Snapshot returns the underlying state snapshot.
|
|
||||||
func (db *HistoricDB) Snapshot() *snapshot.Tree {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Commit flushes all pending writes and finalizes the state transition,
|
// Commit flushes all pending writes and finalizes the state transition,
|
||||||
// committing the changes to the underlying storage. It returns an error
|
// committing the changes to the underlying storage. It returns an error
|
||||||
// if the commit fails.
|
// if the commit fails.
|
||||||
func (db *HistoricDB) Commit(update *stateUpdate) error {
|
func (db *HistoricDB) Commit(update *stateUpdate) error {
|
||||||
return errors.New("not implemented")
|
return errors.New("not implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Iteratee returns a state iteratee associated with the specified state root,
|
||||||
|
// through which the account iterator and storage iterator can be created.
|
||||||
|
func (db *HistoricDB) Iteratee(root common.Hash) (Iteratee, error) {
|
||||||
|
return nil, errors.New("not implemented")
|
||||||
|
}
|
||||||
|
|
|
||||||
435
core/state/database_iterator.go
Normal file
435
core/state/database_iterator.go
Normal file
|
|
@ -0,0 +1,435 @@
|
||||||
|
// Copyright 2025 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package state
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/ethereum/go-ethereum/core/state/snapshot"
|
||||||
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
|
"github.com/ethereum/go-ethereum/trie"
|
||||||
|
"github.com/ethereum/go-ethereum/triedb"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Iterator is an iterator to step over all the accounts or the specific
|
||||||
|
// storage in the specific state.
|
||||||
|
type Iterator interface {
|
||||||
|
// Next steps the iterator forward one element. It returns false if the iterator
|
||||||
|
// is exhausted or if an error occurs. Any error encountered is retained and
|
||||||
|
// can be retrieved via Error().
|
||||||
|
Next() bool
|
||||||
|
|
||||||
|
// Error returns any failure that occurred during iteration, which might have
|
||||||
|
// caused a premature iteration exit.
|
||||||
|
Error() error
|
||||||
|
|
||||||
|
// Hash returns the hash of the account or storage slot the iterator is
|
||||||
|
// currently at.
|
||||||
|
Hash() common.Hash
|
||||||
|
|
||||||
|
// Release releases associated resources. Release should always succeed and
|
||||||
|
// can be called multiple times without causing error.
|
||||||
|
Release()
|
||||||
|
}
|
||||||
|
|
||||||
|
// AccountIterator is an iterator to step over all the accounts in the
|
||||||
|
// specific state.
|
||||||
|
type AccountIterator interface {
|
||||||
|
Iterator
|
||||||
|
|
||||||
|
// Address returns the raw account address the iterator is currently at.
|
||||||
|
// An error will be returned if the preimage is not available.
|
||||||
|
Address() (common.Address, error)
|
||||||
|
|
||||||
|
// Account returns the RLP encoded account the iterator is currently at.
|
||||||
|
// An error will be retained if the iterator becomes invalid.
|
||||||
|
Account() []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// StorageIterator is an iterator to step over the specific storage in the
|
||||||
|
// specific state.
|
||||||
|
type StorageIterator interface {
|
||||||
|
Iterator
|
||||||
|
|
||||||
|
// Key returns the raw storage slot key the iterator is currently at.
|
||||||
|
// An error will be returned if the preimage is not available.
|
||||||
|
Key() (common.Hash, error)
|
||||||
|
|
||||||
|
// Slot returns the storage slot the iterator is currently at. An error will
|
||||||
|
// be retained if the iterator becomes invalid.
|
||||||
|
Slot() []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// Iteratee wraps the NewIterator methods for traversing the accounts and
|
||||||
|
// storages of the specific state.
|
||||||
|
type Iteratee interface {
|
||||||
|
// NewAccountIterator creates an account iterator for the state specified by
|
||||||
|
// the given root. It begins at a specified starting position, corresponding
|
||||||
|
// to a particular initial key (or the next key if the specified one does
|
||||||
|
// not exist).
|
||||||
|
//
|
||||||
|
// The starting position here refers to the hash of the account address.
|
||||||
|
NewAccountIterator(start common.Hash) (AccountIterator, error)
|
||||||
|
|
||||||
|
// NewStorageIterator creates a storage iterator for the state specified by
|
||||||
|
// the address hash. It begins at a specified starting position, corresponding
|
||||||
|
// to a particular initial key (or the next key if the specified one does
|
||||||
|
// not exist).
|
||||||
|
//
|
||||||
|
// The starting position here refers to the hash of the slot key.
|
||||||
|
NewStorageIterator(addressHash common.Hash, start common.Hash) (StorageIterator, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// PreimageReader wraps the function Preimage for accessing the preimage of
|
||||||
|
// a given hash.
|
||||||
|
type PreimageReader interface {
|
||||||
|
// Preimage returns the preimage of associated hash.
|
||||||
|
Preimage(hash common.Hash) []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// flatAccountIterator is a wrapper around the underlying flat state iterator.
|
||||||
|
// Before returning data from the iterator, it performs an additional conversion
|
||||||
|
// to bridge the slim encoding with the full encoding format.
|
||||||
|
type flatAccountIterator struct {
|
||||||
|
err error
|
||||||
|
it snapshot.AccountIterator
|
||||||
|
preimage PreimageReader
|
||||||
|
}
|
||||||
|
|
||||||
|
// newFlatAccountIterator constructs the account iterator with the provided
|
||||||
|
// flat state iterator.
|
||||||
|
func newFlatAccountIterator(it snapshot.AccountIterator, preimage PreimageReader) *flatAccountIterator {
|
||||||
|
return &flatAccountIterator{it: it, preimage: preimage}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Next steps the iterator forward one element. It returns false if the iterator
|
||||||
|
// is exhausted or if an error occurs. Any error encountered is retained and
|
||||||
|
// can be retrieved via Error().
|
||||||
|
func (ai *flatAccountIterator) Next() bool {
|
||||||
|
if ai.err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return ai.it.Next()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error returns any failure that occurred during iteration, which might have
|
||||||
|
// caused a premature iteration exit.
|
||||||
|
func (ai *flatAccountIterator) Error() error {
|
||||||
|
if ai.err != nil {
|
||||||
|
return ai.err
|
||||||
|
}
|
||||||
|
return ai.it.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hash returns the hash of the account or storage slot the iterator is
|
||||||
|
// currently at.
|
||||||
|
func (ai *flatAccountIterator) Hash() common.Hash {
|
||||||
|
return ai.it.Hash()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Release releases associated resources. Release should always succeed and
|
||||||
|
// can be called multiple times without causing error.
|
||||||
|
func (ai *flatAccountIterator) Release() {
|
||||||
|
ai.it.Release()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Address returns the raw account address the iterator is currently at.
|
||||||
|
// An error will be returned if the preimage is not available.
|
||||||
|
func (ai *flatAccountIterator) Address() (common.Address, error) {
|
||||||
|
if ai.preimage == nil {
|
||||||
|
return common.Address{}, errors.New("account address is not available")
|
||||||
|
}
|
||||||
|
preimage := ai.preimage.Preimage(ai.Hash())
|
||||||
|
if preimage == nil {
|
||||||
|
return common.Address{}, errors.New("account address is not available")
|
||||||
|
}
|
||||||
|
return common.BytesToAddress(preimage), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Account returns the account data the iterator is currently at. The account
|
||||||
|
// data is encoded as slim format from the underlying iterator, the conversion
|
||||||
|
// is required.
|
||||||
|
func (ai *flatAccountIterator) Account() []byte {
|
||||||
|
data, err := types.FullAccountRLP(ai.it.Account())
|
||||||
|
if err != nil {
|
||||||
|
ai.err = err
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
|
// flatStorageIterator is a wrapper around the underlying flat state iterator.
|
||||||
|
type flatStorageIterator struct {
|
||||||
|
it snapshot.StorageIterator
|
||||||
|
preimage PreimageReader
|
||||||
|
}
|
||||||
|
|
||||||
|
// newFlatStorageIterator constructs the storage iterator with the provided
|
||||||
|
// flat state iterator.
|
||||||
|
func newFlatStorageIterator(it snapshot.StorageIterator, preimage PreimageReader) *flatStorageIterator {
|
||||||
|
return &flatStorageIterator{it: it, preimage: preimage}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Next steps the iterator forward one element. It returns false if the iterator
|
||||||
|
// is exhausted or if an error occurs. Any error encountered is retained and
|
||||||
|
// can be retrieved via Error().
|
||||||
|
func (si *flatStorageIterator) Next() bool {
|
||||||
|
return si.it.Next()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error returns any failure that occurred during iteration, which might have
|
||||||
|
// caused a premature iteration exit.
|
||||||
|
func (si *flatStorageIterator) Error() error {
|
||||||
|
return si.it.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hash returns the hash of the account or storage slot the iterator is
|
||||||
|
// currently at.
|
||||||
|
func (si *flatStorageIterator) Hash() common.Hash {
|
||||||
|
return si.it.Hash()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Release releases associated resources. Release should always succeed and
|
||||||
|
// can be called multiple times without causing error.
|
||||||
|
func (si *flatStorageIterator) Release() {
|
||||||
|
si.it.Release()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Key returns the raw storage slot key the iterator is currently at.
|
||||||
|
// An error will be returned if the preimage is not available.
|
||||||
|
func (si *flatStorageIterator) Key() (common.Hash, error) {
|
||||||
|
if si.preimage == nil {
|
||||||
|
return common.Hash{}, errors.New("slot key is not available")
|
||||||
|
}
|
||||||
|
preimage := si.preimage.Preimage(si.Hash())
|
||||||
|
if preimage == nil {
|
||||||
|
return common.Hash{}, errors.New("slot key is not available")
|
||||||
|
}
|
||||||
|
return common.BytesToHash(preimage), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Slot returns the storage slot data the iterator is currently at.
|
||||||
|
func (si *flatStorageIterator) Slot() []byte {
|
||||||
|
return si.it.Slot()
|
||||||
|
}
|
||||||
|
|
||||||
|
// merkleIterator implements the Iterator interface, providing functions to traverse
|
||||||
|
// the accounts or storages with the manner of Merkle-Patricia-Trie.
|
||||||
|
type merkleIterator struct {
|
||||||
|
tr Trie
|
||||||
|
it *trie.Iterator
|
||||||
|
account bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// newMerkleTrieIterator constructs the iterator with the given trie and starting position.
|
||||||
|
func newMerkleTrieIterator(tr Trie, start common.Hash, account bool) (*merkleIterator, error) {
|
||||||
|
it, err := tr.NodeIterator(start.Bytes())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &merkleIterator{
|
||||||
|
tr: tr,
|
||||||
|
it: trie.NewIterator(it),
|
||||||
|
account: account,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Next steps the iterator forward one element. It returns false if the iterator
|
||||||
|
// is exhausted or if an error occurs. Any error encountered is retained and
|
||||||
|
// can be retrieved via Error().
|
||||||
|
func (ti *merkleIterator) Next() bool {
|
||||||
|
return ti.it.Next()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error returns any failure that occurred during iteration, which might have
|
||||||
|
// caused a premature iteration exit.
|
||||||
|
func (ti *merkleIterator) Error() error {
|
||||||
|
return ti.it.Err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hash returns the hash of the account or storage slot the iterator is
|
||||||
|
// currently at.
|
||||||
|
func (ti *merkleIterator) Hash() common.Hash {
|
||||||
|
return common.BytesToHash(ti.it.Key)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Release releases associated resources. Release should always succeed and
|
||||||
|
// can be called multiple times without causing error.
|
||||||
|
func (ti *merkleIterator) Release() {}
|
||||||
|
|
||||||
|
// Address returns the raw account address the iterator is currently at.
|
||||||
|
// An error will be returned if the preimage is not available.
|
||||||
|
func (ti *merkleIterator) Address() (common.Address, error) {
|
||||||
|
if !ti.account {
|
||||||
|
return common.Address{}, errors.New("account address is not available")
|
||||||
|
}
|
||||||
|
preimage := ti.tr.GetKey(ti.it.Key)
|
||||||
|
if preimage == nil {
|
||||||
|
return common.Address{}, errors.New("account address is not available")
|
||||||
|
}
|
||||||
|
return common.BytesToAddress(preimage), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Account returns the account data the iterator is currently at.
|
||||||
|
func (ti *merkleIterator) Account() []byte {
|
||||||
|
if !ti.account {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return ti.it.Value
|
||||||
|
}
|
||||||
|
|
||||||
|
// Key returns the raw storage slot key the iterator is currently at.
|
||||||
|
// An error will be returned if the preimage is not available.
|
||||||
|
func (ti *merkleIterator) Key() (common.Hash, error) {
|
||||||
|
if ti.account {
|
||||||
|
return common.Hash{}, errors.New("slot key is not available")
|
||||||
|
}
|
||||||
|
preimage := ti.tr.GetKey(ti.it.Key)
|
||||||
|
if preimage == nil {
|
||||||
|
return common.Hash{}, errors.New("slot key is not available")
|
||||||
|
}
|
||||||
|
return common.BytesToHash(preimage), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Slot returns the storage slot the iterator is currently at.
|
||||||
|
func (ti *merkleIterator) Slot() []byte {
|
||||||
|
if ti.account {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return ti.it.Value
|
||||||
|
}
|
||||||
|
|
||||||
|
// stateIteratee implements Iteratee interface, providing the state traversal
|
||||||
|
// functionalities of a specific state.
|
||||||
|
type stateIteratee struct {
|
||||||
|
merkle bool
|
||||||
|
root common.Hash
|
||||||
|
triedb *triedb.Database
|
||||||
|
snap *snapshot.Tree
|
||||||
|
}
|
||||||
|
|
||||||
|
func newStateIteratee(merkle bool, root common.Hash, triedb *triedb.Database, snap *snapshot.Tree) (*stateIteratee, error) {
|
||||||
|
return &stateIteratee{
|
||||||
|
merkle: merkle,
|
||||||
|
root: root,
|
||||||
|
triedb: triedb,
|
||||||
|
snap: snap,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewAccountIterator creates an account iterator for the state specified by
|
||||||
|
// the given root. It begins at a specified starting position, corresponding
|
||||||
|
// to a particular initial key (or the next key if the specified one does
|
||||||
|
// not exist).
|
||||||
|
//
|
||||||
|
// The starting position here refers to the hash of the account address.
|
||||||
|
func (si *stateIteratee) NewAccountIterator(start common.Hash) (AccountIterator, error) {
|
||||||
|
// If the external snapshot is available (hash scheme), try to initialize
|
||||||
|
// the account iterator from there first.
|
||||||
|
if si.snap != nil {
|
||||||
|
it, err := si.snap.AccountIterator(si.root, start)
|
||||||
|
if err == nil {
|
||||||
|
return newFlatAccountIterator(it, si.triedb), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// If the external snapshot is not available, try to initialize the
|
||||||
|
// account iterator from the trie database (path scheme)
|
||||||
|
it, err := si.triedb.AccountIterator(si.root, start)
|
||||||
|
if err == nil {
|
||||||
|
return newFlatAccountIterator(it, si.triedb), nil
|
||||||
|
}
|
||||||
|
if !si.merkle {
|
||||||
|
return nil, fmt.Errorf("state %x is not available for account traversal", si.root)
|
||||||
|
}
|
||||||
|
// The snapshot is not usable so far, construct the account iterator from
|
||||||
|
// the trie as the fallback. It's not as efficient as the flat state iterator.
|
||||||
|
tr, err := trie.NewStateTrie(trie.StateTrieID(si.root), si.triedb)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return newMerkleTrieIterator(tr, start, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewStorageIterator creates a storage iterator for the state specified by
|
||||||
|
// the address hash. It begins at a specified starting position, corresponding
|
||||||
|
// to a particular initial key (or the next key if the specified one does not exist).
|
||||||
|
//
|
||||||
|
// The starting position here refers to the hash of the slot key.
|
||||||
|
func (si *stateIteratee) NewStorageIterator(addressHash common.Hash, start common.Hash) (StorageIterator, error) {
|
||||||
|
// If the external snapshot is available (hash scheme), try to initialize
|
||||||
|
// the storage iterator from there first.
|
||||||
|
if si.snap != nil {
|
||||||
|
it, err := si.snap.StorageIterator(si.root, addressHash, start)
|
||||||
|
if err == nil {
|
||||||
|
return newFlatStorageIterator(it, si.triedb), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// If the external snapshot is not available, try to initialize the
|
||||||
|
// storage iterator from the trie database (path scheme)
|
||||||
|
it, err := si.triedb.StorageIterator(si.root, addressHash, start)
|
||||||
|
if err == nil {
|
||||||
|
return newFlatStorageIterator(it, si.triedb), nil
|
||||||
|
}
|
||||||
|
if !si.merkle {
|
||||||
|
return nil, fmt.Errorf("state %x is not available for storage traversal", si.root)
|
||||||
|
}
|
||||||
|
// The snapshot is not usable so far, construct the storage iterator from
|
||||||
|
// the trie as the fallback. It's not as efficient as the flat state iterator.
|
||||||
|
tr, err := trie.NewStateTrie(trie.StateTrieID(si.root), si.triedb)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
acct, err := tr.GetAccountByHash(addressHash)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if acct == nil || acct.Root == types.EmptyRootHash {
|
||||||
|
return &exhaustedIterator{}, nil
|
||||||
|
}
|
||||||
|
storageTr, err := trie.NewStateTrie(trie.StorageTrieID(si.root, addressHash, acct.Root), si.triedb)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return newMerkleTrieIterator(storageTr, start, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
type exhaustedIterator struct{}
|
||||||
|
|
||||||
|
func (e exhaustedIterator) Next() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e exhaustedIterator) Error() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e exhaustedIterator) Hash() common.Hash {
|
||||||
|
return common.Hash{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e exhaustedIterator) Release() {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e exhaustedIterator) Key() (common.Hash, error) {
|
||||||
|
return common.Hash{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e exhaustedIterator) Slot() []byte {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
262
core/state/database_iterator_test.go
Normal file
262
core/state/database_iterator_test.go
Normal file
|
|
@ -0,0 +1,262 @@
|
||||||
|
// 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 state
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||||
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
|
"github.com/ethereum/go-ethereum/rlp"
|
||||||
|
"github.com/ethereum/go-ethereum/trie"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestExhaustedIterator verifies the exhaustedIterator sentinel: Next is false,
|
||||||
|
// Error is nil, Hash/Key are zero, Slot is nil, and double Release is safe.
|
||||||
|
func TestExhaustedIterator(t *testing.T) {
|
||||||
|
var it exhaustedIterator
|
||||||
|
|
||||||
|
if it.Next() {
|
||||||
|
t.Fatal("Next() returned true")
|
||||||
|
}
|
||||||
|
if err := it.Error(); err != nil {
|
||||||
|
t.Fatalf("Error() = %v, want nil", err)
|
||||||
|
}
|
||||||
|
if hash := it.Hash(); hash != (common.Hash{}) {
|
||||||
|
t.Fatalf("Hash() = %x, want zero", hash)
|
||||||
|
}
|
||||||
|
if key, err := it.Key(); key != (common.Hash{}) || err != nil {
|
||||||
|
t.Fatalf("Key() = %x, %v; want zero, nil", key, err)
|
||||||
|
}
|
||||||
|
if slot := it.Slot(); slot != nil {
|
||||||
|
t.Fatalf("Slot() = %x, want nil", slot)
|
||||||
|
}
|
||||||
|
it.Release()
|
||||||
|
it.Release()
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestAccountIterator tests the account iterator: correct count, ascending
|
||||||
|
// hash order, valid full-format RLP, data integrity, address preimage
|
||||||
|
// resolution, and seek behavior.
|
||||||
|
func TestAccountIterator(t *testing.T) {
|
||||||
|
testAccountIterator(t, rawdb.HashScheme)
|
||||||
|
testAccountIterator(t, rawdb.PathScheme)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAccountIterator(t *testing.T, scheme string) {
|
||||||
|
_, sdb, ndb, root, accounts := makeTestState(scheme)
|
||||||
|
ndb.Commit(root, false)
|
||||||
|
|
||||||
|
iteratee, err := sdb.Iteratee(root)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("(%s) failed to create iteratee: %v", scheme, err)
|
||||||
|
}
|
||||||
|
// Build lookups from address hash.
|
||||||
|
addrByHash := make(map[common.Hash]*testAccount)
|
||||||
|
for _, acc := range accounts {
|
||||||
|
addrByHash[crypto.Keccak256Hash(acc.address.Bytes())] = acc
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Full iteration: count, ordering, RLP validity, data integrity, address resolution ---
|
||||||
|
acctIt, err := iteratee.NewAccountIterator(common.Hash{})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("(%s) failed to create account iterator: %v", scheme, err)
|
||||||
|
}
|
||||||
|
var (
|
||||||
|
hashes []common.Hash
|
||||||
|
prevHash common.Hash
|
||||||
|
)
|
||||||
|
for acctIt.Next() {
|
||||||
|
hash := acctIt.Hash()
|
||||||
|
if hash == (common.Hash{}) {
|
||||||
|
t.Fatalf("(%s) zero hash at position %d", scheme, len(hashes))
|
||||||
|
}
|
||||||
|
if len(hashes) > 0 && bytes.Compare(prevHash.Bytes(), hash.Bytes()) >= 0 {
|
||||||
|
t.Fatalf("(%s) hashes not ascending: %x >= %x", scheme, prevHash, hash)
|
||||||
|
}
|
||||||
|
prevHash = hash
|
||||||
|
hashes = append(hashes, hash)
|
||||||
|
|
||||||
|
// Decode and verify account data.
|
||||||
|
blob := acctIt.Account()
|
||||||
|
if blob == nil {
|
||||||
|
t.Fatalf("(%s) nil account at %x", scheme, hash)
|
||||||
|
}
|
||||||
|
var decoded types.StateAccount
|
||||||
|
if err := rlp.DecodeBytes(blob, &decoded); err != nil {
|
||||||
|
t.Fatalf("(%s) bad RLP at %x: %v", scheme, hash, err)
|
||||||
|
}
|
||||||
|
acc := addrByHash[hash]
|
||||||
|
if decoded.Nonce != acc.nonce {
|
||||||
|
t.Fatalf("(%s) nonce %x: got %d, want %d", scheme, hash, decoded.Nonce, acc.nonce)
|
||||||
|
}
|
||||||
|
if decoded.Balance.Cmp(acc.balance) != 0 {
|
||||||
|
t.Fatalf("(%s) balance %x: got %v, want %v", scheme, hash, decoded.Balance, acc.balance)
|
||||||
|
}
|
||||||
|
// Verify address preimage resolution.
|
||||||
|
addr, err := acctIt.Address()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("(%s) failed to address: %v", scheme, err)
|
||||||
|
}
|
||||||
|
if addr != acc.address {
|
||||||
|
t.Fatalf("(%s) Address() = %x, want %x", scheme, addr, acc.address)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
acctIt.Release()
|
||||||
|
|
||||||
|
if err := acctIt.Error(); err != nil {
|
||||||
|
t.Fatalf("(%s) iteration error: %v", scheme, err)
|
||||||
|
}
|
||||||
|
if len(hashes) != len(accounts) {
|
||||||
|
t.Fatalf("(%s) iterated %d accounts, want %d", scheme, len(hashes), len(accounts))
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Seek: starting from midpoint should skip earlier entries ---
|
||||||
|
mid := hashes[len(hashes)/2]
|
||||||
|
seekIt, err := iteratee.NewAccountIterator(mid)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("(%s) failed to create seeked iterator: %v", scheme, err)
|
||||||
|
}
|
||||||
|
seekCount := 0
|
||||||
|
for seekIt.Next() {
|
||||||
|
if bytes.Compare(seekIt.Hash().Bytes(), mid.Bytes()) < 0 {
|
||||||
|
t.Fatalf("(%s) seeked iterator returned hash before start", scheme)
|
||||||
|
}
|
||||||
|
seekCount++
|
||||||
|
}
|
||||||
|
seekIt.Release()
|
||||||
|
|
||||||
|
if seekCount != len(hashes)/2 {
|
||||||
|
t.Fatalf("(%s) unexpected seeked count, %d != %d", scheme, seekCount, len(hashes)/2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestStorageIterator tests the storage iterator: correct slot counts against
|
||||||
|
// the trie, ascending hash order, non-nil slot data, key preimage resolution,
|
||||||
|
// seek behavior, and empty-storage accounts.
|
||||||
|
func TestStorageIterator(t *testing.T) {
|
||||||
|
testStorageIterator(t, rawdb.HashScheme)
|
||||||
|
testStorageIterator(t, rawdb.PathScheme)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testStorageIterator(t *testing.T, scheme string) {
|
||||||
|
_, sdb, ndb, root, accounts := makeTestState(scheme)
|
||||||
|
ndb.Commit(root, false)
|
||||||
|
|
||||||
|
iteratee, err := sdb.Iteratee(root)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("(%s) failed to create iteratee: %v", scheme, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Slot count and ordering for every account ---
|
||||||
|
var withStorage common.Hash // remember an account that has storage for seek test
|
||||||
|
for _, acc := range accounts {
|
||||||
|
addrHash := crypto.Keccak256Hash(acc.address.Bytes())
|
||||||
|
expected := countStorageSlots(t, scheme, sdb, root, addrHash)
|
||||||
|
|
||||||
|
storageIt, err := iteratee.NewStorageIterator(addrHash, common.Hash{})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("(%s) failed to create storage iterator for %x: %v", scheme, acc.address, err)
|
||||||
|
}
|
||||||
|
count := 0
|
||||||
|
var prevHash common.Hash
|
||||||
|
for storageIt.Next() {
|
||||||
|
hash := storageIt.Hash()
|
||||||
|
if count > 0 && bytes.Compare(prevHash.Bytes(), hash.Bytes()) >= 0 {
|
||||||
|
t.Fatalf("(%s) storage hashes not ascending for %x", scheme, acc.address)
|
||||||
|
}
|
||||||
|
prevHash = hash
|
||||||
|
if storageIt.Slot() == nil {
|
||||||
|
t.Fatalf("(%s) nil slot at %x", scheme, hash)
|
||||||
|
}
|
||||||
|
// Check key preimage resolution on first slot.
|
||||||
|
if _, err := storageIt.Key(); err != nil {
|
||||||
|
t.Fatalf("(%s) Key() failed to resolve", scheme)
|
||||||
|
}
|
||||||
|
count++
|
||||||
|
}
|
||||||
|
if err := storageIt.Error(); err != nil {
|
||||||
|
t.Fatalf("(%s) storage iteration error for %x: %v", scheme, acc.address, err)
|
||||||
|
}
|
||||||
|
storageIt.Release()
|
||||||
|
|
||||||
|
if count != expected {
|
||||||
|
t.Fatalf("(%s) account %x: %d slots, want %d", scheme, acc.address, count, expected)
|
||||||
|
}
|
||||||
|
if count > 0 {
|
||||||
|
withStorage = addrHash
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Seek: starting from second slot should skip the first ---
|
||||||
|
if withStorage == (common.Hash{}) {
|
||||||
|
t.Fatalf("(%s) no account with storage found", scheme)
|
||||||
|
}
|
||||||
|
fullIt, err := iteratee.NewStorageIterator(withStorage, common.Hash{})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("(%s) failed to create full storage iterator: %v", scheme, err)
|
||||||
|
}
|
||||||
|
var slotHashes []common.Hash
|
||||||
|
for fullIt.Next() {
|
||||||
|
slotHashes = append(slotHashes, fullIt.Hash())
|
||||||
|
}
|
||||||
|
fullIt.Release()
|
||||||
|
|
||||||
|
seekIt, err := iteratee.NewStorageIterator(withStorage, slotHashes[1])
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("(%s) failed to create seeked storage iterator: %v", scheme, err)
|
||||||
|
}
|
||||||
|
seekCount := 0
|
||||||
|
for seekIt.Next() {
|
||||||
|
if bytes.Compare(seekIt.Hash().Bytes(), slotHashes[1].Bytes()) < 0 {
|
||||||
|
t.Fatalf("(%s) seeked storage iterator returned hash before start", scheme)
|
||||||
|
}
|
||||||
|
seekCount++
|
||||||
|
}
|
||||||
|
seekIt.Release()
|
||||||
|
|
||||||
|
if seekCount != len(slotHashes)-1 {
|
||||||
|
t.Fatalf("(%s) unexpected seeked storage count %d != %d", scheme, seekCount, len(slotHashes)-1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// countStorageSlots counts storage slots for an account by opening the
|
||||||
|
// storage trie directly.
|
||||||
|
func countStorageSlots(t *testing.T, scheme string, sdb Database, root common.Hash, addrHash common.Hash) int {
|
||||||
|
t.Helper()
|
||||||
|
accTrie, err := trie.NewStateTrie(trie.StateTrieID(root), sdb.TrieDB())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("(%s) failed to open account trie: %v", scheme, err)
|
||||||
|
}
|
||||||
|
acct, err := accTrie.GetAccountByHash(addrHash)
|
||||||
|
if err != nil || acct == nil || acct.Root == types.EmptyRootHash {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
storageTrie, err := trie.NewStateTrie(trie.StorageTrieID(root, addrHash, acct.Root), sdb.TrieDB())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("(%s) failed to open storage trie for %x: %v", scheme, addrHash, err)
|
||||||
|
}
|
||||||
|
it := trie.NewIterator(storageTrie.MustNodeIterator(nil))
|
||||||
|
count := 0
|
||||||
|
for it.Next() {
|
||||||
|
count++
|
||||||
|
}
|
||||||
|
return count
|
||||||
|
}
|
||||||
|
|
@ -27,7 +27,6 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
"github.com/ethereum/go-ethereum/log"
|
"github.com/ethereum/go-ethereum/log"
|
||||||
"github.com/ethereum/go-ethereum/rlp"
|
"github.com/ethereum/go-ethereum/rlp"
|
||||||
"github.com/ethereum/go-ethereum/trie"
|
|
||||||
"github.com/ethereum/go-ethereum/trie/bintrie"
|
"github.com/ethereum/go-ethereum/trie/bintrie"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -45,6 +44,7 @@ type DumpConfig struct {
|
||||||
type DumpCollector interface {
|
type DumpCollector interface {
|
||||||
// OnRoot is called with the state root
|
// OnRoot is called with the state root
|
||||||
OnRoot(common.Hash)
|
OnRoot(common.Hash)
|
||||||
|
|
||||||
// OnAccount is called once for each account in the trie
|
// OnAccount is called once for each account in the trie
|
||||||
OnAccount(*common.Address, DumpAccount)
|
OnAccount(*common.Address, DumpAccount)
|
||||||
}
|
}
|
||||||
|
|
@ -65,9 +65,10 @@ type DumpAccount struct {
|
||||||
type Dump struct {
|
type Dump struct {
|
||||||
Root string `json:"root"`
|
Root string `json:"root"`
|
||||||
Accounts map[string]DumpAccount `json:"accounts"`
|
Accounts map[string]DumpAccount `json:"accounts"`
|
||||||
|
|
||||||
// Next can be set to represent that this dump is only partial, and Next
|
// Next can be set to represent that this dump is only partial, and Next
|
||||||
// is where an iterator should be positioned in order to continue the dump.
|
// is where an iterator should be positioned in order to continue the dump.
|
||||||
Next []byte `json:"next,omitempty"` // nil if no more accounts
|
Next hexutil.Bytes `json:"next,omitempty"` // nil if no more accounts
|
||||||
}
|
}
|
||||||
|
|
||||||
// OnRoot implements DumpCollector interface
|
// OnRoot implements DumpCollector interface
|
||||||
|
|
@ -114,9 +115,6 @@ func (d iterativeDump) OnRoot(root common.Hash) {
|
||||||
|
|
||||||
// DumpToCollector iterates the state according to the given options and inserts
|
// DumpToCollector iterates the state according to the given options and inserts
|
||||||
// the items into a collector for aggregation or serialization.
|
// the items into a collector for aggregation or serialization.
|
||||||
//
|
|
||||||
// The state iterator is still trie-based and can be converted to snapshot-based
|
|
||||||
// once the state snapshot is fully integrated into database. TODO(rjl493456442).
|
|
||||||
func (s *StateDB) DumpToCollector(c DumpCollector, conf *DumpConfig) (nextKey []byte) {
|
func (s *StateDB) DumpToCollector(c DumpCollector, conf *DumpConfig) (nextKey []byte) {
|
||||||
// Sanitize the input to allow nil configs
|
// Sanitize the input to allow nil configs
|
||||||
if conf == nil {
|
if conf == nil {
|
||||||
|
|
@ -131,20 +129,23 @@ func (s *StateDB) DumpToCollector(c DumpCollector, conf *DumpConfig) (nextKey []
|
||||||
log.Info("Trie dumping started", "root", s.originalRoot)
|
log.Info("Trie dumping started", "root", s.originalRoot)
|
||||||
c.OnRoot(s.originalRoot)
|
c.OnRoot(s.originalRoot)
|
||||||
|
|
||||||
tr, err := s.db.OpenTrie(s.originalRoot)
|
iteratee, err := s.db.Iteratee(s.originalRoot)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
trieIt, err := tr.NodeIterator(conf.Start)
|
var startHash common.Hash
|
||||||
|
if conf.Start != nil {
|
||||||
|
startHash = common.BytesToHash(conf.Start)
|
||||||
|
}
|
||||||
|
acctIt, err := iteratee.NewAccountIterator(startHash)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("Trie dumping error", "err", err)
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
it := trie.NewIterator(trieIt)
|
defer acctIt.Release()
|
||||||
|
|
||||||
for it.Next() {
|
for acctIt.Next() {
|
||||||
var data types.StateAccount
|
var data types.StateAccount
|
||||||
if err := rlp.DecodeBytes(it.Value, &data); err != nil {
|
if err := rlp.DecodeBytes(acctIt.Account(), &data); err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
var (
|
var (
|
||||||
|
|
@ -153,63 +154,55 @@ func (s *StateDB) DumpToCollector(c DumpCollector, conf *DumpConfig) (nextKey []
|
||||||
Nonce: data.Nonce,
|
Nonce: data.Nonce,
|
||||||
Root: data.Root[:],
|
Root: data.Root[:],
|
||||||
CodeHash: data.CodeHash,
|
CodeHash: data.CodeHash,
|
||||||
AddressHash: it.Key,
|
AddressHash: acctIt.Hash().Bytes(),
|
||||||
}
|
}
|
||||||
address *common.Address
|
address *common.Address
|
||||||
addr common.Address
|
|
||||||
addrBytes = tr.GetKey(it.Key)
|
|
||||||
)
|
)
|
||||||
if addrBytes == nil {
|
addrBytes, err := acctIt.Address()
|
||||||
|
if err != nil {
|
||||||
missingPreimages++
|
missingPreimages++
|
||||||
if conf.OnlyWithAddresses {
|
if conf.OnlyWithAddresses {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
addr = common.BytesToAddress(addrBytes)
|
address = &addrBytes
|
||||||
address = &addr
|
|
||||||
account.Address = address
|
account.Address = address
|
||||||
}
|
}
|
||||||
obj := newObject(s, addr, &data)
|
obj := newObject(s, addrBytes, &data)
|
||||||
if !conf.SkipCode {
|
if !conf.SkipCode {
|
||||||
account.Code = obj.Code()
|
account.Code = obj.Code()
|
||||||
}
|
}
|
||||||
if !conf.SkipStorage {
|
if !conf.SkipStorage {
|
||||||
account.Storage = make(map[common.Hash]string)
|
account.Storage = make(map[common.Hash]string)
|
||||||
|
|
||||||
storageTr, err := s.db.OpenStorageTrie(s.originalRoot, addr, obj.Root(), tr)
|
storageIt, err := iteratee.NewStorageIterator(acctIt.Hash(), common.Hash{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("Failed to load storage trie", "err", err)
|
log.Error("Failed to load storage trie", "err", err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
trieIt, err := storageTr.NodeIterator(nil)
|
|
||||||
if err != nil {
|
|
||||||
log.Error("Failed to create trie iterator", "err", err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
storageIt := trie.NewIterator(trieIt)
|
|
||||||
for storageIt.Next() {
|
for storageIt.Next() {
|
||||||
_, content, _, err := rlp.Split(storageIt.Value)
|
_, content, _, err := rlp.Split(storageIt.Slot())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("Failed to decode the value returned by iterator", "error", err)
|
log.Error("Failed to decode the value returned by iterator", "error", err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
key := storageTr.GetKey(storageIt.Key)
|
key, err := storageIt.Key()
|
||||||
if key == nil {
|
if err != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
account.Storage[common.BytesToHash(key)] = common.Bytes2Hex(content)
|
account.Storage[key] = common.Bytes2Hex(content)
|
||||||
}
|
}
|
||||||
|
storageIt.Release()
|
||||||
}
|
}
|
||||||
c.OnAccount(address, account)
|
c.OnAccount(address, account)
|
||||||
accounts++
|
accounts++
|
||||||
if time.Since(logged) > 8*time.Second {
|
if time.Since(logged) > 8*time.Second {
|
||||||
log.Info("Trie dumping in progress", "at", common.Bytes2Hex(it.Key), "accounts", accounts,
|
log.Info("Trie dumping in progress", "at", acctIt.Hash().Hex(), "accounts", accounts, "elapsed", common.PrettyDuration(time.Since(start)))
|
||||||
"elapsed", common.PrettyDuration(time.Since(start)))
|
|
||||||
logged = time.Now()
|
logged = time.Now()
|
||||||
}
|
}
|
||||||
if conf.Max > 0 && accounts >= conf.Max {
|
if conf.Max > 0 && accounts >= conf.Max {
|
||||||
if it.Next() {
|
if acctIt.Next() {
|
||||||
nextKey = it.Key
|
nextKey = acctIt.Hash().Bytes()
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
@ -217,9 +210,7 @@ func (s *StateDB) DumpToCollector(c DumpCollector, conf *DumpConfig) (nextKey []
|
||||||
if missingPreimages > 0 {
|
if missingPreimages > 0 {
|
||||||
log.Warn("Dump incomplete due to missing preimages", "missing", missingPreimages)
|
log.Warn("Dump incomplete due to missing preimages", "missing", missingPreimages)
|
||||||
}
|
}
|
||||||
log.Info("Trie dumping complete", "accounts", accounts,
|
log.Info("Trie dumping complete", "accounts", accounts, "elapsed", common.PrettyDuration(time.Since(start)))
|
||||||
"elapsed", common.PrettyDuration(time.Since(start)))
|
|
||||||
|
|
||||||
return nextKey
|
return nextKey
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -98,7 +98,7 @@ func newFlatReader(reader database.StateReader) *flatReader {
|
||||||
//
|
//
|
||||||
// The returned account might be nil if it's not existent.
|
// The returned account might be nil if it's not existent.
|
||||||
func (r *flatReader) Account(addr common.Address) (*types.StateAccount, error) {
|
func (r *flatReader) Account(addr common.Address) (*types.StateAccount, error) {
|
||||||
account, err := r.reader.Account(crypto.Keccak256Hash(addr.Bytes()))
|
account, err := r.reader.Account(crypto.Keccak256Hash(addr[:]))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -128,8 +128,8 @@ func (r *flatReader) Account(addr common.Address) (*types.StateAccount, error) {
|
||||||
//
|
//
|
||||||
// The returned storage slot might be empty if it's not existent.
|
// The returned storage slot might be empty if it's not existent.
|
||||||
func (r *flatReader) Storage(addr common.Address, key common.Hash) (common.Hash, error) {
|
func (r *flatReader) Storage(addr common.Address, key common.Hash) (common.Hash, error) {
|
||||||
addrHash := crypto.Keccak256Hash(addr.Bytes())
|
addrHash := crypto.Keccak256Hash(addr[:])
|
||||||
slotHash := crypto.Keccak256Hash(key.Bytes())
|
slotHash := crypto.Keccak256Hash(key[:])
|
||||||
ret, err := r.reader.Storage(addrHash, slotHash)
|
ret, err := r.reader.Storage(addrHash, slotHash)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return common.Hash{}, err
|
return common.Hash{}, err
|
||||||
|
|
|
||||||
|
|
@ -474,6 +474,14 @@ func (s *stateObject) commit() (*accountUpdate, *trienode.NodeSet, error) {
|
||||||
s.origin = s.data.Copy()
|
s.origin = s.data.Copy()
|
||||||
return op, nil, nil
|
return op, nil, nil
|
||||||
}
|
}
|
||||||
|
// In Verkle/binary trie mode, all state objects share one unified trie.
|
||||||
|
// The main account trie commit in stateDB.commit() already calls
|
||||||
|
// CollectNodes on this trie, so calling Commit here again would
|
||||||
|
// redundantly traverse and serialize the entire tree per dirty account.
|
||||||
|
if s.db.GetTrie().IsVerkle() {
|
||||||
|
s.origin = s.data.Copy()
|
||||||
|
return op, nil, nil
|
||||||
|
}
|
||||||
root, nodes := s.trie.Commit(false)
|
root, nodes := s.trie.Commit(false)
|
||||||
s.data.Root = root
|
s.data.Root = root
|
||||||
s.origin = s.data.Copy()
|
s.origin = s.data.Copy()
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,6 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/core/state/snapshot"
|
|
||||||
"github.com/ethereum/go-ethereum/core/stateless"
|
"github.com/ethereum/go-ethereum/core/stateless"
|
||||||
"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"
|
||||||
|
|
@ -135,8 +134,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 +199,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 +740,44 @@ func (s *StateDB) GetRefund() uint64 {
|
||||||
return s.refund
|
return s.refund
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type removedAccountWithBalance struct {
|
||||||
|
address common.Address
|
||||||
|
balance *uint256.Int
|
||||||
|
}
|
||||||
|
|
||||||
|
// LogsForBurnAccounts returns 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) LogsForBurnAccounts() []*types.Log {
|
||||||
|
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 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
sort.Slice(list, func(i, j int) bool {
|
||||||
|
return list[i].address.Cmp(list[j].address) < 0
|
||||||
|
})
|
||||||
|
logs := make([]*types.Log, len(list))
|
||||||
|
for i, acct := range list {
|
||||||
|
logs[i] = types.EthBurnLog(acct.address, acct.balance)
|
||||||
|
}
|
||||||
|
return logs
|
||||||
|
}
|
||||||
|
|
||||||
// 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 +859,67 @@ 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)
|
||||||
|
}
|
||||||
|
s.StorageUpdated.Add(1)
|
||||||
|
} else {
|
||||||
|
if err := s.trie.DeleteStorage(addr, key[:]); err != nil {
|
||||||
|
s.setError(err)
|
||||||
|
}
|
||||||
|
s.StorageDeleted.Add(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
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 +932,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 +948,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 +965,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 +1023,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
|
||||||
}
|
}
|
||||||
|
|
@ -991,31 +1041,32 @@ func (s *StateDB) clearJournalAndRefund() {
|
||||||
s.refund = 0
|
s.refund = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
// fastDeleteStorage is the function that efficiently deletes the storage trie
|
// deleteStorage is designed to delete the storage trie of a designated account.
|
||||||
// of a specific account. It leverages the associated state snapshot for fast
|
func (s *StateDB) deleteStorage(addrHash common.Hash, root common.Hash) (map[common.Hash][]byte, map[common.Hash][]byte, *trienode.NodeSet, error) {
|
||||||
// storage iteration and constructs trie node deletion markers by creating
|
|
||||||
// stack trie with iterated slots.
|
|
||||||
func (s *StateDB) fastDeleteStorage(snaps *snapshot.Tree, addrHash common.Hash, root common.Hash) (map[common.Hash][]byte, map[common.Hash][]byte, *trienode.NodeSet, error) {
|
|
||||||
iter, err := snaps.StorageIterator(s.originalRoot, addrHash, common.Hash{})
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, nil, err
|
|
||||||
}
|
|
||||||
defer iter.Release()
|
|
||||||
|
|
||||||
var (
|
var (
|
||||||
nodes = trienode.NewNodeSet(addrHash) // the set for trie node mutations (value is nil)
|
nodes = trienode.NewNodeSet(addrHash) // the set for trie node mutations (value is nil)
|
||||||
storages = make(map[common.Hash][]byte) // the set for storage mutations (value is nil)
|
storages = make(map[common.Hash][]byte) // the set for storage mutations (value is nil)
|
||||||
storageOrigins = make(map[common.Hash][]byte) // the set for tracking the original value of slot
|
storageOrigins = make(map[common.Hash][]byte) // the set for tracking the original value of slot
|
||||||
)
|
)
|
||||||
|
iteratee, err := s.db.Iteratee(s.originalRoot)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, nil, err
|
||||||
|
}
|
||||||
|
it, err := iteratee.NewStorageIterator(addrHash, common.Hash{})
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, nil, err
|
||||||
|
}
|
||||||
|
defer it.Release()
|
||||||
|
|
||||||
stack := trie.NewStackTrie(func(path []byte, hash common.Hash, blob []byte) {
|
stack := trie.NewStackTrie(func(path []byte, hash common.Hash, blob []byte) {
|
||||||
nodes.AddNode(path, trienode.NewDeletedWithPrev(blob))
|
nodes.AddNode(path, trienode.NewDeletedWithPrev(blob))
|
||||||
})
|
})
|
||||||
for iter.Next() {
|
for it.Next() {
|
||||||
slot := common.CopyBytes(iter.Slot())
|
slot := common.CopyBytes(it.Slot())
|
||||||
if err := iter.Error(); err != nil { // error might occur after Slot function
|
if err := it.Error(); err != nil { // error might occur after Slot function
|
||||||
return nil, nil, nil, err
|
return nil, nil, nil, err
|
||||||
}
|
}
|
||||||
key := iter.Hash()
|
key := it.Hash()
|
||||||
storages[key] = nil
|
storages[key] = nil
|
||||||
storageOrigins[key] = slot
|
storageOrigins[key] = slot
|
||||||
|
|
||||||
|
|
@ -1023,7 +1074,7 @@ func (s *StateDB) fastDeleteStorage(snaps *snapshot.Tree, addrHash common.Hash,
|
||||||
return nil, nil, nil, err
|
return nil, nil, nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err := iter.Error(); err != nil { // error might occur during iteration
|
if err := it.Error(); err != nil { // error might occur during iteration
|
||||||
return nil, nil, nil, err
|
return nil, nil, nil, err
|
||||||
}
|
}
|
||||||
if stack.Hash() != root {
|
if stack.Hash() != root {
|
||||||
|
|
@ -1032,68 +1083,6 @@ func (s *StateDB) fastDeleteStorage(snaps *snapshot.Tree, addrHash common.Hash,
|
||||||
return storages, storageOrigins, nodes, nil
|
return storages, storageOrigins, nodes, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// slowDeleteStorage serves as a less-efficient alternative to "fastDeleteStorage,"
|
|
||||||
// employed when the associated state snapshot is not available. It iterates the
|
|
||||||
// storage slots along with all internal trie nodes via trie directly.
|
|
||||||
func (s *StateDB) slowDeleteStorage(addr common.Address, addrHash common.Hash, root common.Hash) (map[common.Hash][]byte, map[common.Hash][]byte, *trienode.NodeSet, error) {
|
|
||||||
tr, err := s.db.OpenStorageTrie(s.originalRoot, addr, root, s.trie)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, nil, fmt.Errorf("failed to open storage trie, err: %w", err)
|
|
||||||
}
|
|
||||||
it, err := tr.NodeIterator(nil)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, nil, fmt.Errorf("failed to open storage iterator, err: %w", err)
|
|
||||||
}
|
|
||||||
var (
|
|
||||||
nodes = trienode.NewNodeSet(addrHash) // the set for trie node mutations (value is nil)
|
|
||||||
storages = make(map[common.Hash][]byte) // the set for storage mutations (value is nil)
|
|
||||||
storageOrigins = make(map[common.Hash][]byte) // the set for tracking the original value of slot
|
|
||||||
)
|
|
||||||
for it.Next(true) {
|
|
||||||
if it.Leaf() {
|
|
||||||
key := common.BytesToHash(it.LeafKey())
|
|
||||||
storages[key] = nil
|
|
||||||
storageOrigins[key] = common.CopyBytes(it.LeafBlob())
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if it.Hash() == (common.Hash{}) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
nodes.AddNode(it.Path(), trienode.NewDeletedWithPrev(it.NodeBlob()))
|
|
||||||
}
|
|
||||||
if err := it.Error(); err != nil {
|
|
||||||
return nil, nil, nil, err
|
|
||||||
}
|
|
||||||
return storages, storageOrigins, nodes, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// deleteStorage is designed to delete the storage trie of a designated account.
|
|
||||||
// The function will make an attempt to utilize an efficient strategy if the
|
|
||||||
// associated state snapshot is reachable; otherwise, it will resort to a less
|
|
||||||
// efficient approach.
|
|
||||||
func (s *StateDB) deleteStorage(addr common.Address, addrHash common.Hash, root common.Hash) (map[common.Hash][]byte, map[common.Hash][]byte, *trienode.NodeSet, error) {
|
|
||||||
var (
|
|
||||||
err error
|
|
||||||
nodes *trienode.NodeSet // the set for trie node mutations (value is nil)
|
|
||||||
storages map[common.Hash][]byte // the set for storage mutations (value is nil)
|
|
||||||
storageOrigins map[common.Hash][]byte // the set for tracking the original value of slot
|
|
||||||
)
|
|
||||||
// The fast approach can be failed if the snapshot is not fully
|
|
||||||
// generated, or it's internally corrupted. Fallback to the slow
|
|
||||||
// one just in case.
|
|
||||||
snaps := s.db.Snapshot()
|
|
||||||
if snaps != nil {
|
|
||||||
storages, storageOrigins, nodes, err = s.fastDeleteStorage(snaps, addrHash, root)
|
|
||||||
}
|
|
||||||
if snaps == nil || err != nil {
|
|
||||||
storages, storageOrigins, nodes, err = s.slowDeleteStorage(addr, addrHash, root)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, nil, err
|
|
||||||
}
|
|
||||||
return storages, storageOrigins, nodes, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// handleDestruction processes all destruction markers and deletes the account
|
// handleDestruction processes all destruction markers and deletes the account
|
||||||
// and associated storage slots if necessary. There are four potential scenarios
|
// and associated storage slots if necessary. There are four potential scenarios
|
||||||
// as following:
|
// as following:
|
||||||
|
|
@ -1144,7 +1133,7 @@ func (s *StateDB) handleDestruction(noStorageWiping bool) (map[common.Hash]*acco
|
||||||
return nil, nil, fmt.Errorf("unexpected storage wiping, %x", addr)
|
return nil, nil, fmt.Errorf("unexpected storage wiping, %x", addr)
|
||||||
}
|
}
|
||||||
// Remove storage slots belonging to the account.
|
// Remove storage slots belonging to the account.
|
||||||
storages, storagesOrigin, set, err := s.deleteStorage(addr, addrHash, prev.Root)
|
storages, storagesOrigin, set, err := s.deleteStorage(addrHash, prev.Root)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, fmt.Errorf("failed to delete storage, err: %w", err)
|
return nil, nil, fmt.Errorf("failed to delete storage, err: %w", err)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -229,6 +229,10 @@ func (s *hookedStateDB) AddLog(log *types.Log) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *hookedStateDB) LogsForBurnAccounts() []*types.Log {
|
||||||
|
return s.inner.LogsForBurnAccounts()
|
||||||
|
}
|
||||||
|
|
||||||
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.
|
||||||
|
|
|
||||||
|
|
@ -1296,12 +1296,12 @@ func TestDeleteStorage(t *testing.T) {
|
||||||
obj := fastState.getOrNewStateObject(addr)
|
obj := fastState.getOrNewStateObject(addr)
|
||||||
storageRoot := obj.data.Root
|
storageRoot := obj.data.Root
|
||||||
|
|
||||||
_, _, fastNodes, err := fastState.deleteStorage(addr, crypto.Keccak256Hash(addr[:]), storageRoot)
|
_, _, fastNodes, err := fastState.deleteStorage(crypto.Keccak256Hash(addr[:]), storageRoot)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
_, _, slowNodes, err := slowState.deleteStorage(addr, crypto.Keccak256Hash(addr[:]), storageRoot)
|
_, _, slowNodes, err := slowState.deleteStorage(crypto.Keccak256Hash(addr[:]), storageRoot)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -260,6 +260,9 @@ func ProcessBeaconBlockRoot(beaconRoot common.Hash, evm *vm.EVM) {
|
||||||
evm.SetTxContext(NewEVMTxContext(msg))
|
evm.SetTxContext(NewEVMTxContext(msg))
|
||||||
evm.StateDB.AddAddressToAccessList(params.BeaconRootsAddress)
|
evm.StateDB.AddAddressToAccessList(params.BeaconRootsAddress)
|
||||||
_, _, _ = evm.Call(msg.From, *msg.To, msg.Data, 30_000_000, common.U2560)
|
_, _, _ = evm.Call(msg.From, *msg.To, msg.Data, 30_000_000, common.U2560)
|
||||||
|
if evm.StateDB.AccessEvents() != nil {
|
||||||
|
evm.StateDB.AccessEvents().Merge(evm.AccessEvents)
|
||||||
|
}
|
||||||
evm.StateDB.Finalise(true)
|
evm.StateDB.Finalise(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -323,6 +326,9 @@ func processRequestsSystemCall(requests *[][]byte, evm *vm.EVM, requestType byte
|
||||||
evm.SetTxContext(NewEVMTxContext(msg))
|
evm.SetTxContext(NewEVMTxContext(msg))
|
||||||
evm.StateDB.AddAddressToAccessList(addr)
|
evm.StateDB.AddAddressToAccessList(addr)
|
||||||
ret, _, err := evm.Call(msg.From, *msg.To, msg.Data, 30_000_000, common.U2560)
|
ret, _, err := evm.Call(msg.From, *msg.To, msg.Data, 30_000_000, common.U2560)
|
||||||
|
if evm.StateDB.AccessEvents() != nil {
|
||||||
|
evm.StateDB.AccessEvents().Merge(evm.AccessEvents)
|
||||||
|
}
|
||||||
evm.StateDB.Finalise(true)
|
evm.StateDB.Finalise(true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("system call failed to execute: %v", err)
|
return fmt.Errorf("system call failed to execute: %v", err)
|
||||||
|
|
|
||||||
|
|
@ -583,6 +583,11 @@ 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 {
|
||||||
|
for _, log := range st.evm.StateDB.LogsForBurnAccounts() {
|
||||||
|
st.evm.StateDB.AddLog(log)
|
||||||
|
}
|
||||||
|
}
|
||||||
return &ExecutionResult{
|
return &ExecutionResult{
|
||||||
UsedGas: st.gasUsed(),
|
UsedGas: st.gasUsed(),
|
||||||
MaxUsedGas: peakGasUsed,
|
MaxUsedGas: peakGasUsed,
|
||||||
|
|
|
||||||
|
|
@ -53,7 +53,7 @@ func ExecuteStateless(ctx context.Context, config *params.ChainConfig, vmconfig
|
||||||
}
|
}
|
||||||
// Create and populate the state database to serve as the stateless backend
|
// Create and populate the state database to serve as the stateless backend
|
||||||
memdb := witness.MakeHashDB()
|
memdb := witness.MakeHashDB()
|
||||||
db, err := state.New(witness.Root(), state.NewDatabase(triedb.NewDatabase(memdb, triedb.HashDefaults), nil))
|
db, err := state.New(witness.Root(), state.NewDatabase(triedb.NewDatabase(memdb, triedb.HashDefaults), state.NewCodeDB(memdb)))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return common.Hash{}, common.Hash{}, err
|
return common.Hash{}, common.Hash{}, err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@
|
||||||
package stateless
|
package stateless
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"io"
|
"io"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||||
|
|
@ -42,6 +43,9 @@ func (w *Witness) ToExtWitness() *ExtWitness {
|
||||||
|
|
||||||
// FromExtWitness converts the consensus witness format into our internal one.
|
// FromExtWitness converts the consensus witness format into our internal one.
|
||||||
func (w *Witness) FromExtWitness(ext *ExtWitness) error {
|
func (w *Witness) FromExtWitness(ext *ExtWitness) error {
|
||||||
|
if len(ext.Headers) == 0 {
|
||||||
|
return errors.New("witness must contain at least one header")
|
||||||
|
}
|
||||||
w.Headers = ext.Headers
|
w.Headers = ext.Headers
|
||||||
|
|
||||||
w.Codes = make(map[string]struct{}, len(ext.Codes))
|
w.Codes = make(map[string]struct{}, len(ext.Codes))
|
||||||
|
|
|
||||||
|
|
@ -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.
|
||||||
|
|
|
||||||
|
|
@ -1865,11 +1865,11 @@ func (p *BlobPool) drop() {
|
||||||
//
|
//
|
||||||
// The transactions can also be pre-filtered by the dynamic fee components to
|
// The transactions can also be pre-filtered by the dynamic fee components to
|
||||||
// reduce allocations and load on downstream subsystems.
|
// reduce allocations and load on downstream subsystems.
|
||||||
func (p *BlobPool) Pending(filter txpool.PendingFilter) map[common.Address][]*txpool.LazyTransaction {
|
func (p *BlobPool) Pending(filter txpool.PendingFilter) (map[common.Address][]*txpool.LazyTransaction, int) {
|
||||||
// If only plain transactions are requested, this pool is unsuitable as it
|
// If only plain transactions are requested, this pool is unsuitable as it
|
||||||
// contains none, don't even bother.
|
// contains none, don't even bother.
|
||||||
if !filter.BlobTxs {
|
if !filter.BlobTxs {
|
||||||
return nil
|
return nil, 0
|
||||||
}
|
}
|
||||||
// Track the amount of time waiting to retrieve the list of pending blob txs
|
// Track the amount of time waiting to retrieve the list of pending blob txs
|
||||||
// from the pool and the amount of time actually spent on assembling the data.
|
// from the pool and the amount of time actually spent on assembling the data.
|
||||||
|
|
@ -1885,6 +1885,7 @@ func (p *BlobPool) Pending(filter txpool.PendingFilter) map[common.Address][]*tx
|
||||||
pendtimeHist.Update(time.Since(execStart).Nanoseconds())
|
pendtimeHist.Update(time.Since(execStart).Nanoseconds())
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
var count int
|
||||||
pending := make(map[common.Address][]*txpool.LazyTransaction, len(p.index))
|
pending := make(map[common.Address][]*txpool.LazyTransaction, len(p.index))
|
||||||
for addr, txs := range p.index {
|
for addr, txs := range p.index {
|
||||||
lazies := make([]*txpool.LazyTransaction, 0, len(txs))
|
lazies := make([]*txpool.LazyTransaction, 0, len(txs))
|
||||||
|
|
@ -1930,9 +1931,10 @@ func (p *BlobPool) Pending(filter txpool.PendingFilter) map[common.Address][]*tx
|
||||||
}
|
}
|
||||||
if len(lazies) > 0 {
|
if len(lazies) > 0 {
|
||||||
pending[addr] = lazies
|
pending[addr] = lazies
|
||||||
|
count += len(lazies)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return pending
|
return pending, count
|
||||||
}
|
}
|
||||||
|
|
||||||
// updateStorageMetrics retrieves a bunch of stats from the data store and pushes
|
// updateStorageMetrics retrieves a bunch of stats from the data store and pushes
|
||||||
|
|
|
||||||
|
|
@ -2122,7 +2122,7 @@ func benchmarkPoolPending(b *testing.B, datacap uint64) {
|
||||||
b.ReportAllocs()
|
b.ReportAllocs()
|
||||||
|
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
p := pool.Pending(txpool.PendingFilter{
|
p, _ := pool.Pending(txpool.PendingFilter{
|
||||||
MinTip: uint256.NewInt(1),
|
MinTip: uint256.NewInt(1),
|
||||||
BaseFee: chain.basefee,
|
BaseFee: chain.basefee,
|
||||||
BlobFee: chain.blobfee,
|
BlobFee: chain.blobfee,
|
||||||
|
|
|
||||||
|
|
@ -494,15 +494,16 @@ func (pool *LegacyPool) ContentFrom(addr common.Address) ([]*types.Transaction,
|
||||||
//
|
//
|
||||||
// The transactions can also be pre-filtered by the dynamic fee components to
|
// The transactions can also be pre-filtered by the dynamic fee components to
|
||||||
// reduce allocations and load on downstream subsystems.
|
// reduce allocations and load on downstream subsystems.
|
||||||
func (pool *LegacyPool) Pending(filter txpool.PendingFilter) map[common.Address][]*txpool.LazyTransaction {
|
func (pool *LegacyPool) Pending(filter txpool.PendingFilter) (map[common.Address][]*txpool.LazyTransaction, int) {
|
||||||
// If only blob transactions are requested, this pool is unsuitable as it
|
// If only blob transactions are requested, this pool is unsuitable as it
|
||||||
// contains none, don't even bother.
|
// contains none, don't even bother.
|
||||||
if filter.BlobTxs {
|
if filter.BlobTxs {
|
||||||
return nil
|
return nil, 0
|
||||||
}
|
}
|
||||||
pool.mu.Lock()
|
pool.mu.Lock()
|
||||||
defer pool.mu.Unlock()
|
defer pool.mu.Unlock()
|
||||||
|
|
||||||
|
var count int
|
||||||
pending := make(map[common.Address][]*txpool.LazyTransaction, len(pool.pending))
|
pending := make(map[common.Address][]*txpool.LazyTransaction, len(pool.pending))
|
||||||
for addr, list := range pool.pending {
|
for addr, list := range pool.pending {
|
||||||
txs := list.Flatten()
|
txs := list.Flatten()
|
||||||
|
|
@ -539,9 +540,10 @@ func (pool *LegacyPool) Pending(filter txpool.PendingFilter) map[common.Address]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pending[addr] = lazies
|
pending[addr] = lazies
|
||||||
|
count += len(lazies)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return pending
|
return pending, count
|
||||||
}
|
}
|
||||||
|
|
||||||
// ValidateTxBasics checks whether a transaction is valid according to the consensus
|
// ValidateTxBasics checks whether a transaction is valid according to the consensus
|
||||||
|
|
@ -996,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.
|
||||||
|
|
@ -1406,7 +1404,7 @@ func (pool *LegacyPool) promoteExecutables(accounts []common.Address) []*types.T
|
||||||
// promote all promotable transactions
|
// promote all promotable transactions
|
||||||
promoted := make([]*types.Transaction, 0, len(promotable))
|
promoted := make([]*types.Transaction, 0, len(promotable))
|
||||||
for _, tx := range promotable {
|
for _, tx := range promotable {
|
||||||
from, _ := pool.signer.Sender(tx)
|
from, _ := types.Sender(pool.signer, tx) // already validated
|
||||||
if pool.promoteTx(from, tx.Hash(), tx) {
|
if pool.promoteTx(from, tx.Hash(), tx) {
|
||||||
promoted = append(promoted, tx)
|
promoted = append(promoted, tx)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -154,7 +154,7 @@ type SubPool interface {
|
||||||
//
|
//
|
||||||
// The transactions can also be pre-filtered by the dynamic fee components to
|
// The transactions can also be pre-filtered by the dynamic fee components to
|
||||||
// reduce allocations and load on downstream subsystems.
|
// reduce allocations and load on downstream subsystems.
|
||||||
Pending(filter PendingFilter) map[common.Address][]*LazyTransaction
|
Pending(filter PendingFilter) (map[common.Address][]*LazyTransaction, int)
|
||||||
|
|
||||||
// SubscribeTransactions subscribes to new transaction events. The subscriber
|
// SubscribeTransactions subscribes to new transaction events. The subscriber
|
||||||
// can decide whether to receive notifications only for newly seen transactions
|
// can decide whether to receive notifications only for newly seen transactions
|
||||||
|
|
|
||||||
|
|
@ -359,14 +359,17 @@ func (p *TxPool) Add(txs []*types.Transaction, sync bool) []error {
|
||||||
//
|
//
|
||||||
// The transactions can also be pre-filtered by the dynamic fee components to
|
// The transactions can also be pre-filtered by the dynamic fee components to
|
||||||
// reduce allocations and load on downstream subsystems.
|
// reduce allocations and load on downstream subsystems.
|
||||||
func (p *TxPool) Pending(filter PendingFilter) map[common.Address][]*LazyTransaction {
|
func (p *TxPool) Pending(filter PendingFilter) (map[common.Address][]*LazyTransaction, int) {
|
||||||
|
var count int
|
||||||
txs := make(map[common.Address][]*LazyTransaction)
|
txs := make(map[common.Address][]*LazyTransaction)
|
||||||
for _, subpool := range p.subpools {
|
for _, subpool := range p.subpools {
|
||||||
for addr, set := range subpool.Pending(filter) {
|
set, n := subpool.Pending(filter)
|
||||||
txs[addr] = set
|
for addr, list := range set {
|
||||||
|
txs[addr] = list
|
||||||
}
|
}
|
||||||
|
count += n
|
||||||
}
|
}
|
||||||
return txs
|
return txs, count
|
||||||
}
|
}
|
||||||
|
|
||||||
// SubscribeTransactions registers a subscription for new transaction events,
|
// SubscribeTransactions registers a subscription for new transaction events,
|
||||||
|
|
|
||||||
|
|
@ -350,9 +350,12 @@ func (e *BlockAccessList) PrettyPrint() string {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Copy returns a deep copy of the access list
|
// Copy returns a deep copy of the access list
|
||||||
func (e *BlockAccessList) Copy() (res BlockAccessList) {
|
func (e *BlockAccessList) Copy() *BlockAccessList {
|
||||||
for _, accountAccess := range e.Accesses {
|
cpy := &BlockAccessList{
|
||||||
res.Accesses = append(res.Accesses, accountAccess.Copy())
|
Accesses: make([]AccountAccess, 0, len(e.Accesses)),
|
||||||
}
|
}
|
||||||
return
|
for _, accountAccess := range e.Accesses {
|
||||||
|
cpy.Accesses = append(cpy.Accesses, accountAccess.Copy())
|
||||||
|
}
|
||||||
|
return cpy
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -190,8 +190,8 @@ func makeTestAccountAccess(sort bool) AccountAccess {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func makeTestBAL(sort bool) BlockAccessList {
|
func makeTestBAL(sort bool) *BlockAccessList {
|
||||||
list := BlockAccessList{}
|
list := &BlockAccessList{}
|
||||||
for i := 0; i < 5; i++ {
|
for i := 0; i < 5; i++ {
|
||||||
list.Accesses = append(list.Accesses, makeTestAccountAccess(sort))
|
list.Accesses = append(list.Accesses, makeTestAccountAccess(sort))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,7 @@ import (
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||||
|
"github.com/ethereum/go-ethereum/core/types/bal"
|
||||||
"github.com/ethereum/go-ethereum/rlp"
|
"github.com/ethereum/go-ethereum/rlp"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -99,6 +100,9 @@ type Header struct {
|
||||||
// RequestsHash was added by EIP-7685 and is ignored in legacy headers.
|
// RequestsHash was added by EIP-7685 and is ignored in legacy headers.
|
||||||
RequestsHash *common.Hash `json:"requestsHash" rlp:"optional"`
|
RequestsHash *common.Hash `json:"requestsHash" rlp:"optional"`
|
||||||
|
|
||||||
|
// BlockAccessListHash was added by EIP-7928 and is ignored in legacy headers.
|
||||||
|
BlockAccessListHash *common.Hash `json:"balHash" rlp:"optional"`
|
||||||
|
|
||||||
// SlotNumber was added by EIP-7843 and is ignored in legacy headers.
|
// SlotNumber was added by EIP-7843 and is ignored in legacy headers.
|
||||||
SlotNumber *uint64 `json:"slotNumber" rlp:"optional"`
|
SlotNumber *uint64 `json:"slotNumber" rlp:"optional"`
|
||||||
}
|
}
|
||||||
|
|
@ -204,6 +208,7 @@ type Block struct {
|
||||||
uncles []*Header
|
uncles []*Header
|
||||||
transactions Transactions
|
transactions Transactions
|
||||||
withdrawals Withdrawals
|
withdrawals Withdrawals
|
||||||
|
accessList *bal.BlockAccessList
|
||||||
|
|
||||||
// caches
|
// caches
|
||||||
hash atomic.Pointer[common.Hash]
|
hash atomic.Pointer[common.Hash]
|
||||||
|
|
@ -320,6 +325,10 @@ func CopyHeader(h *Header) *Header {
|
||||||
cpy.RequestsHash = new(common.Hash)
|
cpy.RequestsHash = new(common.Hash)
|
||||||
*cpy.RequestsHash = *h.RequestsHash
|
*cpy.RequestsHash = *h.RequestsHash
|
||||||
}
|
}
|
||||||
|
if h.BlockAccessListHash != nil {
|
||||||
|
cpy.BlockAccessListHash = new(common.Hash)
|
||||||
|
*cpy.BlockAccessListHash = *h.BlockAccessListHash
|
||||||
|
}
|
||||||
if h.SlotNumber != nil {
|
if h.SlotNumber != nil {
|
||||||
cpy.SlotNumber = new(uint64)
|
cpy.SlotNumber = new(uint64)
|
||||||
*cpy.SlotNumber = *h.SlotNumber
|
*cpy.SlotNumber = *h.SlotNumber
|
||||||
|
|
@ -358,9 +367,10 @@ func (b *Block) Body() *Body {
|
||||||
// Accessors for body data. These do not return a copy because the content
|
// Accessors for body data. These do not return a copy because the content
|
||||||
// of the body slices does not affect the cached hash/size in block.
|
// of the body slices does not affect the cached hash/size in block.
|
||||||
|
|
||||||
func (b *Block) Uncles() []*Header { return b.uncles }
|
func (b *Block) Uncles() []*Header { return b.uncles }
|
||||||
func (b *Block) Transactions() Transactions { return b.transactions }
|
func (b *Block) Transactions() Transactions { return b.transactions }
|
||||||
func (b *Block) Withdrawals() Withdrawals { return b.withdrawals }
|
func (b *Block) Withdrawals() Withdrawals { return b.withdrawals }
|
||||||
|
func (b *Block) AccessList() *bal.BlockAccessList { return b.accessList }
|
||||||
|
|
||||||
func (b *Block) Transaction(hash common.Hash) *Transaction {
|
func (b *Block) Transaction(hash common.Hash) *Transaction {
|
||||||
for _, transaction := range b.transactions {
|
for _, transaction := range b.transactions {
|
||||||
|
|
@ -495,6 +505,7 @@ func (b *Block) WithSeal(header *Header) *Block {
|
||||||
transactions: b.transactions,
|
transactions: b.transactions,
|
||||||
uncles: b.uncles,
|
uncles: b.uncles,
|
||||||
withdrawals: b.withdrawals,
|
withdrawals: b.withdrawals,
|
||||||
|
accessList: b.accessList,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -506,6 +517,7 @@ func (b *Block) WithBody(body Body) *Block {
|
||||||
transactions: slices.Clone(body.Transactions),
|
transactions: slices.Clone(body.Transactions),
|
||||||
uncles: make([]*Header, len(body.Uncles)),
|
uncles: make([]*Header, len(body.Uncles)),
|
||||||
withdrawals: slices.Clone(body.Withdrawals),
|
withdrawals: slices.Clone(body.Withdrawals),
|
||||||
|
accessList: b.accessList,
|
||||||
}
|
}
|
||||||
for i := range body.Uncles {
|
for i := range body.Uncles {
|
||||||
block.uncles[i] = CopyHeader(body.Uncles[i])
|
block.uncles[i] = CopyHeader(body.Uncles[i])
|
||||||
|
|
@ -513,6 +525,24 @@ func (b *Block) WithBody(body Body) *Block {
|
||||||
return block
|
return block
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WithAccessList returns a copy of the block with the given access list embedded.
|
||||||
|
func (b *Block) WithAccessList(accessList *bal.BlockAccessList) *Block {
|
||||||
|
return b.WithAccessListUnsafe(accessList.Copy())
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithAccessListUnsafe returns a copy of the block with the given access list
|
||||||
|
// embedded. Note that the access list is not deep-copied; use WithAccessList
|
||||||
|
// if the provided list may be modified by other actors.
|
||||||
|
func (b *Block) WithAccessListUnsafe(accessList *bal.BlockAccessList) *Block {
|
||||||
|
return &Block{
|
||||||
|
header: b.header,
|
||||||
|
transactions: b.transactions,
|
||||||
|
uncles: b.uncles,
|
||||||
|
withdrawals: b.withdrawals,
|
||||||
|
accessList: accessList,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Hash returns the keccak256 hash of b's header.
|
// Hash returns the keccak256 hash of b's header.
|
||||||
// The hash is computed on the first call and cached thereafter.
|
// The hash is computed on the first call and cached thereafter.
|
||||||
func (b *Block) Hash() common.Hash {
|
func (b *Block) Hash() common.Hash {
|
||||||
|
|
|
||||||
|
|
@ -16,29 +16,30 @@ var _ = (*headerMarshaling)(nil)
|
||||||
// MarshalJSON marshals as JSON.
|
// MarshalJSON marshals as JSON.
|
||||||
func (h Header) MarshalJSON() ([]byte, error) {
|
func (h Header) MarshalJSON() ([]byte, error) {
|
||||||
type Header struct {
|
type Header struct {
|
||||||
ParentHash common.Hash `json:"parentHash" gencodec:"required"`
|
ParentHash common.Hash `json:"parentHash" gencodec:"required"`
|
||||||
UncleHash common.Hash `json:"sha3Uncles" gencodec:"required"`
|
UncleHash common.Hash `json:"sha3Uncles" gencodec:"required"`
|
||||||
Coinbase common.Address `json:"miner"`
|
Coinbase common.Address `json:"miner"`
|
||||||
Root common.Hash `json:"stateRoot" gencodec:"required"`
|
Root common.Hash `json:"stateRoot" gencodec:"required"`
|
||||||
TxHash common.Hash `json:"transactionsRoot" gencodec:"required"`
|
TxHash common.Hash `json:"transactionsRoot" gencodec:"required"`
|
||||||
ReceiptHash common.Hash `json:"receiptsRoot" gencodec:"required"`
|
ReceiptHash common.Hash `json:"receiptsRoot" gencodec:"required"`
|
||||||
Bloom Bloom `json:"logsBloom" gencodec:"required"`
|
Bloom Bloom `json:"logsBloom" gencodec:"required"`
|
||||||
Difficulty *hexutil.Big `json:"difficulty" gencodec:"required"`
|
Difficulty *hexutil.Big `json:"difficulty" gencodec:"required"`
|
||||||
Number *hexutil.Big `json:"number" gencodec:"required"`
|
Number *hexutil.Big `json:"number" gencodec:"required"`
|
||||||
GasLimit hexutil.Uint64 `json:"gasLimit" gencodec:"required"`
|
GasLimit hexutil.Uint64 `json:"gasLimit" gencodec:"required"`
|
||||||
GasUsed hexutil.Uint64 `json:"gasUsed" gencodec:"required"`
|
GasUsed hexutil.Uint64 `json:"gasUsed" gencodec:"required"`
|
||||||
Time hexutil.Uint64 `json:"timestamp" gencodec:"required"`
|
Time hexutil.Uint64 `json:"timestamp" gencodec:"required"`
|
||||||
Extra hexutil.Bytes `json:"extraData" gencodec:"required"`
|
Extra hexutil.Bytes `json:"extraData" gencodec:"required"`
|
||||||
MixDigest common.Hash `json:"mixHash"`
|
MixDigest common.Hash `json:"mixHash"`
|
||||||
Nonce BlockNonce `json:"nonce"`
|
Nonce BlockNonce `json:"nonce"`
|
||||||
BaseFee *hexutil.Big `json:"baseFeePerGas" rlp:"optional"`
|
BaseFee *hexutil.Big `json:"baseFeePerGas" rlp:"optional"`
|
||||||
WithdrawalsHash *common.Hash `json:"withdrawalsRoot" rlp:"optional"`
|
WithdrawalsHash *common.Hash `json:"withdrawalsRoot" rlp:"optional"`
|
||||||
BlobGasUsed *hexutil.Uint64 `json:"blobGasUsed" rlp:"optional"`
|
BlobGasUsed *hexutil.Uint64 `json:"blobGasUsed" rlp:"optional"`
|
||||||
ExcessBlobGas *hexutil.Uint64 `json:"excessBlobGas" rlp:"optional"`
|
ExcessBlobGas *hexutil.Uint64 `json:"excessBlobGas" rlp:"optional"`
|
||||||
ParentBeaconRoot *common.Hash `json:"parentBeaconBlockRoot" rlp:"optional"`
|
ParentBeaconRoot *common.Hash `json:"parentBeaconBlockRoot" rlp:"optional"`
|
||||||
RequestsHash *common.Hash `json:"requestsHash" rlp:"optional"`
|
RequestsHash *common.Hash `json:"requestsHash" rlp:"optional"`
|
||||||
SlotNumber *hexutil.Uint64 `json:"slotNumber" rlp:"optional"`
|
BlockAccessListHash *common.Hash `json:"balHash" rlp:"optional"`
|
||||||
Hash common.Hash `json:"hash"`
|
SlotNumber *hexutil.Uint64 `json:"slotNumber" rlp:"optional"`
|
||||||
|
Hash common.Hash `json:"hash"`
|
||||||
}
|
}
|
||||||
var enc Header
|
var enc Header
|
||||||
enc.ParentHash = h.ParentHash
|
enc.ParentHash = h.ParentHash
|
||||||
|
|
@ -62,6 +63,7 @@ func (h Header) MarshalJSON() ([]byte, error) {
|
||||||
enc.ExcessBlobGas = (*hexutil.Uint64)(h.ExcessBlobGas)
|
enc.ExcessBlobGas = (*hexutil.Uint64)(h.ExcessBlobGas)
|
||||||
enc.ParentBeaconRoot = h.ParentBeaconRoot
|
enc.ParentBeaconRoot = h.ParentBeaconRoot
|
||||||
enc.RequestsHash = h.RequestsHash
|
enc.RequestsHash = h.RequestsHash
|
||||||
|
enc.BlockAccessListHash = h.BlockAccessListHash
|
||||||
enc.SlotNumber = (*hexutil.Uint64)(h.SlotNumber)
|
enc.SlotNumber = (*hexutil.Uint64)(h.SlotNumber)
|
||||||
enc.Hash = h.Hash()
|
enc.Hash = h.Hash()
|
||||||
return json.Marshal(&enc)
|
return json.Marshal(&enc)
|
||||||
|
|
@ -70,28 +72,29 @@ func (h Header) MarshalJSON() ([]byte, error) {
|
||||||
// UnmarshalJSON unmarshals from JSON.
|
// UnmarshalJSON unmarshals from JSON.
|
||||||
func (h *Header) UnmarshalJSON(input []byte) error {
|
func (h *Header) UnmarshalJSON(input []byte) error {
|
||||||
type Header struct {
|
type Header struct {
|
||||||
ParentHash *common.Hash `json:"parentHash" gencodec:"required"`
|
ParentHash *common.Hash `json:"parentHash" gencodec:"required"`
|
||||||
UncleHash *common.Hash `json:"sha3Uncles" gencodec:"required"`
|
UncleHash *common.Hash `json:"sha3Uncles" gencodec:"required"`
|
||||||
Coinbase *common.Address `json:"miner"`
|
Coinbase *common.Address `json:"miner"`
|
||||||
Root *common.Hash `json:"stateRoot" gencodec:"required"`
|
Root *common.Hash `json:"stateRoot" gencodec:"required"`
|
||||||
TxHash *common.Hash `json:"transactionsRoot" gencodec:"required"`
|
TxHash *common.Hash `json:"transactionsRoot" gencodec:"required"`
|
||||||
ReceiptHash *common.Hash `json:"receiptsRoot" gencodec:"required"`
|
ReceiptHash *common.Hash `json:"receiptsRoot" gencodec:"required"`
|
||||||
Bloom *Bloom `json:"logsBloom" gencodec:"required"`
|
Bloom *Bloom `json:"logsBloom" gencodec:"required"`
|
||||||
Difficulty *hexutil.Big `json:"difficulty" gencodec:"required"`
|
Difficulty *hexutil.Big `json:"difficulty" gencodec:"required"`
|
||||||
Number *hexutil.Big `json:"number" gencodec:"required"`
|
Number *hexutil.Big `json:"number" gencodec:"required"`
|
||||||
GasLimit *hexutil.Uint64 `json:"gasLimit" gencodec:"required"`
|
GasLimit *hexutil.Uint64 `json:"gasLimit" gencodec:"required"`
|
||||||
GasUsed *hexutil.Uint64 `json:"gasUsed" gencodec:"required"`
|
GasUsed *hexutil.Uint64 `json:"gasUsed" gencodec:"required"`
|
||||||
Time *hexutil.Uint64 `json:"timestamp" gencodec:"required"`
|
Time *hexutil.Uint64 `json:"timestamp" gencodec:"required"`
|
||||||
Extra *hexutil.Bytes `json:"extraData" gencodec:"required"`
|
Extra *hexutil.Bytes `json:"extraData" gencodec:"required"`
|
||||||
MixDigest *common.Hash `json:"mixHash"`
|
MixDigest *common.Hash `json:"mixHash"`
|
||||||
Nonce *BlockNonce `json:"nonce"`
|
Nonce *BlockNonce `json:"nonce"`
|
||||||
BaseFee *hexutil.Big `json:"baseFeePerGas" rlp:"optional"`
|
BaseFee *hexutil.Big `json:"baseFeePerGas" rlp:"optional"`
|
||||||
WithdrawalsHash *common.Hash `json:"withdrawalsRoot" rlp:"optional"`
|
WithdrawalsHash *common.Hash `json:"withdrawalsRoot" rlp:"optional"`
|
||||||
BlobGasUsed *hexutil.Uint64 `json:"blobGasUsed" rlp:"optional"`
|
BlobGasUsed *hexutil.Uint64 `json:"blobGasUsed" rlp:"optional"`
|
||||||
ExcessBlobGas *hexutil.Uint64 `json:"excessBlobGas" rlp:"optional"`
|
ExcessBlobGas *hexutil.Uint64 `json:"excessBlobGas" rlp:"optional"`
|
||||||
ParentBeaconRoot *common.Hash `json:"parentBeaconBlockRoot" rlp:"optional"`
|
ParentBeaconRoot *common.Hash `json:"parentBeaconBlockRoot" rlp:"optional"`
|
||||||
RequestsHash *common.Hash `json:"requestsHash" rlp:"optional"`
|
RequestsHash *common.Hash `json:"requestsHash" rlp:"optional"`
|
||||||
SlotNumber *hexutil.Uint64 `json:"slotNumber" rlp:"optional"`
|
BlockAccessListHash *common.Hash `json:"balHash" rlp:"optional"`
|
||||||
|
SlotNumber *hexutil.Uint64 `json:"slotNumber" rlp:"optional"`
|
||||||
}
|
}
|
||||||
var dec Header
|
var dec Header
|
||||||
if err := json.Unmarshal(input, &dec); err != nil {
|
if err := json.Unmarshal(input, &dec); err != nil {
|
||||||
|
|
@ -172,6 +175,9 @@ func (h *Header) UnmarshalJSON(input []byte) error {
|
||||||
if dec.RequestsHash != nil {
|
if dec.RequestsHash != nil {
|
||||||
h.RequestsHash = dec.RequestsHash
|
h.RequestsHash = dec.RequestsHash
|
||||||
}
|
}
|
||||||
|
if dec.BlockAccessListHash != nil {
|
||||||
|
h.BlockAccessListHash = dec.BlockAccessListHash
|
||||||
|
}
|
||||||
if dec.SlotNumber != nil {
|
if dec.SlotNumber != nil {
|
||||||
h.SlotNumber = (*uint64)(dec.SlotNumber)
|
h.SlotNumber = (*uint64)(dec.SlotNumber)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -43,8 +43,9 @@ func (obj *Header) EncodeRLP(_w io.Writer) error {
|
||||||
_tmp4 := obj.ExcessBlobGas != nil
|
_tmp4 := obj.ExcessBlobGas != nil
|
||||||
_tmp5 := obj.ParentBeaconRoot != nil
|
_tmp5 := obj.ParentBeaconRoot != nil
|
||||||
_tmp6 := obj.RequestsHash != nil
|
_tmp6 := obj.RequestsHash != nil
|
||||||
_tmp7 := obj.SlotNumber != nil
|
_tmp7 := obj.BlockAccessListHash != nil
|
||||||
if _tmp1 || _tmp2 || _tmp3 || _tmp4 || _tmp5 || _tmp6 || _tmp7 {
|
_tmp8 := obj.SlotNumber != nil
|
||||||
|
if _tmp1 || _tmp2 || _tmp3 || _tmp4 || _tmp5 || _tmp6 || _tmp7 || _tmp8 {
|
||||||
if obj.BaseFee == nil {
|
if obj.BaseFee == nil {
|
||||||
w.Write(rlp.EmptyString)
|
w.Write(rlp.EmptyString)
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -54,42 +55,49 @@ func (obj *Header) EncodeRLP(_w io.Writer) error {
|
||||||
w.WriteBigInt(obj.BaseFee)
|
w.WriteBigInt(obj.BaseFee)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if _tmp2 || _tmp3 || _tmp4 || _tmp5 || _tmp6 || _tmp7 {
|
if _tmp2 || _tmp3 || _tmp4 || _tmp5 || _tmp6 || _tmp7 || _tmp8 {
|
||||||
if obj.WithdrawalsHash == nil {
|
if obj.WithdrawalsHash == nil {
|
||||||
w.Write([]byte{0x80})
|
w.Write([]byte{0x80})
|
||||||
} else {
|
} else {
|
||||||
w.WriteBytes(obj.WithdrawalsHash[:])
|
w.WriteBytes(obj.WithdrawalsHash[:])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if _tmp3 || _tmp4 || _tmp5 || _tmp6 || _tmp7 {
|
if _tmp3 || _tmp4 || _tmp5 || _tmp6 || _tmp7 || _tmp8 {
|
||||||
if obj.BlobGasUsed == nil {
|
if obj.BlobGasUsed == nil {
|
||||||
w.Write([]byte{0x80})
|
w.Write([]byte{0x80})
|
||||||
} else {
|
} else {
|
||||||
w.WriteUint64((*obj.BlobGasUsed))
|
w.WriteUint64((*obj.BlobGasUsed))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if _tmp4 || _tmp5 || _tmp6 || _tmp7 {
|
if _tmp4 || _tmp5 || _tmp6 || _tmp7 || _tmp8 {
|
||||||
if obj.ExcessBlobGas == nil {
|
if obj.ExcessBlobGas == nil {
|
||||||
w.Write([]byte{0x80})
|
w.Write([]byte{0x80})
|
||||||
} else {
|
} else {
|
||||||
w.WriteUint64((*obj.ExcessBlobGas))
|
w.WriteUint64((*obj.ExcessBlobGas))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if _tmp5 || _tmp6 || _tmp7 {
|
if _tmp5 || _tmp6 || _tmp7 || _tmp8 {
|
||||||
if obj.ParentBeaconRoot == nil {
|
if obj.ParentBeaconRoot == nil {
|
||||||
w.Write([]byte{0x80})
|
w.Write([]byte{0x80})
|
||||||
} else {
|
} else {
|
||||||
w.WriteBytes(obj.ParentBeaconRoot[:])
|
w.WriteBytes(obj.ParentBeaconRoot[:])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if _tmp6 || _tmp7 {
|
if _tmp6 || _tmp7 || _tmp8 {
|
||||||
if obj.RequestsHash == nil {
|
if obj.RequestsHash == nil {
|
||||||
w.Write([]byte{0x80})
|
w.Write([]byte{0x80})
|
||||||
} else {
|
} else {
|
||||||
w.WriteBytes(obj.RequestsHash[:])
|
w.WriteBytes(obj.RequestsHash[:])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if _tmp7 {
|
if _tmp7 || _tmp8 {
|
||||||
|
if obj.BlockAccessListHash == nil {
|
||||||
|
w.Write([]byte{0x80})
|
||||||
|
} else {
|
||||||
|
w.WriteBytes(obj.BlockAccessListHash[:])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if _tmp8 {
|
||||||
if obj.SlotNumber == nil {
|
if obj.SlotNumber == nil {
|
||||||
w.Write([]byte{0x80})
|
w.Write([]byte{0x80})
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
LogsForBurnAccounts() []*types.Log
|
||||||
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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -347,7 +347,7 @@ func (b *EthAPIBackend) SendTx(ctx context.Context, signedTx *types.Transaction)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *EthAPIBackend) GetPoolTransactions() (types.Transactions, error) {
|
func (b *EthAPIBackend) GetPoolTransactions() (types.Transactions, error) {
|
||||||
pending := b.eth.txPool.Pending(txpool.PendingFilter{})
|
pending, _ := b.eth.txPool.Pending(txpool.PendingFilter{})
|
||||||
var txs types.Transactions
|
var txs types.Transactions
|
||||||
for _, batch := range pending {
|
for _, batch := range pending {
|
||||||
for _, lazy := range batch {
|
for _, lazy := range batch {
|
||||||
|
|
@ -414,9 +414,10 @@ func (b *EthAPIBackend) SyncProgress(ctx context.Context) ethereum.SyncProgress
|
||||||
prog.TxIndexFinishedBlocks = txProg.Indexed
|
prog.TxIndexFinishedBlocks = txProg.Indexed
|
||||||
prog.TxIndexRemainingBlocks = txProg.Remaining
|
prog.TxIndexRemainingBlocks = txProg.Remaining
|
||||||
}
|
}
|
||||||
remain, err := b.eth.blockchain.StateIndexProgress()
|
stateRemain, trienodeRemain, err := b.eth.blockchain.StateIndexProgress()
|
||||||
if err == nil {
|
if err == nil {
|
||||||
prog.StateIndexRemaining = remain
|
prog.StateIndexRemaining = stateRemain
|
||||||
|
prog.TrienodeIndexRemaining = trienodeRemain
|
||||||
}
|
}
|
||||||
return prog
|
return prog
|
||||||
}
|
}
|
||||||
|
|
@ -484,12 +485,12 @@ func (b *EthAPIBackend) CurrentHeader() *types.Header {
|
||||||
return b.eth.blockchain.CurrentHeader()
|
return b.eth.blockchain.CurrentHeader()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *EthAPIBackend) StateAtBlock(ctx context.Context, block *types.Block, reexec uint64, base *state.StateDB, readOnly bool, preferDisk bool) (*state.StateDB, tracers.StateReleaseFunc, error) {
|
func (b *EthAPIBackend) StateAtBlock(ctx context.Context, block *types.Block, base *state.StateDB, readOnly bool, preferDisk bool) (*state.StateDB, tracers.StateReleaseFunc, error) {
|
||||||
return b.eth.stateAtBlock(ctx, block, reexec, base, readOnly, preferDisk)
|
return b.eth.stateAtBlock(ctx, block, base, readOnly, preferDisk)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *EthAPIBackend) StateAtTransaction(ctx context.Context, block *types.Block, txIndex int, reexec uint64) (*types.Transaction, vm.BlockContext, *state.StateDB, tracers.StateReleaseFunc, error) {
|
func (b *EthAPIBackend) StateAtTransaction(ctx context.Context, block *types.Block, txIndex int) (*types.Transaction, vm.BlockContext, *state.StateDB, tracers.StateReleaseFunc, error) {
|
||||||
return b.eth.stateAtTransaction(ctx, block, txIndex, reexec)
|
return b.eth.stateAtTransaction(ctx, block, txIndex)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *EthAPIBackend) RPCTxSyncDefaultTimeout() time.Duration {
|
func (b *EthAPIBackend) RPCTxSyncDefaultTimeout() time.Duration {
|
||||||
|
|
|
||||||
|
|
@ -222,7 +222,7 @@ func (api *DebugAPI) StorageRangeAt(ctx context.Context, blockNrOrHash rpc.Block
|
||||||
if block == nil {
|
if block == nil {
|
||||||
return StorageRangeResult{}, fmt.Errorf("block %v not found", blockNrOrHash)
|
return StorageRangeResult{}, fmt.Errorf("block %v not found", blockNrOrHash)
|
||||||
}
|
}
|
||||||
_, _, statedb, release, err := api.eth.stateAtTransaction(ctx, block, txIndex, 0)
|
_, _, statedb, release, err := api.eth.stateAtTransaction(ctx, block, txIndex)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return StorageRangeResult{}, err
|
return StorageRangeResult{}, err
|
||||||
}
|
}
|
||||||
|
|
@ -236,6 +236,8 @@ func storageRangeAt(statedb *state.StateDB, root common.Hash, address common.Add
|
||||||
if storageRoot == types.EmptyRootHash || storageRoot == (common.Hash{}) {
|
if storageRoot == types.EmptyRootHash || storageRoot == (common.Hash{}) {
|
||||||
return StorageRangeResult{}, nil // empty storage
|
return StorageRangeResult{}, nil // empty storage
|
||||||
}
|
}
|
||||||
|
// TODO(rjl493456442) it's problematic for traversing the state with in-memory
|
||||||
|
// state mutations, specifically txIndex != 0.
|
||||||
id := trie.StorageTrieID(root, crypto.Keccak256Hash(address.Bytes()), storageRoot)
|
id := trie.StorageTrieID(root, crypto.Keccak256Hash(address.Bytes()), storageRoot)
|
||||||
tr, err := trie.NewStateTrie(id, statedb.Database().TrieDB())
|
tr, err := trie.NewStateTrie(id, statedb.Database().TrieDB())
|
||||||
if err != nil {
|
if err != 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,
|
||||||
|
|
|
||||||
|
|
@ -162,7 +162,7 @@ func newConsensusAPIWithoutHeartbeat(eth *eth.Ethereum) *ConsensusAPI {
|
||||||
//
|
//
|
||||||
// If there are payloadAttributes: we try to assemble a block with the payloadAttributes
|
// If there are payloadAttributes: we try to assemble a block with the payloadAttributes
|
||||||
// and return its payloadID.
|
// and return its payloadID.
|
||||||
func (api *ConsensusAPI) ForkchoiceUpdatedV1(update engine.ForkchoiceStateV1, payloadAttributes *engine.PayloadAttributes) (engine.ForkChoiceResponse, error) {
|
func (api *ConsensusAPI) ForkchoiceUpdatedV1(ctx context.Context, update engine.ForkchoiceStateV1, payloadAttributes *engine.PayloadAttributes) (engine.ForkChoiceResponse, error) {
|
||||||
if payloadAttributes != nil {
|
if payloadAttributes != nil {
|
||||||
switch {
|
switch {
|
||||||
case payloadAttributes.Withdrawals != nil || payloadAttributes.BeaconRoot != nil:
|
case payloadAttributes.Withdrawals != nil || payloadAttributes.BeaconRoot != nil:
|
||||||
|
|
@ -171,12 +171,12 @@ func (api *ConsensusAPI) ForkchoiceUpdatedV1(update engine.ForkchoiceStateV1, pa
|
||||||
return engine.STATUS_INVALID, paramsErr("fcuV1 called post-shanghai")
|
return engine.STATUS_INVALID, paramsErr("fcuV1 called post-shanghai")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return api.forkchoiceUpdated(update, payloadAttributes, engine.PayloadV1, false)
|
return api.forkchoiceUpdated(ctx, update, payloadAttributes, engine.PayloadV1, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ForkchoiceUpdatedV2 is equivalent to V1 with the addition of withdrawals in the payload
|
// ForkchoiceUpdatedV2 is equivalent to V1 with the addition of withdrawals in the payload
|
||||||
// attributes. It supports both PayloadAttributesV1 and PayloadAttributesV2.
|
// attributes. It supports both PayloadAttributesV1 and PayloadAttributesV2.
|
||||||
func (api *ConsensusAPI) ForkchoiceUpdatedV2(update engine.ForkchoiceStateV1, params *engine.PayloadAttributes) (engine.ForkChoiceResponse, error) {
|
func (api *ConsensusAPI) ForkchoiceUpdatedV2(ctx context.Context, update engine.ForkchoiceStateV1, params *engine.PayloadAttributes) (engine.ForkChoiceResponse, error) {
|
||||||
if params != nil {
|
if params != nil {
|
||||||
switch {
|
switch {
|
||||||
case params.BeaconRoot != nil:
|
case params.BeaconRoot != nil:
|
||||||
|
|
@ -189,12 +189,12 @@ func (api *ConsensusAPI) ForkchoiceUpdatedV2(update engine.ForkchoiceStateV1, pa
|
||||||
return engine.STATUS_INVALID, unsupportedForkErr("fcuV2 must only be called with paris or shanghai payloads")
|
return engine.STATUS_INVALID, unsupportedForkErr("fcuV2 must only be called with paris or shanghai payloads")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return api.forkchoiceUpdated(update, params, engine.PayloadV2, false)
|
return api.forkchoiceUpdated(ctx, update, params, engine.PayloadV2, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ForkchoiceUpdatedV3 is equivalent to V2 with the addition of parent beacon block root
|
// ForkchoiceUpdatedV3 is equivalent to V2 with the addition of parent beacon block root
|
||||||
// in the payload attributes. It supports only PayloadAttributesV3.
|
// in the payload attributes. It supports only PayloadAttributesV3.
|
||||||
func (api *ConsensusAPI) ForkchoiceUpdatedV3(update engine.ForkchoiceStateV1, params *engine.PayloadAttributes) (engine.ForkChoiceResponse, error) {
|
func (api *ConsensusAPI) ForkchoiceUpdatedV3(ctx context.Context, update engine.ForkchoiceStateV1, params *engine.PayloadAttributes) (engine.ForkChoiceResponse, error) {
|
||||||
if params != nil {
|
if params != nil {
|
||||||
switch {
|
switch {
|
||||||
case params.Withdrawals == nil:
|
case params.Withdrawals == nil:
|
||||||
|
|
@ -209,12 +209,12 @@ func (api *ConsensusAPI) ForkchoiceUpdatedV3(update engine.ForkchoiceStateV1, pa
|
||||||
// hash, even if params are wrong. To do this we need to split up
|
// hash, even if params are wrong. To do this we need to split up
|
||||||
// forkchoiceUpdate into a function that only updates the head and then a
|
// forkchoiceUpdate into a function that only updates the head and then a
|
||||||
// function that kicks off block construction.
|
// function that kicks off block construction.
|
||||||
return api.forkchoiceUpdated(update, params, engine.PayloadV3, false)
|
return api.forkchoiceUpdated(ctx, update, params, engine.PayloadV3, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ForkchoiceUpdatedV4 is equivalent to V3 with the addition of slot number
|
// ForkchoiceUpdatedV4 is equivalent to V3 with the addition of slot number
|
||||||
// in the payload attributes. It supports only PayloadAttributesV4.
|
// in the payload attributes. It supports only PayloadAttributesV4.
|
||||||
func (api *ConsensusAPI) ForkchoiceUpdatedV4(update engine.ForkchoiceStateV1, params *engine.PayloadAttributes) (engine.ForkChoiceResponse, error) {
|
func (api *ConsensusAPI) ForkchoiceUpdatedV4(ctx context.Context, update engine.ForkchoiceStateV1, params *engine.PayloadAttributes) (engine.ForkChoiceResponse, error) {
|
||||||
if params != nil {
|
if params != nil {
|
||||||
switch {
|
switch {
|
||||||
case params.Withdrawals == nil:
|
case params.Withdrawals == nil:
|
||||||
|
|
@ -231,10 +231,12 @@ func (api *ConsensusAPI) ForkchoiceUpdatedV4(update engine.ForkchoiceStateV1, pa
|
||||||
// hash, even if params are wrong. To do this we need to split up
|
// hash, even if params are wrong. To do this we need to split up
|
||||||
// forkchoiceUpdate into a function that only updates the head and then a
|
// forkchoiceUpdate into a function that only updates the head and then a
|
||||||
// function that kicks off block construction.
|
// function that kicks off block construction.
|
||||||
return api.forkchoiceUpdated(update, params, engine.PayloadV4, false)
|
return api.forkchoiceUpdated(ctx, update, params, engine.PayloadV4, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (api *ConsensusAPI) forkchoiceUpdated(update engine.ForkchoiceStateV1, payloadAttributes *engine.PayloadAttributes, payloadVersion engine.PayloadVersion, payloadWitness bool) (engine.ForkChoiceResponse, error) {
|
func (api *ConsensusAPI) forkchoiceUpdated(ctx context.Context, update engine.ForkchoiceStateV1, payloadAttributes *engine.PayloadAttributes, payloadVersion engine.PayloadVersion, payloadWitness bool) (result engine.ForkChoiceResponse, err error) {
|
||||||
|
ctx, _, spanEnd := telemetry.StartSpan(ctx, "engine.forkchoiceUpdated")
|
||||||
|
defer spanEnd(&err)
|
||||||
api.forkchoiceLock.Lock()
|
api.forkchoiceLock.Lock()
|
||||||
defer api.forkchoiceLock.Unlock()
|
defer api.forkchoiceLock.Unlock()
|
||||||
|
|
||||||
|
|
@ -375,7 +377,7 @@ func (api *ConsensusAPI) forkchoiceUpdated(update engine.ForkchoiceStateV1, payl
|
||||||
if api.localBlocks.has(id) {
|
if api.localBlocks.has(id) {
|
||||||
return valid(&id), nil
|
return valid(&id), nil
|
||||||
}
|
}
|
||||||
payload, err := api.eth.Miner().BuildPayload(args, payloadWitness)
|
payload, err := api.eth.Miner().BuildPayload(ctx, args, payloadWitness)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("Failed to build payload", "err", err)
|
log.Error("Failed to build payload", "err", err)
|
||||||
return valid(nil), engine.InvalidPayloadAttributes.With(err)
|
return valid(nil), engine.InvalidPayloadAttributes.With(err)
|
||||||
|
|
|
||||||
|
|
@ -190,7 +190,7 @@ func TestEth2PrepareAndGetPayload(t *testing.T) {
|
||||||
SafeBlockHash: common.Hash{},
|
SafeBlockHash: common.Hash{},
|
||||||
FinalizedBlockHash: common.Hash{},
|
FinalizedBlockHash: common.Hash{},
|
||||||
}
|
}
|
||||||
_, err := api.ForkchoiceUpdatedV1(fcState, &blockParams)
|
_, err := api.ForkchoiceUpdatedV1(context.Background(), fcState, &blockParams)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("error preparing payload, err=%v", err)
|
t.Fatalf("error preparing payload, err=%v", err)
|
||||||
}
|
}
|
||||||
|
|
@ -270,7 +270,7 @@ func TestInvalidPayloadTimestamp(t *testing.T) {
|
||||||
SafeBlockHash: common.Hash{},
|
SafeBlockHash: common.Hash{},
|
||||||
FinalizedBlockHash: common.Hash{},
|
FinalizedBlockHash: common.Hash{},
|
||||||
}
|
}
|
||||||
_, err := api.ForkchoiceUpdatedV1(fcState, ¶ms)
|
_, err := api.ForkchoiceUpdatedV1(context.Background(), fcState, ¶ms)
|
||||||
if test.shouldErr && err == nil {
|
if test.shouldErr && err == nil {
|
||||||
t.Fatalf("expected error preparing payload with invalid timestamp, err=%v", err)
|
t.Fatalf("expected error preparing payload with invalid timestamp, err=%v", err)
|
||||||
} else if !test.shouldErr && err != nil {
|
} else if !test.shouldErr && err != nil {
|
||||||
|
|
@ -329,7 +329,7 @@ func TestEth2NewBlock(t *testing.T) {
|
||||||
SafeBlockHash: block.Hash(),
|
SafeBlockHash: block.Hash(),
|
||||||
FinalizedBlockHash: block.Hash(),
|
FinalizedBlockHash: block.Hash(),
|
||||||
}
|
}
|
||||||
if _, err := api.ForkchoiceUpdatedV1(fcState, nil); err != nil {
|
if _, err := api.ForkchoiceUpdatedV1(context.Background(), fcState, nil); err != nil {
|
||||||
t.Fatalf("Failed to insert block: %v", err)
|
t.Fatalf("Failed to insert block: %v", err)
|
||||||
}
|
}
|
||||||
if have, want := ethservice.BlockChain().CurrentBlock().Number.Uint64(), block.NumberU64(); have != want {
|
if have, want := ethservice.BlockChain().CurrentBlock().Number.Uint64(), block.NumberU64(); have != want {
|
||||||
|
|
@ -369,7 +369,7 @@ func TestEth2NewBlock(t *testing.T) {
|
||||||
SafeBlockHash: block.Hash(),
|
SafeBlockHash: block.Hash(),
|
||||||
FinalizedBlockHash: block.Hash(),
|
FinalizedBlockHash: block.Hash(),
|
||||||
}
|
}
|
||||||
if _, err := api.ForkchoiceUpdatedV1(fcState, nil); err != nil {
|
if _, err := api.ForkchoiceUpdatedV1(context.Background(), fcState, nil); err != nil {
|
||||||
t.Fatalf("Failed to insert block: %v", err)
|
t.Fatalf("Failed to insert block: %v", err)
|
||||||
}
|
}
|
||||||
if ethservice.BlockChain().CurrentBlock().Number.Uint64() != block.NumberU64() {
|
if ethservice.BlockChain().CurrentBlock().Number.Uint64() != block.NumberU64() {
|
||||||
|
|
@ -515,7 +515,7 @@ func setupBlocks(t *testing.T, ethservice *eth.Ethereum, n int, parent *types.He
|
||||||
SafeBlockHash: payload.ParentHash,
|
SafeBlockHash: payload.ParentHash,
|
||||||
FinalizedBlockHash: payload.ParentHash,
|
FinalizedBlockHash: payload.ParentHash,
|
||||||
}
|
}
|
||||||
if _, err := api.ForkchoiceUpdatedV1(fcState, nil); err != nil {
|
if _, err := api.ForkchoiceUpdatedV1(context.Background(), fcState, nil); err != nil {
|
||||||
t.Fatalf("Failed to insert block: %v", err)
|
t.Fatalf("Failed to insert block: %v", err)
|
||||||
}
|
}
|
||||||
if ethservice.BlockChain().CurrentBlock().Number.Uint64() != payload.Number {
|
if ethservice.BlockChain().CurrentBlock().Number.Uint64() != payload.Number {
|
||||||
|
|
@ -629,7 +629,7 @@ func TestNewPayloadOnInvalidChain(t *testing.T) {
|
||||||
err error
|
err error
|
||||||
)
|
)
|
||||||
for i := 0; ; i++ {
|
for i := 0; ; i++ {
|
||||||
if resp, err = api.ForkchoiceUpdatedV1(fcState, ¶ms); err != nil {
|
if resp, err = api.ForkchoiceUpdatedV1(context.Background(), fcState, ¶ms); err != nil {
|
||||||
t.Fatalf("error preparing payload, err=%v", err)
|
t.Fatalf("error preparing payload, err=%v", err)
|
||||||
}
|
}
|
||||||
if resp.PayloadStatus.Status != engine.VALID {
|
if resp.PayloadStatus.Status != engine.VALID {
|
||||||
|
|
@ -660,7 +660,7 @@ func TestNewPayloadOnInvalidChain(t *testing.T) {
|
||||||
SafeBlockHash: payload.ExecutionPayload.ParentHash,
|
SafeBlockHash: payload.ExecutionPayload.ParentHash,
|
||||||
FinalizedBlockHash: payload.ExecutionPayload.ParentHash,
|
FinalizedBlockHash: payload.ExecutionPayload.ParentHash,
|
||||||
}
|
}
|
||||||
if _, err := api.ForkchoiceUpdatedV1(fcState, nil); err != nil {
|
if _, err := api.ForkchoiceUpdatedV1(context.Background(), fcState, nil); err != nil {
|
||||||
t.Fatalf("Failed to insert block: %v", err)
|
t.Fatalf("Failed to insert block: %v", err)
|
||||||
}
|
}
|
||||||
if ethservice.BlockChain().CurrentBlock().Number.Uint64() != payload.ExecutionPayload.Number {
|
if ethservice.BlockChain().CurrentBlock().Number.Uint64() != payload.ExecutionPayload.Number {
|
||||||
|
|
@ -679,7 +679,7 @@ func assembleEnvelope(api *ConsensusAPI, parentHash common.Hash, params *engine.
|
||||||
Withdrawals: params.Withdrawals,
|
Withdrawals: params.Withdrawals,
|
||||||
BeaconRoot: params.BeaconRoot,
|
BeaconRoot: params.BeaconRoot,
|
||||||
}
|
}
|
||||||
payload, err := api.eth.Miner().BuildPayload(args, false)
|
payload, err := api.eth.Miner().BuildPayload(context.Background(), args, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -867,7 +867,7 @@ func TestTrickRemoteBlockCache(t *testing.T) {
|
||||||
t.Error("invalid status: VALID on an invalid chain")
|
t.Error("invalid status: VALID on an invalid chain")
|
||||||
}
|
}
|
||||||
// Now reorg to the head of the invalid chain
|
// Now reorg to the head of the invalid chain
|
||||||
resp, err := apiB.ForkchoiceUpdatedV1(engine.ForkchoiceStateV1{HeadBlockHash: payload.BlockHash, SafeBlockHash: payload.BlockHash, FinalizedBlockHash: payload.ParentHash}, nil)
|
resp, err := apiB.ForkchoiceUpdatedV1(context.Background(), engine.ForkchoiceStateV1{HeadBlockHash: payload.BlockHash, SafeBlockHash: payload.BlockHash, FinalizedBlockHash: payload.ParentHash}, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
@ -970,7 +970,7 @@ func TestSimultaneousNewBlock(t *testing.T) {
|
||||||
for ii := 0; ii < 10; ii++ {
|
for ii := 0; ii < 10; ii++ {
|
||||||
go func() {
|
go func() {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
if _, err := api.ForkchoiceUpdatedV1(fcState, nil); err != nil {
|
if _, err := api.ForkchoiceUpdatedV1(context.Background(), fcState, nil); err != nil {
|
||||||
errMu.Lock()
|
errMu.Lock()
|
||||||
testErr = fmt.Errorf("failed to insert block: %w", err)
|
testErr = fmt.Errorf("failed to insert block: %w", err)
|
||||||
errMu.Unlock()
|
errMu.Unlock()
|
||||||
|
|
@ -1011,7 +1011,7 @@ func TestWithdrawals(t *testing.T) {
|
||||||
fcState := engine.ForkchoiceStateV1{
|
fcState := engine.ForkchoiceStateV1{
|
||||||
HeadBlockHash: parent.Hash(),
|
HeadBlockHash: parent.Hash(),
|
||||||
}
|
}
|
||||||
resp, err := api.ForkchoiceUpdatedV2(fcState, &blockParams)
|
resp, err := api.ForkchoiceUpdatedV2(context.Background(), fcState, &blockParams)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("error preparing payload, err=%v", err)
|
t.Fatalf("error preparing payload, err=%v", err)
|
||||||
}
|
}
|
||||||
|
|
@ -1063,7 +1063,7 @@ func TestWithdrawals(t *testing.T) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
fcState.HeadBlockHash = execData.ExecutionPayload.BlockHash
|
fcState.HeadBlockHash = execData.ExecutionPayload.BlockHash
|
||||||
_, err = api.ForkchoiceUpdatedV2(fcState, &blockParams)
|
_, err = api.ForkchoiceUpdatedV2(context.Background(), fcState, &blockParams)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("error preparing payload, err=%v", err)
|
t.Fatalf("error preparing payload, err=%v", err)
|
||||||
}
|
}
|
||||||
|
|
@ -1090,7 +1090,7 @@ func TestWithdrawals(t *testing.T) {
|
||||||
|
|
||||||
// 11: set block as head.
|
// 11: set block as head.
|
||||||
fcState.HeadBlockHash = execData.ExecutionPayload.BlockHash
|
fcState.HeadBlockHash = execData.ExecutionPayload.BlockHash
|
||||||
_, err = api.ForkchoiceUpdatedV2(fcState, nil)
|
_, err = api.ForkchoiceUpdatedV2(context.Background(), fcState, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("error preparing payload, err=%v", err)
|
t.Fatalf("error preparing payload, err=%v", err)
|
||||||
}
|
}
|
||||||
|
|
@ -1196,10 +1196,10 @@ func TestNilWithdrawals(t *testing.T) {
|
||||||
)
|
)
|
||||||
if !shanghai {
|
if !shanghai {
|
||||||
payloadVersion = engine.PayloadV1
|
payloadVersion = engine.PayloadV1
|
||||||
_, err = api.ForkchoiceUpdatedV1(fcState, &test.blockParams)
|
_, err = api.ForkchoiceUpdatedV1(context.Background(), fcState, &test.blockParams)
|
||||||
} else {
|
} else {
|
||||||
payloadVersion = engine.PayloadV2
|
payloadVersion = engine.PayloadV2
|
||||||
_, err = api.ForkchoiceUpdatedV2(fcState, &test.blockParams)
|
_, err = api.ForkchoiceUpdatedV2(context.Background(), fcState, &test.blockParams)
|
||||||
}
|
}
|
||||||
if test.wantErr {
|
if test.wantErr {
|
||||||
if err == nil {
|
if err == nil {
|
||||||
|
|
@ -1579,7 +1579,7 @@ func TestParentBeaconBlockRoot(t *testing.T) {
|
||||||
fcState := engine.ForkchoiceStateV1{
|
fcState := engine.ForkchoiceStateV1{
|
||||||
HeadBlockHash: parent.Hash(),
|
HeadBlockHash: parent.Hash(),
|
||||||
}
|
}
|
||||||
resp, err := api.ForkchoiceUpdatedV3(fcState, &blockParams)
|
resp, err := api.ForkchoiceUpdatedV3(context.Background(), fcState, &blockParams)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("error preparing payload, err=%v", err.(*engine.EngineAPIError).ErrorData())
|
t.Fatalf("error preparing payload, err=%v", err.(*engine.EngineAPIError).ErrorData())
|
||||||
}
|
}
|
||||||
|
|
@ -1610,7 +1610,7 @@ func TestParentBeaconBlockRoot(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
fcState.HeadBlockHash = execData.ExecutionPayload.BlockHash
|
fcState.HeadBlockHash = execData.ExecutionPayload.BlockHash
|
||||||
resp, err = api.ForkchoiceUpdatedV3(fcState, nil)
|
resp, err = api.ForkchoiceUpdatedV3(context.Background(), fcState, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("error preparing payload, err=%v", err.(*engine.EngineAPIError).ErrorData())
|
t.Fatalf("error preparing payload, err=%v", err.(*engine.EngineAPIError).ErrorData())
|
||||||
}
|
}
|
||||||
|
|
@ -1666,7 +1666,7 @@ func TestWitnessCreationAndConsumption(t *testing.T) {
|
||||||
SafeBlockHash: common.Hash{},
|
SafeBlockHash: common.Hash{},
|
||||||
FinalizedBlockHash: common.Hash{},
|
FinalizedBlockHash: common.Hash{},
|
||||||
}
|
}
|
||||||
_, err := api.ForkchoiceUpdatedWithWitnessV3(fcState, &blockParams)
|
_, err := api.ForkchoiceUpdatedWithWitnessV3(context.Background(), fcState, &blockParams)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("error preparing payload, err=%v", err)
|
t.Fatalf("error preparing payload, err=%v", err)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -126,7 +126,7 @@ func NewSimulatedBeacon(period uint64, feeRecipient common.Address, eth *eth.Eth
|
||||||
// if genesis block, send forkchoiceUpdated to trigger transition to PoS
|
// if genesis block, send forkchoiceUpdated to trigger transition to PoS
|
||||||
if block.Number.Sign() == 0 {
|
if block.Number.Sign() == 0 {
|
||||||
version := payloadVersion(eth.BlockChain().Config(), block.Time)
|
version := payloadVersion(eth.BlockChain().Config(), block.Time)
|
||||||
if _, err := engineAPI.forkchoiceUpdated(current, nil, version, false); err != nil {
|
if _, err := engineAPI.forkchoiceUpdated(context.Background(), current, nil, version, false); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -212,7 +212,16 @@ func (c *SimulatedBeacon) sealBlock(withdrawals []*types.Withdrawal, timestamp u
|
||||||
slotNumber := uint64(0)
|
slotNumber := uint64(0)
|
||||||
attribute.SlotNumber = &slotNumber
|
attribute.SlotNumber = &slotNumber
|
||||||
}
|
}
|
||||||
fcResponse, err := c.engineAPI.forkchoiceUpdated(c.curForkchoiceState, attribute, version, false)
|
|
||||||
|
// Create a server span for forkchoiceUpdated with payload attributes,
|
||||||
|
// simulating an incoming engine API request from a real consensus client.
|
||||||
|
fcCtx, fcSpanEnd := telemetry.StartServerSpan(context.Background(), tracer, telemetry.RPCInfo{
|
||||||
|
System: "jsonrpc",
|
||||||
|
Service: "engine",
|
||||||
|
Method: "forkchoiceUpdatedV" + fmt.Sprintf("%d", version),
|
||||||
|
})
|
||||||
|
fcResponse, err := c.engineAPI.forkchoiceUpdated(fcCtx, c.curForkchoiceState, attribute, version, false)
|
||||||
|
fcSpanEnd(&err)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
@ -226,7 +235,15 @@ func (c *SimulatedBeacon) sealBlock(withdrawals []*types.Withdrawal, timestamp u
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Create a server span for getPayload, simulating the consensus client
|
||||||
|
// coming back to retrieve the built payload.
|
||||||
|
_, gpSpanEnd := telemetry.StartServerSpan(context.Background(), tracer, telemetry.RPCInfo{
|
||||||
|
System: "jsonrpc",
|
||||||
|
Service: "engine",
|
||||||
|
Method: "getPayloadV" + fmt.Sprintf("%d", version),
|
||||||
|
})
|
||||||
envelope, err := c.engineAPI.getPayload(*fcResponse.PayloadID, true, nil, nil)
|
envelope, err := c.engineAPI.getPayload(*fcResponse.PayloadID, true, nil, nil)
|
||||||
|
gpSpanEnd(&err)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
@ -274,6 +291,7 @@ func (c *SimulatedBeacon) sealBlock(withdrawals []*types.Withdrawal, timestamp u
|
||||||
Service: "engine",
|
Service: "engine",
|
||||||
Method: "newPayloadV" + fmt.Sprintf("%d", version),
|
Method: "newPayloadV" + fmt.Sprintf("%d", version),
|
||||||
})
|
})
|
||||||
|
|
||||||
// Mark the payload as canon
|
// Mark the payload as canon
|
||||||
_, err = c.engineAPI.newPayload(npCtx, *payload, blobHashes, beaconRoot, requests, false)
|
_, err = c.engineAPI.newPayload(npCtx, *payload, blobHashes, beaconRoot, requests, false)
|
||||||
npSpanEnd(&err)
|
npSpanEnd(&err)
|
||||||
|
|
@ -282,8 +300,16 @@ func (c *SimulatedBeacon) sealBlock(withdrawals []*types.Withdrawal, timestamp u
|
||||||
}
|
}
|
||||||
c.setCurrentState(payload.BlockHash, finalizedHash)
|
c.setCurrentState(payload.BlockHash, finalizedHash)
|
||||||
|
|
||||||
// Mark the block containing the payload as canonical
|
// Create a server span for the final forkchoiceUpdated (no payload attributes),
|
||||||
if _, err = c.engineAPI.forkchoiceUpdated(c.curForkchoiceState, nil, version, false); err != nil {
|
// which sets the new block as the canonical chain head.
|
||||||
|
fcuCtx, fcuSpanEnd := telemetry.StartServerSpan(context.Background(), tracer, telemetry.RPCInfo{
|
||||||
|
System: "jsonrpc",
|
||||||
|
Service: "engine",
|
||||||
|
Method: "forkchoiceUpdatedV" + fmt.Sprintf("%d", version),
|
||||||
|
})
|
||||||
|
_, err = c.engineAPI.forkchoiceUpdated(fcuCtx, c.curForkchoiceState, nil, version, false)
|
||||||
|
fcuSpanEnd(&err)
|
||||||
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
c.lastBlockTime = payload.Timestamp
|
c.lastBlockTime = payload.Timestamp
|
||||||
|
|
@ -349,7 +375,7 @@ func (c *SimulatedBeacon) Rollback() {
|
||||||
func (c *SimulatedBeacon) Fork(parentHash common.Hash) error {
|
func (c *SimulatedBeacon) Fork(parentHash common.Hash) error {
|
||||||
// Ensure no pending transactions.
|
// Ensure no pending transactions.
|
||||||
c.eth.TxPool().Sync()
|
c.eth.TxPool().Sync()
|
||||||
if len(c.eth.TxPool().Pending(txpool.PendingFilter{})) != 0 {
|
if pending, _ := c.eth.TxPool().Pending(txpool.PendingFilter{}); len(pending) != 0 {
|
||||||
return errors.New("pending block dirty")
|
return errors.New("pending block dirty")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -363,7 +389,7 @@ func (c *SimulatedBeacon) Fork(parentHash common.Hash) error {
|
||||||
|
|
||||||
// AdjustTime creates a new block with an adjusted timestamp.
|
// AdjustTime creates a new block with an adjusted timestamp.
|
||||||
func (c *SimulatedBeacon) AdjustTime(adjustment time.Duration) error {
|
func (c *SimulatedBeacon) AdjustTime(adjustment time.Duration) error {
|
||||||
if len(c.eth.TxPool().Pending(txpool.PendingFilter{})) != 0 {
|
if pending, _ := c.eth.TxPool().Pending(txpool.PendingFilter{}); len(pending) != 0 {
|
||||||
return errors.New("could not adjust time on non-empty block")
|
return errors.New("could not adjust time on non-empty block")
|
||||||
}
|
}
|
||||||
parent := c.eth.BlockChain().CurrentBlock()
|
parent := c.eth.BlockChain().CurrentBlock()
|
||||||
|
|
|
||||||
|
|
@ -35,7 +35,7 @@ import (
|
||||||
|
|
||||||
// ForkchoiceUpdatedWithWitnessV1 is analogous to ForkchoiceUpdatedV1, only it
|
// ForkchoiceUpdatedWithWitnessV1 is analogous to ForkchoiceUpdatedV1, only it
|
||||||
// generates an execution witness too if block building was requested.
|
// generates an execution witness too if block building was requested.
|
||||||
func (api *ConsensusAPI) ForkchoiceUpdatedWithWitnessV1(update engine.ForkchoiceStateV1, payloadAttributes *engine.PayloadAttributes) (engine.ForkChoiceResponse, error) {
|
func (api *ConsensusAPI) ForkchoiceUpdatedWithWitnessV1(ctx context.Context, update engine.ForkchoiceStateV1, payloadAttributes *engine.PayloadAttributes) (engine.ForkChoiceResponse, error) {
|
||||||
if payloadAttributes != nil {
|
if payloadAttributes != nil {
|
||||||
switch {
|
switch {
|
||||||
case payloadAttributes.Withdrawals != nil || payloadAttributes.BeaconRoot != nil:
|
case payloadAttributes.Withdrawals != nil || payloadAttributes.BeaconRoot != nil:
|
||||||
|
|
@ -44,12 +44,12 @@ func (api *ConsensusAPI) ForkchoiceUpdatedWithWitnessV1(update engine.Forkchoice
|
||||||
return engine.STATUS_INVALID, paramsErr("fcuV1 called post-shanghai")
|
return engine.STATUS_INVALID, paramsErr("fcuV1 called post-shanghai")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return api.forkchoiceUpdated(update, payloadAttributes, engine.PayloadV1, true)
|
return api.forkchoiceUpdated(ctx, update, payloadAttributes, engine.PayloadV1, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ForkchoiceUpdatedWithWitnessV2 is analogous to ForkchoiceUpdatedV2, only it
|
// ForkchoiceUpdatedWithWitnessV2 is analogous to ForkchoiceUpdatedV2, only it
|
||||||
// generates an execution witness too if block building was requested.
|
// generates an execution witness too if block building was requested.
|
||||||
func (api *ConsensusAPI) ForkchoiceUpdatedWithWitnessV2(update engine.ForkchoiceStateV1, params *engine.PayloadAttributes) (engine.ForkChoiceResponse, error) {
|
func (api *ConsensusAPI) ForkchoiceUpdatedWithWitnessV2(ctx context.Context, update engine.ForkchoiceStateV1, params *engine.PayloadAttributes) (engine.ForkChoiceResponse, error) {
|
||||||
if params != nil {
|
if params != nil {
|
||||||
switch {
|
switch {
|
||||||
case params.BeaconRoot != nil:
|
case params.BeaconRoot != nil:
|
||||||
|
|
@ -62,12 +62,12 @@ func (api *ConsensusAPI) ForkchoiceUpdatedWithWitnessV2(update engine.Forkchoice
|
||||||
return engine.STATUS_INVALID, unsupportedForkErr("fcuV2 must only be called with paris or shanghai payloads")
|
return engine.STATUS_INVALID, unsupportedForkErr("fcuV2 must only be called with paris or shanghai payloads")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return api.forkchoiceUpdated(update, params, engine.PayloadV2, true)
|
return api.forkchoiceUpdated(ctx, update, params, engine.PayloadV2, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ForkchoiceUpdatedWithWitnessV3 is analogous to ForkchoiceUpdatedV3, only it
|
// ForkchoiceUpdatedWithWitnessV3 is analogous to ForkchoiceUpdatedV3, only it
|
||||||
// generates an execution witness too if block building was requested.
|
// generates an execution witness too if block building was requested.
|
||||||
func (api *ConsensusAPI) ForkchoiceUpdatedWithWitnessV3(update engine.ForkchoiceStateV1, params *engine.PayloadAttributes) (engine.ForkChoiceResponse, error) {
|
func (api *ConsensusAPI) ForkchoiceUpdatedWithWitnessV3(ctx context.Context, update engine.ForkchoiceStateV1, params *engine.PayloadAttributes) (engine.ForkChoiceResponse, error) {
|
||||||
if params != nil {
|
if params != nil {
|
||||||
switch {
|
switch {
|
||||||
case params.Withdrawals == nil:
|
case params.Withdrawals == nil:
|
||||||
|
|
@ -82,7 +82,7 @@ func (api *ConsensusAPI) ForkchoiceUpdatedWithWitnessV3(update engine.Forkchoice
|
||||||
// hash, even if params are wrong. To do this we need to split up
|
// hash, even if params are wrong. To do this we need to split up
|
||||||
// forkchoiceUpdate into a function that only updates the head and then a
|
// forkchoiceUpdate into a function that only updates the head and then a
|
||||||
// function that kicks off block construction.
|
// function that kicks off block construction.
|
||||||
return api.forkchoiceUpdated(update, params, engine.PayloadV3, true)
|
return api.forkchoiceUpdated(ctx, update, params, engine.PayloadV3, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewPayloadWithWitnessV1 is analogous to NewPayloadV1, only it also generates
|
// NewPayloadWithWitnessV1 is analogous to NewPayloadV1, only it also generates
|
||||||
|
|
|
||||||
|
|
@ -81,9 +81,10 @@ func (api *DownloaderAPI) eventLoop() {
|
||||||
prog.TxIndexFinishedBlocks = txProg.Indexed
|
prog.TxIndexFinishedBlocks = txProg.Indexed
|
||||||
prog.TxIndexRemainingBlocks = txProg.Remaining
|
prog.TxIndexRemainingBlocks = txProg.Remaining
|
||||||
}
|
}
|
||||||
remain, err := api.chain.StateIndexProgress()
|
stateRemain, trienodeRemain, err := api.chain.StateIndexProgress()
|
||||||
if err == nil {
|
if err == nil {
|
||||||
prog.StateIndexRemaining = remain
|
prog.StateIndexRemaining = stateRemain
|
||||||
|
prog.TrienodeIndexRemaining = trienodeRemain
|
||||||
}
|
}
|
||||||
return prog
|
return prog
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -259,8 +259,8 @@ func (dlp *downloadTesterPeer) RequestBodies(hashes []common.Hash, sink chan *et
|
||||||
// RequestReceipts constructs a getReceipts method associated with a particular
|
// RequestReceipts constructs a getReceipts method associated with a particular
|
||||||
// peer in the download tester. The returned function can be used to retrieve
|
// peer in the download tester. The returned function can be used to retrieve
|
||||||
// batches of block receipts from the particularly requested peer.
|
// batches of block receipts from the particularly requested peer.
|
||||||
func (dlp *downloadTesterPeer) RequestReceipts(hashes []common.Hash, sink chan *eth.Response) (*eth.Request, error) {
|
func (dlp *downloadTesterPeer) RequestReceipts(hashes []common.Hash, gasUsed []uint64, timestamps []uint64, sink chan *eth.Response) (*eth.Request, error) {
|
||||||
blobs := eth.ServiceGetReceiptsQuery(dlp.chain, hashes)
|
blobs := eth.ServiceGetReceiptsQuery69(dlp.chain, hashes)
|
||||||
receipts := make([]types.Receipts, blobs.Len())
|
receipts := make([]types.Receipts, blobs.Len())
|
||||||
|
|
||||||
// compute hashes
|
// compute hashes
|
||||||
|
|
@ -370,7 +370,7 @@ func (dlp *downloadTesterPeer) RequestTrieNodes(id uint64, root common.Hash, cou
|
||||||
Paths: encPaths,
|
Paths: encPaths,
|
||||||
Bytes: uint64(bytes),
|
Bytes: uint64(bytes),
|
||||||
}
|
}
|
||||||
nodes, _ := snap.ServiceGetTrieNodesQuery(dlp.chain, req, time.Now())
|
nodes, _ := snap.ServiceGetTrieNodesQuery(dlp.chain, req)
|
||||||
go dlp.dl.downloader.SnapSyncer.OnTrieNodes(dlp, id, nodes)
|
go dlp.dl.downloader.SnapSyncer.OnTrieNodes(dlp, id, nodes)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -78,11 +78,17 @@ func (q *receiptQueue) request(peer *peerConnection, req *fetchRequest, resCh ch
|
||||||
if q.receiptFetchHook != nil {
|
if q.receiptFetchHook != nil {
|
||||||
q.receiptFetchHook(req.Headers)
|
q.receiptFetchHook(req.Headers)
|
||||||
}
|
}
|
||||||
hashes := make([]common.Hash, 0, len(req.Headers))
|
var (
|
||||||
|
gasUsed = make([]uint64, 0, len(req.Headers))
|
||||||
|
timestamps = make([]uint64, 0, len(req.Headers))
|
||||||
|
hashes = make([]common.Hash, 0, len(req.Headers))
|
||||||
|
)
|
||||||
for _, header := range req.Headers {
|
for _, header := range req.Headers {
|
||||||
hashes = append(hashes, header.Hash())
|
hashes = append(hashes, header.Hash())
|
||||||
|
gasUsed = append(gasUsed, header.GasUsed)
|
||||||
|
timestamps = append(timestamps, header.Time)
|
||||||
}
|
}
|
||||||
return peer.peer.RequestReceipts(hashes, resCh)
|
return peer.peer.RequestReceipts(hashes, gasUsed, timestamps, resCh)
|
||||||
}
|
}
|
||||||
|
|
||||||
// deliver is responsible for taking a generic response packet from the concurrent
|
// deliver is responsible for taking a generic response packet from the concurrent
|
||||||
|
|
|
||||||
|
|
@ -60,7 +60,7 @@ type Peer interface {
|
||||||
RequestHeadersByNumber(uint64, int, int, bool, chan *eth.Response) (*eth.Request, error)
|
RequestHeadersByNumber(uint64, int, int, bool, chan *eth.Response) (*eth.Request, error)
|
||||||
|
|
||||||
RequestBodies([]common.Hash, chan *eth.Response) (*eth.Request, error)
|
RequestBodies([]common.Hash, chan *eth.Response) (*eth.Request, error)
|
||||||
RequestReceipts([]common.Hash, chan *eth.Response) (*eth.Request, error)
|
RequestReceipts([]common.Hash, []uint64, []uint64, chan *eth.Response) (*eth.Request, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// newPeerConnection creates a new downloader peer.
|
// newPeerConnection creates a new downloader peer.
|
||||||
|
|
|
||||||
|
|
@ -208,7 +208,7 @@ func (p *skeletonTestPeer) RequestBodies([]common.Hash, chan *eth.Response) (*et
|
||||||
panic("skeleton sync must not request block bodies")
|
panic("skeleton sync must not request block bodies")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *skeletonTestPeer) RequestReceipts([]common.Hash, chan *eth.Response) (*eth.Request, error) {
|
func (p *skeletonTestPeer) RequestReceipts([]common.Hash, []uint64, []uint64, chan *eth.Response) (*eth.Request, error) {
|
||||||
panic("skeleton sync must not request receipts")
|
panic("skeleton sync must not request receipts")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -536,6 +536,9 @@ func (api *FilterAPI) GetFilterLogs(ctx context.Context, id rpc.ID) ([]*types.Lo
|
||||||
if f.crit.ToBlock != nil {
|
if f.crit.ToBlock != nil {
|
||||||
end = f.crit.ToBlock.Int64()
|
end = f.crit.ToBlock.Int64()
|
||||||
}
|
}
|
||||||
|
if begin >= 0 && begin < int64(api.events.backend.HistoryPruningCutoff()) {
|
||||||
|
return nil, &history.PrunedHistoryError{}
|
||||||
|
}
|
||||||
// Construct the range filter
|
// Construct the range filter
|
||||||
filter = api.sys.NewRangeFilter(begin, end, f.crit.Addresses, f.crit.Topics, api.rangeLimit)
|
filter = api.sys.NewRangeFilter(begin, end, f.crit.Addresses, f.crit.Topics, api.rangeLimit)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,6 @@ package filters
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
|
||||||
"math"
|
"math"
|
||||||
"math/big"
|
"math/big"
|
||||||
"slices"
|
"slices"
|
||||||
|
|
@ -147,7 +146,7 @@ func (f *Filter) Logs(ctx context.Context) ([]*types.Log, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if f.rangeLimit != 0 && (end-begin) > f.rangeLimit {
|
if f.rangeLimit != 0 && (end-begin) > f.rangeLimit {
|
||||||
return nil, fmt.Errorf("exceed maximum block range: %d", f.rangeLimit)
|
return nil, invalidParamsErr("exceed maximum block range %d", f.rangeLimit)
|
||||||
}
|
}
|
||||||
return f.rangeLogs(ctx, begin, end)
|
return f.rangeLogs(ctx, begin, end)
|
||||||
}
|
}
|
||||||
|
|
@ -390,7 +389,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()
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ package filters
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
"math/big"
|
"math/big"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
@ -357,7 +358,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),
|
||||||
|
|
@ -633,7 +635,19 @@ func TestRangeLimit(t *testing.T) {
|
||||||
// Set rangeLimit to 5, but request a range of 9 (end - begin = 9, from 0 to 9)
|
// Set rangeLimit to 5, but request a range of 9 (end - begin = 9, from 0 to 9)
|
||||||
filter := sys.NewRangeFilter(0, 9, nil, nil, 5)
|
filter := sys.NewRangeFilter(0, 9, nil, nil, 5)
|
||||||
_, err = filter.Logs(context.Background())
|
_, err = filter.Logs(context.Background())
|
||||||
if err == nil || !strings.Contains(err.Error(), "exceed maximum block range") {
|
if err == nil {
|
||||||
t.Fatalf("expected range limit error, got %v", err)
|
t.Fatal("expected range limit error, got nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
var re rpc.Error
|
||||||
|
if errors.As(err, &re) {
|
||||||
|
if re.ErrorCode() != -32602 {
|
||||||
|
t.Fatalf("expected error code -32602, got %d", re.ErrorCode())
|
||||||
|
}
|
||||||
|
if re.Error() != "exceed maximum block range 5" {
|
||||||
|
t.Fatalf("expected error message 'exceed maximum block range 5', got %q", re.Error())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
t.Fatalf("expected rpc error, got %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue