mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-22 20:56:42 +00:00
Merge branch 'ethereum:master' into master
This commit is contained in:
commit
624503cdcf
70 changed files with 19429 additions and 11365 deletions
6
.github/workflows/go.yml
vendored
6
.github/workflows/go.yml
vendored
|
|
@ -10,7 +10,7 @@ on:
|
|||
jobs:
|
||||
lint:
|
||||
name: Lint
|
||||
runs-on: self-hosted-ghr
|
||||
runs-on: [self-hosted-ghr, size-s-x64]
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
|
|
@ -37,7 +37,7 @@ jobs:
|
|||
test:
|
||||
name: Test
|
||||
needs: lint
|
||||
runs-on: self-hosted-ghr
|
||||
runs-on: [self-hosted-ghr, size-l-x64]
|
||||
strategy:
|
||||
matrix:
|
||||
go:
|
||||
|
|
@ -55,4 +55,4 @@ jobs:
|
|||
cache: false
|
||||
|
||||
- name: Run tests
|
||||
run: go run build/ci.go test
|
||||
run: go run build/ci.go test -p 8
|
||||
|
|
|
|||
22
appveyor.yml
22
appveyor.yml
|
|
@ -2,7 +2,6 @@ clone_depth: 5
|
|||
version: "{branch}.{build}"
|
||||
|
||||
image:
|
||||
- Ubuntu
|
||||
- Visual Studio 2019
|
||||
|
||||
environment:
|
||||
|
|
@ -17,25 +16,6 @@ install:
|
|||
- go version
|
||||
|
||||
for:
|
||||
# Linux has its own script without -arch and -cc.
|
||||
# The linux builder also runs lint.
|
||||
- matrix:
|
||||
only:
|
||||
- image: Ubuntu
|
||||
build_script:
|
||||
- go run build/ci.go lint
|
||||
- go run build/ci.go check_generate
|
||||
- go run build/ci.go check_baddeps
|
||||
- go run build/ci.go install -dlgo
|
||||
test_script:
|
||||
- go run build/ci.go test -dlgo -short
|
||||
|
||||
# linux/386 is disabled.
|
||||
- matrix:
|
||||
exclude:
|
||||
- image: Ubuntu
|
||||
GETH_ARCH: 386
|
||||
|
||||
# Windows builds for amd64 + 386.
|
||||
- matrix:
|
||||
only:
|
||||
|
|
@ -56,4 +36,4 @@ for:
|
|||
- go run build/ci.go archive -arch %GETH_ARCH% -type zip -signer WINDOWS_SIGNING_KEY -upload gethstore/builds
|
||||
- go run build/ci.go nsis -arch %GETH_ARCH% -signer WINDOWS_SIGNING_KEY -upload gethstore/builds
|
||||
test_script:
|
||||
- go run build/ci.go test -dlgo -arch %GETH_ARCH% -cc %GETH_CC% -short
|
||||
- go run build/ci.go test -dlgo -arch %GETH_ARCH% -cc %GETH_CC% -short -skip-spectests
|
||||
|
|
|
|||
28
build/ci.go
28
build/ci.go
|
|
@ -281,20 +281,26 @@ func buildFlags(env build.Environment, staticLinking bool, buildTags []string) (
|
|||
|
||||
func doTest(cmdline []string) {
|
||||
var (
|
||||
dlgo = flag.Bool("dlgo", false, "Download Go and build with it")
|
||||
arch = flag.String("arch", "", "Run tests for given architecture")
|
||||
cc = flag.String("cc", "", "Sets C compiler binary")
|
||||
coverage = flag.Bool("coverage", false, "Whether to record code coverage")
|
||||
verbose = flag.Bool("v", false, "Whether to log verbosely")
|
||||
race = flag.Bool("race", false, "Execute the race detector")
|
||||
short = flag.Bool("short", false, "Pass the 'short'-flag to go test")
|
||||
cachedir = flag.String("cachedir", "./build/cache", "directory for caching downloads")
|
||||
dlgo = flag.Bool("dlgo", false, "Download Go and build with it")
|
||||
arch = flag.String("arch", "", "Run tests for given architecture")
|
||||
cc = flag.String("cc", "", "Sets C compiler binary")
|
||||
coverage = flag.Bool("coverage", false, "Whether to record code coverage")
|
||||
verbose = flag.Bool("v", false, "Whether to log verbosely")
|
||||
race = flag.Bool("race", false, "Execute the race detector")
|
||||
short = flag.Bool("short", false, "Pass the 'short'-flag to go test")
|
||||
cachedir = flag.String("cachedir", "./build/cache", "directory for caching downloads")
|
||||
skipspectests = flag.Bool("skip-spectests", false, "Skip downloading execution-spec-tests fixtures")
|
||||
threads = flag.Int("p", 1, "Number of CPU threads to use for testing")
|
||||
)
|
||||
flag.CommandLine.Parse(cmdline)
|
||||
|
||||
// Get test fixtures.
|
||||
// Load checksums file (needed for both spec tests and dlgo)
|
||||
csdb := download.MustLoadChecksums("build/checksums.txt")
|
||||
downloadSpecTestFixtures(csdb, *cachedir)
|
||||
|
||||
// Get test fixtures.
|
||||
if !*skipspectests {
|
||||
downloadSpecTestFixtures(csdb, *cachedir)
|
||||
}
|
||||
|
||||
// Configure the toolchain.
|
||||
tc := build.GoToolchain{GOARCH: *arch, CC: *cc}
|
||||
|
|
@ -315,7 +321,7 @@ func doTest(cmdline []string) {
|
|||
|
||||
// Test a single package at a time. CI builders are slow
|
||||
// and some tests run into timeouts under load.
|
||||
gotest.Args = append(gotest.Args, "-p", "1")
|
||||
gotest.Args = append(gotest.Args, "-p", fmt.Sprintf("%d", *threads))
|
||||
if *coverage {
|
||||
gotest.Args = append(gotest.Args, "-covermode=atomic", "-cover")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
#!/bin/sh
|
||||
|
||||
hivechain generate \
|
||||
--pos \
|
||||
--fork-interval 6 \
|
||||
--tx-interval 1 \
|
||||
--length 500 \
|
||||
--length 600 \
|
||||
--outdir testdata \
|
||||
--lastfork cancun \
|
||||
--lastfork prague \
|
||||
--outputs accounts,genesis,chain,headstate,txinfo,headblock,headfcu,newpayload,forkenv
|
||||
|
|
|
|||
|
|
@ -86,3 +86,9 @@ func protoOffset(proto Proto) uint64 {
|
|||
panic("unhandled protocol")
|
||||
}
|
||||
}
|
||||
|
||||
// msgTypePtr is the constraint for protocol message types.
|
||||
type msgTypePtr[U any] interface {
|
||||
*U
|
||||
Kind() byte
|
||||
}
|
||||
|
|
|
|||
|
|
@ -86,9 +86,9 @@ func (s *Suite) TestSnapGetAccountRange(t *utesting.T) {
|
|||
root: root,
|
||||
startingHash: zero,
|
||||
limitHash: ffHash,
|
||||
expAccounts: 86,
|
||||
expAccounts: 67,
|
||||
expFirst: firstKey,
|
||||
expLast: common.HexToHash("0x445cb5c1278fdce2f9cbdb681bdd76c52f8e50e41dbd9e220242a69ba99ac099"),
|
||||
expLast: common.HexToHash("0x622e662246601dd04f996289ce8b85e86db7bb15bb17f86487ec9d543ddb6f9a"),
|
||||
desc: "In this test, we request the entire state range, but limit the response to 4000 bytes.",
|
||||
},
|
||||
{
|
||||
|
|
@ -96,9 +96,9 @@ func (s *Suite) TestSnapGetAccountRange(t *utesting.T) {
|
|||
root: root,
|
||||
startingHash: zero,
|
||||
limitHash: ffHash,
|
||||
expAccounts: 65,
|
||||
expAccounts: 49,
|
||||
expFirst: firstKey,
|
||||
expLast: common.HexToHash("0x2e6fe1362b3e388184fd7bf08e99e74170b26361624ffd1c5f646da7067b58b6"),
|
||||
expLast: common.HexToHash("0x445cb5c1278fdce2f9cbdb681bdd76c52f8e50e41dbd9e220242a69ba99ac099"),
|
||||
desc: "In this test, we request the entire state range, but limit the response to 3000 bytes.",
|
||||
},
|
||||
{
|
||||
|
|
@ -106,9 +106,9 @@ func (s *Suite) TestSnapGetAccountRange(t *utesting.T) {
|
|||
root: root,
|
||||
startingHash: zero,
|
||||
limitHash: ffHash,
|
||||
expAccounts: 44,
|
||||
expAccounts: 34,
|
||||
expFirst: firstKey,
|
||||
expLast: common.HexToHash("0x1c3f74249a4892081ba0634a819aec9ed25f34c7653f5719b9098487e65ab595"),
|
||||
expLast: common.HexToHash("0x2ef46ebd2073cecde499c2e8df028ad79a26d57bfaa812c4c6f7eb4c9617b913"),
|
||||
desc: "In this test, we request the entire state range, but limit the response to 2000 bytes.",
|
||||
},
|
||||
{
|
||||
|
|
@ -177,9 +177,9 @@ The server should return the first available account.`,
|
|||
root: root,
|
||||
startingHash: firstKey,
|
||||
limitHash: ffHash,
|
||||
expAccounts: 86,
|
||||
expAccounts: 67,
|
||||
expFirst: firstKey,
|
||||
expLast: common.HexToHash("0x445cb5c1278fdce2f9cbdb681bdd76c52f8e50e41dbd9e220242a69ba99ac099"),
|
||||
expLast: common.HexToHash("0x622e662246601dd04f996289ce8b85e86db7bb15bb17f86487ec9d543ddb6f9a"),
|
||||
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.`,
|
||||
},
|
||||
|
|
@ -188,9 +188,9 @@ The server should return the first available account of the state as the first i
|
|||
root: root,
|
||||
startingHash: hashAdd(firstKey, 1),
|
||||
limitHash: ffHash,
|
||||
expAccounts: 86,
|
||||
expAccounts: 67,
|
||||
expFirst: secondKey,
|
||||
expLast: common.HexToHash("0x4615e5f5df5b25349a00ad313c6cd0436b6c08ee5826e33a018661997f85ebaa"),
|
||||
expLast: common.HexToHash("0x66192e4c757fba1cdc776e6737008f42d50370d3cd801db3624274283bf7cd63"),
|
||||
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.`,
|
||||
},
|
||||
|
|
@ -226,9 +226,9 @@ server to return no data because genesis is older than 127 blocks.`,
|
|||
root: s.chain.RootAt(int(s.chain.Head().Number().Uint64()) - 127),
|
||||
startingHash: zero,
|
||||
limitHash: ffHash,
|
||||
expAccounts: 84,
|
||||
expAccounts: 66,
|
||||
expFirst: firstKey,
|
||||
expLast: common.HexToHash("0x580aa878e2f92d113a12c0a3ce3c21972b03dbe80786858d49a72097e2c491a3"),
|
||||
expLast: common.HexToHash("0x729953a43ed6c913df957172680a17e5735143ad767bda8f58ac84ec62fbec5e"),
|
||||
desc: `This test requests data at a state root that is 127 blocks old.
|
||||
We expect the server to have this state available.`,
|
||||
},
|
||||
|
|
@ -657,8 +657,8 @@ The server should reject the request.`,
|
|||
// 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
|
||||
// way. So you'll have to update this when the test chain is changed.
|
||||
common.HexToHash("0x3e963a69401a70224cbfb8c0cc2249b019041a538675d71ccf80c9328d114e2e"),
|
||||
common.HexToHash("0xd0670d09cdfbf3c6320eb3e92c47c57baa6c226551a2d488c05581091e6b1689"),
|
||||
common.HexToHash("0x5bdc0d6057b35642a16d27223ea5454e5a17a400e28f7328971a5f2a87773b76"),
|
||||
common.HexToHash("0x0a76c9812ca90ffed8ee4d191e683f93386b6e50cfe3679c0760d27510aa7fc5"),
|
||||
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,
|
||||
|
|
@ -678,8 +678,8 @@ The server should reject the request.`,
|
|||
// be updated when the test chain is changed.
|
||||
expHashes: []common.Hash{
|
||||
empty,
|
||||
common.HexToHash("0xd0670d09cdfbf3c6320eb3e92c47c57baa6c226551a2d488c05581091e6b1689"),
|
||||
common.HexToHash("0x3e963a69401a70224cbfb8c0cc2249b019041a538675d71ccf80c9328d114e2e"),
|
||||
common.HexToHash("0x0a76c9812ca90ffed8ee4d191e683f93386b6e50cfe3679c0760d27510aa7fc5"),
|
||||
common.HexToHash("0x5bdc0d6057b35642a16d27223ea5454e5a17a400e28f7328971a5f2a87773b76"),
|
||||
},
|
||||
},
|
||||
|
||||
|
|
|
|||
|
|
@ -196,6 +196,7 @@ to check if the node disconnects after receiving multiple invalid requests.`)
|
|||
func (s *Suite) TestSimultaneousRequests(t *utesting.T) {
|
||||
t.Log(`This test requests blocks headers from the node, performing two requests
|
||||
concurrently, with different request IDs.`)
|
||||
|
||||
conn, err := s.dialAndPeer(nil)
|
||||
if err != nil {
|
||||
t.Fatalf("peering failed: %v", err)
|
||||
|
|
@ -235,37 +236,36 @@ concurrently, with different request IDs.`)
|
|||
}
|
||||
|
||||
// Wait for responses.
|
||||
headers1 := new(eth.BlockHeadersPacket)
|
||||
if err := conn.ReadMsg(ethProto, eth.BlockHeadersMsg, &headers1); err != nil {
|
||||
t.Fatalf("error reading block headers msg: %v", err)
|
||||
}
|
||||
if got, want := headers1.RequestId, req1.RequestId; got != want {
|
||||
t.Fatalf("unexpected request id in response: got %d, want %d", got, want)
|
||||
}
|
||||
headers2 := new(eth.BlockHeadersPacket)
|
||||
if err := conn.ReadMsg(ethProto, eth.BlockHeadersMsg, &headers2); err != nil {
|
||||
t.Fatalf("error reading block headers msg: %v", err)
|
||||
}
|
||||
if got, want := headers2.RequestId, req2.RequestId; got != want {
|
||||
t.Fatalf("unexpected request id in response: got %d, want %d", got, want)
|
||||
// Note they can arrive in either order.
|
||||
resp, err := collectResponses(conn, 2, func(msg *eth.BlockHeadersPacket) uint64 {
|
||||
if msg.RequestId != 111 && msg.RequestId != 222 {
|
||||
t.Fatalf("response with unknown request ID: %v", msg.RequestId)
|
||||
}
|
||||
return msg.RequestId
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Check received headers for accuracy.
|
||||
// Check if headers match.
|
||||
resp1 := resp[111]
|
||||
if expected, err := s.chain.GetHeaders(req1); err != nil {
|
||||
t.Fatalf("failed to get expected headers for request 1: %v", err)
|
||||
} else if !headersMatch(expected, headers1.BlockHeadersRequest) {
|
||||
t.Fatalf("header mismatch: \nexpected %v \ngot %v", expected, headers1)
|
||||
} else if !headersMatch(expected, resp1.BlockHeadersRequest) {
|
||||
t.Fatalf("header mismatch for request ID %v: \nexpected %v \ngot %v", 111, expected, resp1)
|
||||
}
|
||||
resp2 := resp[222]
|
||||
if expected, err := s.chain.GetHeaders(req2); err != nil {
|
||||
t.Fatalf("failed to get expected headers for request 2: %v", err)
|
||||
} else if !headersMatch(expected, headers2.BlockHeadersRequest) {
|
||||
t.Fatalf("header mismatch: \nexpected %v \ngot %v", expected, headers2)
|
||||
} else if !headersMatch(expected, resp2.BlockHeadersRequest) {
|
||||
t.Fatalf("header mismatch for request ID %v: \nexpected %v \ngot %v", 222, expected, resp2)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Suite) TestSameRequestID(t *utesting.T) {
|
||||
t.Log(`This test requests block headers, performing two concurrent requests with the
|
||||
same request ID. The node should handle the request by responding to both requests.`)
|
||||
|
||||
conn, err := s.dialAndPeer(nil)
|
||||
if err != nil {
|
||||
t.Fatalf("peering failed: %v", err)
|
||||
|
|
@ -289,7 +289,7 @@ same request ID. The node should handle the request by responding to both reques
|
|||
Origin: eth.HashOrNumber{
|
||||
Number: 33,
|
||||
},
|
||||
Amount: 2,
|
||||
Amount: 3,
|
||||
},
|
||||
}
|
||||
|
||||
|
|
@ -301,35 +301,52 @@ same request ID. The node should handle the request by responding to both reques
|
|||
t.Fatalf("failed to write to connection: %v", err)
|
||||
}
|
||||
|
||||
// Wait for the responses.
|
||||
headers1 := new(eth.BlockHeadersPacket)
|
||||
if err := conn.ReadMsg(ethProto, eth.BlockHeadersMsg, &headers1); err != nil {
|
||||
t.Fatalf("error reading from connection: %v", err)
|
||||
}
|
||||
if got, want := headers1.RequestId, request1.RequestId; got != want {
|
||||
t.Fatalf("unexpected request id: got %d, want %d", got, want)
|
||||
}
|
||||
headers2 := new(eth.BlockHeadersPacket)
|
||||
if err := conn.ReadMsg(ethProto, eth.BlockHeadersMsg, &headers2); err != nil {
|
||||
t.Fatalf("error reading from connection: %v", err)
|
||||
}
|
||||
if got, want := headers2.RequestId, request2.RequestId; got != want {
|
||||
t.Fatalf("unexpected request id: got %d, want %d", got, want)
|
||||
// Wait for the responses. They can arrive in either order, and we can't tell them
|
||||
// apart by their request ID, so use the number of headers instead.
|
||||
resp, err := collectResponses(conn, 2, func(msg *eth.BlockHeadersPacket) uint64 {
|
||||
id := uint64(len(msg.BlockHeadersRequest))
|
||||
if id != 2 && id != 3 {
|
||||
t.Fatalf("invalid number of headers in response: %d", id)
|
||||
}
|
||||
return id
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Check if headers match.
|
||||
resp1 := resp[2]
|
||||
if expected, err := s.chain.GetHeaders(request1); err != nil {
|
||||
t.Fatalf("failed to get expected block headers: %v", err)
|
||||
} else if !headersMatch(expected, headers1.BlockHeadersRequest) {
|
||||
t.Fatalf("header mismatch: \nexpected %v \ngot %v", expected, headers1)
|
||||
t.Fatalf("failed to get expected headers for request 1: %v", err)
|
||||
} else if !headersMatch(expected, resp1.BlockHeadersRequest) {
|
||||
t.Fatalf("headers mismatch: \nexpected %v \ngot %v", expected, resp1)
|
||||
}
|
||||
resp2 := resp[3]
|
||||
if expected, err := s.chain.GetHeaders(request2); err != nil {
|
||||
t.Fatalf("failed to get expected block headers: %v", err)
|
||||
} else if !headersMatch(expected, headers2.BlockHeadersRequest) {
|
||||
t.Fatalf("header mismatch: \nexpected %v \ngot %v", expected, headers2)
|
||||
t.Fatalf("failed to get expected headers for request 2: %v", err)
|
||||
} else if !headersMatch(expected, resp2.BlockHeadersRequest) {
|
||||
t.Fatalf("headers mismatch: \nexpected %v \ngot %v", expected, resp2)
|
||||
}
|
||||
}
|
||||
|
||||
// collectResponses waits for n messages of type T on the given connection.
|
||||
// The messsages are collected according to the 'identity' function.
|
||||
func collectResponses[T any, P msgTypePtr[T]](conn *Conn, n int, identity func(P) uint64) (map[uint64]P, error) {
|
||||
resp := make(map[uint64]P, n)
|
||||
for range n {
|
||||
r := new(T)
|
||||
if err := conn.ReadMsg(ethProto, eth.BlockHeadersMsg, r); err != nil {
|
||||
return resp, fmt.Errorf("read error: %v", err)
|
||||
}
|
||||
id := identity(r)
|
||||
if resp[id] != nil {
|
||||
return resp, fmt.Errorf("duplicate response %v", r)
|
||||
}
|
||||
resp[id] = r
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (s *Suite) TestZeroRequestID(t *utesting.T) {
|
||||
t.Log(`This test sends a GetBlockHeaders message with a request-id of zero,
|
||||
and expects a response.`)
|
||||
|
|
@ -887,7 +904,7 @@ func (s *Suite) makeBlobTxs(count, blobs int, discriminator byte) (txs types.Tra
|
|||
from, nonce := s.chain.GetSender(5)
|
||||
for i := 0; i < count; i++ {
|
||||
// Make blob data, max of 2 blobs per tx.
|
||||
blobdata := make([]byte, blobs%3)
|
||||
blobdata := make([]byte, min(blobs, 2))
|
||||
for i := range blobdata {
|
||||
blobdata[i] = discriminator
|
||||
blobs -= 1
|
||||
|
|
|
|||
BIN
cmd/devp2p/internal/ethtest/testdata/chain.rlp
vendored
BIN
cmd/devp2p/internal/ethtest/testdata/chain.rlp
vendored
Binary file not shown.
|
|
@ -1,20 +1,27 @@
|
|||
{
|
||||
"HIVE_CANCUN_TIMESTAMP": "840",
|
||||
"HIVE_CANCUN_BLOB_BASE_FEE_UPDATE_FRACTION": "3338477",
|
||||
"HIVE_CANCUN_BLOB_MAX": "6",
|
||||
"HIVE_CANCUN_BLOB_TARGET": "3",
|
||||
"HIVE_CANCUN_TIMESTAMP": "60",
|
||||
"HIVE_CHAIN_ID": "3503995874084926",
|
||||
"HIVE_FORK_ARROW_GLACIER": "60",
|
||||
"HIVE_FORK_BERLIN": "48",
|
||||
"HIVE_FORK_BYZANTIUM": "18",
|
||||
"HIVE_FORK_CONSTANTINOPLE": "24",
|
||||
"HIVE_FORK_GRAY_GLACIER": "66",
|
||||
"HIVE_FORK_ARROW_GLACIER": "0",
|
||||
"HIVE_FORK_BERLIN": "0",
|
||||
"HIVE_FORK_BYZANTIUM": "0",
|
||||
"HIVE_FORK_CONSTANTINOPLE": "0",
|
||||
"HIVE_FORK_GRAY_GLACIER": "0",
|
||||
"HIVE_FORK_HOMESTEAD": "0",
|
||||
"HIVE_FORK_ISTANBUL": "36",
|
||||
"HIVE_FORK_LONDON": "54",
|
||||
"HIVE_FORK_MUIR_GLACIER": "42",
|
||||
"HIVE_FORK_PETERSBURG": "30",
|
||||
"HIVE_FORK_SPURIOUS": "12",
|
||||
"HIVE_FORK_TANGERINE": "6",
|
||||
"HIVE_MERGE_BLOCK_ID": "72",
|
||||
"HIVE_FORK_ISTANBUL": "0",
|
||||
"HIVE_FORK_LONDON": "0",
|
||||
"HIVE_FORK_MUIR_GLACIER": "0",
|
||||
"HIVE_FORK_PETERSBURG": "0",
|
||||
"HIVE_FORK_SPURIOUS": "0",
|
||||
"HIVE_FORK_TANGERINE": "0",
|
||||
"HIVE_MERGE_BLOCK_ID": "0",
|
||||
"HIVE_NETWORK_ID": "3503995874084926",
|
||||
"HIVE_SHANGHAI_TIMESTAMP": "780",
|
||||
"HIVE_TERMINAL_TOTAL_DIFFICULTY": "9454784"
|
||||
"HIVE_PRAGUE_BLOB_BASE_FEE_UPDATE_FRACTION": "5007716",
|
||||
"HIVE_PRAGUE_BLOB_MAX": "9",
|
||||
"HIVE_PRAGUE_BLOB_TARGET": "6",
|
||||
"HIVE_PRAGUE_TIMESTAMP": "120",
|
||||
"HIVE_SHANGHAI_TIMESTAMP": "0",
|
||||
"HIVE_TERMINAL_TOTAL_DIFFICULTY": "131072"
|
||||
}
|
||||
|
|
@ -2,28 +2,35 @@
|
|||
"config": {
|
||||
"chainId": 3503995874084926,
|
||||
"homesteadBlock": 0,
|
||||
"eip150Block": 6,
|
||||
"eip155Block": 12,
|
||||
"eip158Block": 12,
|
||||
"byzantiumBlock": 18,
|
||||
"constantinopleBlock": 24,
|
||||
"petersburgBlock": 30,
|
||||
"istanbulBlock": 36,
|
||||
"muirGlacierBlock": 42,
|
||||
"berlinBlock": 48,
|
||||
"londonBlock": 54,
|
||||
"arrowGlacierBlock": 60,
|
||||
"grayGlacierBlock": 66,
|
||||
"mergeNetsplitBlock": 72,
|
||||
"shanghaiTime": 780,
|
||||
"cancunTime": 840,
|
||||
"terminalTotalDifficulty": 9454784,
|
||||
"eip150Block": 0,
|
||||
"eip155Block": 0,
|
||||
"eip158Block": 0,
|
||||
"byzantiumBlock": 0,
|
||||
"constantinopleBlock": 0,
|
||||
"petersburgBlock": 0,
|
||||
"istanbulBlock": 0,
|
||||
"muirGlacierBlock": 0,
|
||||
"berlinBlock": 0,
|
||||
"londonBlock": 0,
|
||||
"arrowGlacierBlock": 0,
|
||||
"grayGlacierBlock": 0,
|
||||
"mergeNetsplitBlock": 0,
|
||||
"shanghaiTime": 0,
|
||||
"cancunTime": 60,
|
||||
"pragueTime": 120,
|
||||
"terminalTotalDifficulty": 131072,
|
||||
"depositContractAddress": "0x0000000000000000000000000000000000000000",
|
||||
"ethash": {},
|
||||
"blobSchedule": {
|
||||
"cancun": {
|
||||
"target": 3,
|
||||
"max": 6,
|
||||
"baseFeeUpdateFraction": 3338477
|
||||
},
|
||||
"prague": {
|
||||
"target": 6,
|
||||
"max": 9,
|
||||
"baseFeeUpdateFraction": 5007716
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
@ -35,6 +42,18 @@
|
|||
"mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
|
||||
"coinbase": "0x0000000000000000000000000000000000000000",
|
||||
"alloc": {
|
||||
"00000961ef480eb55e80d19ad83579a64c007002": {
|
||||
"code": "0x3373fffffffffffffffffffffffffffffffffffffffe1460cb5760115f54807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff146101f457600182026001905f5b5f82111560685781019083028483029004916001019190604d565b909390049250505036603814608857366101f457346101f4575f5260205ff35b34106101f457600154600101600155600354806003026004013381556001015f35815560010160203590553360601b5f5260385f601437604c5fa0600101600355005b6003546002548082038060101160df575060105b5f5b8181146101835782810160030260040181604c02815460601b8152601401816001015481526020019060020154807fffffffffffffffffffffffffffffffff00000000000000000000000000000000168252906010019060401c908160381c81600701538160301c81600601538160281c81600501538160201c81600401538160181c81600301538160101c81600201538160081c81600101535360010160e1565b910180921461019557906002556101a0565b90505f6002555f6003555b5f54807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff14156101cd57505f5b6001546002828201116101e25750505f6101e8565b01600290035b5f555f600155604c025ff35b5f5ffd",
|
||||
"balance": "0x1"
|
||||
},
|
||||
"0000bbddc7ce488642fb579f8b00f3a590007251": {
|
||||
"code": "0x3373fffffffffffffffffffffffffffffffffffffffe1460d35760115f54807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1461019a57600182026001905f5b5f82111560685781019083028483029004916001019190604d565b9093900492505050366060146088573661019a573461019a575f5260205ff35b341061019a57600154600101600155600354806004026004013381556001015f358155600101602035815560010160403590553360601b5f5260605f60143760745fa0600101600355005b6003546002548082038060021160e7575060025b5f5b8181146101295782810160040260040181607402815460601b815260140181600101548152602001816002015481526020019060030154905260010160e9565b910180921461013b5790600255610146565b90505f6002555f6003555b5f54807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff141561017357505f5b6001546001828201116101885750505f61018e565b01600190035b5f555f6001556074025ff35b5f5ffd",
|
||||
"balance": "0x1"
|
||||
},
|
||||
"0000f90827f1c53a10cb7a02335b175320002935": {
|
||||
"code": "0x3373fffffffffffffffffffffffffffffffffffffffe14604657602036036042575f35600143038111604257611fff81430311604257611fff9006545f5260205ff35b5f5ffd5b5f35611fff60014303065500",
|
||||
"balance": "0x1"
|
||||
},
|
||||
"000f3df6d732807ef1319fb7b8bb8522d0beac02": {
|
||||
"code": "0x3373fffffffffffffffffffffffffffffffffffffffe14604d57602036146024575f5ffd5b5f35801560495762001fff810690815414603c575f5ffd5b62001fff01545f5260205ff35b5f5ffd5b62001fff42064281555f359062001fff015500",
|
||||
"balance": "0x2a"
|
||||
|
|
@ -81,6 +100,10 @@
|
|||
"7435ed30a8b4aeb0877cef0c6e8cffe834eb865f": {
|
||||
"balance": "0xc097ce7bc90715b34b9f1000000000"
|
||||
},
|
||||
"7dcd17433742f4c0ca53122ab541d0ba67fc27df": {
|
||||
"code": "0x3680600080376000206000548082558060010160005560005263656d697460206000a2",
|
||||
"balance": "0x0"
|
||||
},
|
||||
"83c7e323d189f18725ac510004fdc2941f8c4a78": {
|
||||
"balance": "0xc097ce7bc90715b34b9f1000000000"
|
||||
},
|
||||
|
|
@ -112,7 +135,7 @@
|
|||
"number": "0x0",
|
||||
"gasUsed": "0x0",
|
||||
"parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
|
||||
"baseFeePerGas": null,
|
||||
"baseFeePerGas": "0x3b9aca00",
|
||||
"excessBlobGas": null,
|
||||
"blobGasUsed": null
|
||||
}
|
||||
}
|
||||
|
|
@ -1,16 +1,16 @@
|
|||
{
|
||||
"parentHash": "0x96a73007443980c5e0985dfbb45279aa496dadea16918ad42c65c0bf8122ec39",
|
||||
"parentHash": "0x65151b101682b54cd08ba226f640c14c86176865ff9bfc57e0147dadaeac34bb",
|
||||
"sha3Uncles": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347",
|
||||
"miner": "0x0000000000000000000000000000000000000000",
|
||||
"stateRoot": "0xea4c1f4d9fa8664c22574c5b2f948a78c4b1a753cebc1861e7fb5b1aa21c5a94",
|
||||
"transactionsRoot": "0xecda39025fc4c609ce778d75eed0aa53b65ce1e3d1373b34bad8578cc31e5b48",
|
||||
"receiptsRoot": "0x056b23fbba480696b65fe5a59b8f2148a1299103c4f57df839233af2cf4ca2d2",
|
||||
"stateRoot": "0xce423ebc60fc7764a43f09f1fe3ae61eef25e3eb8d09b1108f7e7eb77dfff5e6",
|
||||
"transactionsRoot": "0x7ec1ae3989efa75d7bcc766e5e2443afa8a89a5fda42ebba90050e7e702980f7",
|
||||
"receiptsRoot": "0xfe160832b1ca85f38c6674cb0aae3a24693bc49be56e2ecdf3698b71a794de86",
|
||||
"logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
|
||||
"difficulty": "0x0",
|
||||
"number": "0x1f4",
|
||||
"gasLimit": "0x47e7c40",
|
||||
"gasUsed": "0x5208",
|
||||
"timestamp": "0x1388",
|
||||
"number": "0x258",
|
||||
"gasLimit": "0x23f3e20",
|
||||
"gasUsed": "0x19d36",
|
||||
"timestamp": "0x1770",
|
||||
"extraData": "0x",
|
||||
"mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
|
||||
"nonce": "0x0000000000000000",
|
||||
|
|
@ -18,6 +18,7 @@
|
|||
"withdrawalsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
|
||||
"blobGasUsed": "0x0",
|
||||
"excessBlobGas": "0x0",
|
||||
"parentBeaconBlockRoot": "0xf653da50cdff4733f13f7a5e338290e883bdf04adf3f112709728063ea965d6c",
|
||||
"hash": "0x36a166f0dcd160fc5e5c61c9a7c2d7f236d9175bf27f43aaa2150e291f092ef7"
|
||||
"parentBeaconBlockRoot": "0xf5003fc8f92358e790a114bce93ce1d9c283c85e1787f8d7d56714d3489b49e6",
|
||||
"requestsHash": "0xe3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
||||
"hash": "0xce8d86ba17a2ec303155f0e264c58a4b8f94ce3436274cf1924f91acdb7502d0"
|
||||
}
|
||||
|
|
@ -1,12 +1,12 @@
|
|||
{
|
||||
"jsonrpc": "2.0",
|
||||
"id": "fcu500",
|
||||
"id": "fcu600",
|
||||
"method": "engine_forkchoiceUpdatedV3",
|
||||
"params": [
|
||||
{
|
||||
"headBlockHash": "0x36a166f0dcd160fc5e5c61c9a7c2d7f236d9175bf27f43aaa2150e291f092ef7",
|
||||
"safeBlockHash": "0x36a166f0dcd160fc5e5c61c9a7c2d7f236d9175bf27f43aaa2150e291f092ef7",
|
||||
"finalizedBlockHash": "0x36a166f0dcd160fc5e5c61c9a7c2d7f236d9175bf27f43aaa2150e291f092ef7"
|
||||
"headBlockHash": "0xce8d86ba17a2ec303155f0e264c58a4b8f94ce3436274cf1924f91acdb7502d0",
|
||||
"safeBlockHash": "0xce8d86ba17a2ec303155f0e264c58a4b8f94ce3436274cf1924f91acdb7502d0",
|
||||
"finalizedBlockHash": "0xce8d86ba17a2ec303155f0e264c58a4b8f94ce3436274cf1924f91acdb7502d0"
|
||||
},
|
||||
null
|
||||
]
|
||||
|
|
|
|||
6492
cmd/devp2p/internal/ethtest/testdata/headstate.json
vendored
6492
cmd/devp2p/internal/ethtest/testdata/headstate.json
vendored
File diff suppressed because it is too large
Load diff
15735
cmd/devp2p/internal/ethtest/testdata/newpayload.json
vendored
15735
cmd/devp2p/internal/ethtest/testdata/newpayload.json
vendored
File diff suppressed because it is too large
Load diff
5390
cmd/devp2p/internal/ethtest/testdata/txinfo.json
vendored
5390
cmd/devp2p/internal/ethtest/testdata/txinfo.json
vendored
File diff suppressed because it is too large
Load diff
|
|
@ -9,7 +9,7 @@ require (
|
|||
|
||||
require (
|
||||
github.com/StackExchange/wmi v1.2.1 // indirect
|
||||
github.com/VictoriaMetrics/fastcache v1.12.2 // indirect
|
||||
github.com/VictoriaMetrics/fastcache v1.13.0 // indirect
|
||||
github.com/bits-and-blooms/bitset v1.20.0 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/consensys/gnark-crypto v0.18.0 // indirect
|
||||
|
|
@ -24,7 +24,7 @@ require (
|
|||
github.com/ferranbt/fastssz v0.1.4 // indirect
|
||||
github.com/go-ole/go-ole v1.3.0 // indirect
|
||||
github.com/gofrs/flock v0.12.1 // indirect
|
||||
github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb // indirect
|
||||
github.com/golang/snappy v1.0.0 // indirect
|
||||
github.com/holiman/bloomfilter/v2 v2.0.3 // indirect
|
||||
github.com/holiman/uint256 v1.3.2 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.0.9 // indirect
|
||||
|
|
|
|||
|
|
@ -2,15 +2,14 @@ github.com/DataDog/zstd v1.4.5 h1:EndNeuB0l9syBZhut0wns3gV1hL8zX8LIu6ZiVHWLIQ=
|
|||
github.com/DataDog/zstd v1.4.5/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo=
|
||||
github.com/StackExchange/wmi v1.2.1 h1:VIkavFPXSjcnS+O8yTq7NI32k0R5Aj+v39y29VYDOSA=
|
||||
github.com/StackExchange/wmi v1.2.1/go.mod h1:rcmrprowKIVzvc+NUiLncP2uuArMWLCbu9SBzvHz7e8=
|
||||
github.com/VictoriaMetrics/fastcache v1.12.2 h1:N0y9ASrJ0F6h0QaC3o6uJb3NIZ9VKLjCM7NQbSmF7WI=
|
||||
github.com/VictoriaMetrics/fastcache v1.12.2/go.mod h1:AmC+Nzz1+3G2eCPapF6UcsnkThDcMsQicp4xDukwJYI=
|
||||
github.com/VictoriaMetrics/fastcache v1.13.0 h1:AW4mheMR5Vd9FkAPUv+NH6Nhw+fmbTMGMsNAoA/+4G0=
|
||||
github.com/VictoriaMetrics/fastcache v1.13.0/go.mod h1:hHXhl4DA2fTL2HTZDJFXWgW0LNjo6B+4aj2Wmng3TjU=
|
||||
github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156 h1:eMwmnE/GDgah4HI848JfFxHt+iPb26b4zyfspmqY0/8=
|
||||
github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM=
|
||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||
github.com/bits-and-blooms/bitset v1.20.0 h1:2F+rfL86jE2d/bmw7OhqUg2Sj/1rURkBn3MdfoPyRVU=
|
||||
github.com/bits-and-blooms/bitset v1.20.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8=
|
||||
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/cockroachdb/errors v1.11.3 h1:5bA+k2Y6r+oz/6Z/RFlNeVCesGARKuC6YymtcDrbC/I=
|
||||
|
|
@ -31,7 +30,6 @@ github.com/crate-crypto/go-eth-kzg v1.4.0 h1:WzDGjHk4gFg6YzV0rJOAsTK4z3Qkz5jd4RE
|
|||
github.com/crate-crypto/go-eth-kzg v1.4.0/go.mod h1:J9/u5sWfznSObptgfa92Jq8rTswn6ahQWEuiLHOjCUI=
|
||||
github.com/crate-crypto/go-ipa v0.0.0-20240724233137-53bbb0ceb27a h1:W8mUrRp6NOVl3J+MYp5kPMoUZPp7aOYHtaua31lwRHg=
|
||||
github.com/crate-crypto/go-ipa v0.0.0-20240724233137-53bbb0ceb27a/go.mod h1:sTwzHBvIzm2RfVCGNEBZgRyjwK40bVoun3ZnGOCafNM=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/deckarep/golang-set/v2 v2.6.0 h1:XfcQbWM1LlMB8BsJ8N9vW5ehnnPVIw0je80NsVHagjM=
|
||||
|
|
@ -61,9 +59,8 @@ github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
|||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
||||
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb h1:PBC98N2aIaM3XXiurYmW7fx4GZkL8feAMVq7nEjURHk=
|
||||
github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/golang/snappy v1.0.0 h1:Oy607GVXHs7RtbggtPBnr2RmDArIsAefDwvrdWvRhGs=
|
||||
github.com/golang/snappy v1.0.0/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/holiman/bloomfilter/v2 v2.0.3 h1:73e0e/V0tCydx14a0SCYS/EWCxgwLZ18CZcZKVu0fao=
|
||||
github.com/holiman/bloomfilter/v2 v2.0.3/go.mod h1:zpoh+gs7qcpqrHr3dB55AMiJwo0iURXE7ZOP9L9hSkA=
|
||||
github.com/holiman/uint256 v1.3.2 h1:a9EgMPSC1AAaj1SZL5zIQD3WbwTuHrMGOerLjGmM/TA=
|
||||
|
|
@ -112,8 +109,6 @@ github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU
|
|||
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
|
||||
github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible h1:Bn1aCHHRnjv4Bl16T8rcaFjYSrGrIZvpiGO6P3Q4GpU=
|
||||
github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/supranational/blst v0.3.16-0.20250831170142-f48500c1fdbe h1:nbdqkIGOGfUAD54q1s2YBcBz/WcsxCO9HUQ4aGV5hUw=
|
||||
|
|
@ -134,7 +129,6 @@ golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k=
|
||||
golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
|
||||
|
|
|
|||
|
|
@ -394,7 +394,7 @@ func NewBlockChain(db ethdb.Database, genesis *Genesis, engine consensus.Engine,
|
|||
bc.statedb = state.NewDatabase(bc.triedb, nil)
|
||||
bc.validator = NewBlockValidator(chainConfig, bc)
|
||||
bc.prefetcher = newStatePrefetcher(chainConfig, bc.hc)
|
||||
bc.processor = NewStateProcessor(chainConfig, bc.hc)
|
||||
bc.processor = NewStateProcessor(bc.hc)
|
||||
|
||||
genesisHeader := bc.GetHeaderByNumber(0)
|
||||
if genesisHeader == nil {
|
||||
|
|
@ -1690,7 +1690,12 @@ func (bc *BlockChain) writeBlockAndSetHead(block *types.Block, receipts []*types
|
|||
// Set new head.
|
||||
bc.writeHeadBlock(block)
|
||||
|
||||
bc.chainFeed.Send(ChainEvent{Header: block.Header()})
|
||||
bc.chainFeed.Send(ChainEvent{
|
||||
Header: block.Header(),
|
||||
Receipts: receipts,
|
||||
Transactions: block.Transactions(),
|
||||
})
|
||||
|
||||
if len(logs) > 0 {
|
||||
bc.logsFeed.Send(logs)
|
||||
}
|
||||
|
|
@ -2342,6 +2347,13 @@ func (bc *BlockChain) recoverAncestors(block *types.Block, makeWitness bool) (co
|
|||
// collectLogs collects the logs that were generated or removed during the
|
||||
// processing of a block. These logs are later announced as deleted or reborn.
|
||||
func (bc *BlockChain) collectLogs(b *types.Block, removed bool) []*types.Log {
|
||||
_, logs := bc.collectReceiptsAndLogs(b, removed)
|
||||
return logs
|
||||
}
|
||||
|
||||
// collectReceiptsAndLogs retrieves receipts from the database and returns both receipts and logs.
|
||||
// This avoids duplicate database reads when both are needed.
|
||||
func (bc *BlockChain) collectReceiptsAndLogs(b *types.Block, removed bool) ([]*types.Receipt, []*types.Log) {
|
||||
var blobGasPrice *big.Int
|
||||
if b.ExcessBlobGas() != nil {
|
||||
blobGasPrice = eip4844.CalcBlobFee(bc.chainConfig, b.Header())
|
||||
|
|
@ -2359,7 +2371,7 @@ func (bc *BlockChain) collectLogs(b *types.Block, removed bool) []*types.Log {
|
|||
logs = append(logs, log)
|
||||
}
|
||||
}
|
||||
return logs
|
||||
return receipts, logs
|
||||
}
|
||||
|
||||
// reorg takes two blocks, an old chain and a new chain and will reconstruct the
|
||||
|
|
@ -2588,8 +2600,14 @@ func (bc *BlockChain) SetCanonical(head *types.Block) (common.Hash, error) {
|
|||
bc.writeHeadBlock(head)
|
||||
|
||||
// Emit events
|
||||
logs := bc.collectLogs(head, false)
|
||||
bc.chainFeed.Send(ChainEvent{Header: head.Header()})
|
||||
receipts, logs := bc.collectReceiptsAndLogs(head, false)
|
||||
|
||||
bc.chainFeed.Send(ChainEvent{
|
||||
Header: head.Header(),
|
||||
Receipts: receipts,
|
||||
Transactions: head.Transactions(),
|
||||
})
|
||||
|
||||
if len(logs) > 0 {
|
||||
bc.logsFeed.Send(logs)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,7 +27,9 @@ type NewTxsEvent struct{ Txs []*types.Transaction }
|
|||
type RemovedLogsEvent struct{ Logs []*types.Log }
|
||||
|
||||
type ChainEvent struct {
|
||||
Header *types.Header
|
||||
Header *types.Header
|
||||
Receipts []*types.Receipt
|
||||
Transactions []*types.Transaction
|
||||
}
|
||||
|
||||
type ChainHeadEvent struct {
|
||||
|
|
|
|||
|
|
@ -25,21 +25,16 @@ import (
|
|||
"github.com/ethereum/go-ethereum/core/tracing"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/core/vm"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"github.com/holiman/uint256"
|
||||
)
|
||||
|
||||
// ChainContext supports retrieving headers and consensus parameters from the
|
||||
// current blockchain to be used during transaction processing.
|
||||
type ChainContext interface {
|
||||
consensus.ChainHeaderReader
|
||||
|
||||
// Engine retrieves the chain's consensus engine.
|
||||
Engine() consensus.Engine
|
||||
|
||||
// GetHeader returns the header corresponding to the hash/number argument pair.
|
||||
GetHeader(common.Hash, uint64) *types.Header
|
||||
|
||||
// Config returns the chain's configuration.
|
||||
Config() *params.ChainConfig
|
||||
}
|
||||
|
||||
// NewEVMBlockContext creates a new context for use in the EVM.
|
||||
|
|
|
|||
|
|
@ -664,15 +664,6 @@ func DeleteReceipts(db ethdb.KeyValueWriter, hash common.Hash, number uint64) {
|
|||
}
|
||||
}
|
||||
|
||||
// storedReceiptRLP is the storage encoding of a receipt.
|
||||
// Re-definition in core/types/receipt.go.
|
||||
// TODO: Re-use the existing definition.
|
||||
type storedReceiptRLP struct {
|
||||
PostStateOrStatus []byte
|
||||
CumulativeGasUsed uint64
|
||||
Logs []*types.Log
|
||||
}
|
||||
|
||||
// ReceiptLogs is a barebone version of ReceiptForStorage which only keeps
|
||||
// the list of logs. When decoding a stored receipt into this object we
|
||||
// avoid creating the bloom filter.
|
||||
|
|
@ -682,11 +673,11 @@ type receiptLogs struct {
|
|||
|
||||
// DecodeRLP implements rlp.Decoder.
|
||||
func (r *receiptLogs) DecodeRLP(s *rlp.Stream) error {
|
||||
var stored storedReceiptRLP
|
||||
if err := s.Decode(&stored); err != nil {
|
||||
var rs types.ReceiptForStorage
|
||||
if err := rs.DecodeRLP(s); err != nil {
|
||||
return err
|
||||
}
|
||||
r.Logs = stored.Logs
|
||||
r.Logs = rs.Logs
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -46,6 +46,27 @@ func DeleteStateHistoryIndexMetadata(db ethdb.KeyValueWriter) {
|
|||
}
|
||||
}
|
||||
|
||||
// ReadTrienodeHistoryIndexMetadata retrieves the metadata of trienode history index.
|
||||
func ReadTrienodeHistoryIndexMetadata(db ethdb.KeyValueReader) []byte {
|
||||
data, _ := db.Get(headTrienodeHistoryIndexKey)
|
||||
return data
|
||||
}
|
||||
|
||||
// WriteTrienodeHistoryIndexMetadata stores the metadata of trienode history index
|
||||
// into database.
|
||||
func WriteTrienodeHistoryIndexMetadata(db ethdb.KeyValueWriter, blob []byte) {
|
||||
if err := db.Put(headTrienodeHistoryIndexKey, blob); err != nil {
|
||||
log.Crit("Failed to store the metadata of trienode history index", "err", err)
|
||||
}
|
||||
}
|
||||
|
||||
// DeleteTrienodeHistoryIndexMetadata removes the metadata of trienode history index.
|
||||
func DeleteTrienodeHistoryIndexMetadata(db ethdb.KeyValueWriter) {
|
||||
if err := db.Delete(headTrienodeHistoryIndexKey); err != nil {
|
||||
log.Crit("Failed to delete the metadata of trienode history index", "err", err)
|
||||
}
|
||||
}
|
||||
|
||||
// ReadAccountHistoryIndex retrieves the account history index with the provided
|
||||
// account address.
|
||||
func ReadAccountHistoryIndex(db ethdb.KeyValueReader, addressHash common.Hash) []byte {
|
||||
|
|
@ -95,6 +116,30 @@ func DeleteStorageHistoryIndex(db ethdb.KeyValueWriter, addressHash common.Hash,
|
|||
}
|
||||
}
|
||||
|
||||
// ReadTrienodeHistoryIndex retrieves the trienode history index with the provided
|
||||
// account address and storage key hash.
|
||||
func ReadTrienodeHistoryIndex(db ethdb.KeyValueReader, addressHash common.Hash, path []byte) []byte {
|
||||
data, err := db.Get(trienodeHistoryIndexKey(addressHash, path))
|
||||
if err != nil || len(data) == 0 {
|
||||
return nil
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
// WriteTrienodeHistoryIndex writes the provided trienode history index into database.
|
||||
func WriteTrienodeHistoryIndex(db ethdb.KeyValueWriter, addressHash common.Hash, path []byte, data []byte) {
|
||||
if err := db.Put(trienodeHistoryIndexKey(addressHash, path), data); err != nil {
|
||||
log.Crit("Failed to store trienode history index", "err", err)
|
||||
}
|
||||
}
|
||||
|
||||
// DeleteTrienodeHistoryIndex deletes the specified trienode index from the database.
|
||||
func DeleteTrienodeHistoryIndex(db ethdb.KeyValueWriter, addressHash common.Hash, path []byte) {
|
||||
if err := db.Delete(trienodeHistoryIndexKey(addressHash, path)); err != nil {
|
||||
log.Crit("Failed to delete trienode history index", "err", err)
|
||||
}
|
||||
}
|
||||
|
||||
// ReadAccountHistoryIndexBlock retrieves the index block with the provided
|
||||
// account address along with the block id.
|
||||
func ReadAccountHistoryIndexBlock(db ethdb.KeyValueReader, addressHash common.Hash, blockID uint32) []byte {
|
||||
|
|
@ -143,6 +188,30 @@ func DeleteStorageHistoryIndexBlock(db ethdb.KeyValueWriter, addressHash common.
|
|||
}
|
||||
}
|
||||
|
||||
// ReadTrienodeHistoryIndexBlock retrieves the index block with the provided state
|
||||
// identifier along with the block id.
|
||||
func ReadTrienodeHistoryIndexBlock(db ethdb.KeyValueReader, addressHash common.Hash, path []byte, blockID uint32) []byte {
|
||||
data, err := db.Get(trienodeHistoryIndexBlockKey(addressHash, path, blockID))
|
||||
if err != nil || len(data) == 0 {
|
||||
return nil
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
// WriteTrienodeHistoryIndexBlock writes the provided index block into database.
|
||||
func WriteTrienodeHistoryIndexBlock(db ethdb.KeyValueWriter, addressHash common.Hash, path []byte, id uint32, data []byte) {
|
||||
if err := db.Put(trienodeHistoryIndexBlockKey(addressHash, path, id), data); err != nil {
|
||||
log.Crit("Failed to store trienode index block", "err", err)
|
||||
}
|
||||
}
|
||||
|
||||
// DeleteTrienodeHistoryIndexBlock deletes the specified index block from the database.
|
||||
func DeleteTrienodeHistoryIndexBlock(db ethdb.KeyValueWriter, addressHash common.Hash, path []byte, id uint32) {
|
||||
if err := db.Delete(trienodeHistoryIndexBlockKey(addressHash, path, id)); err != nil {
|
||||
log.Crit("Failed to delete trienode index block", "err", err)
|
||||
}
|
||||
}
|
||||
|
||||
// increaseKey increase the input key by one bit. Return nil if the entire
|
||||
// addition operation overflows.
|
||||
func increaseKey(key []byte) []byte {
|
||||
|
|
@ -155,14 +224,26 @@ func increaseKey(key []byte) []byte {
|
|||
return nil
|
||||
}
|
||||
|
||||
// DeleteStateHistoryIndex completely removes all history indexing data, including
|
||||
// DeleteStateHistoryIndexes completely removes all history indexing data, including
|
||||
// indexes for accounts and storages.
|
||||
//
|
||||
// Note, this method assumes the storage space with prefix `StateHistoryIndexPrefix`
|
||||
// is exclusively occupied by the history indexing data!
|
||||
func DeleteStateHistoryIndex(db ethdb.KeyValueRangeDeleter) {
|
||||
start := StateHistoryIndexPrefix
|
||||
limit := increaseKey(bytes.Clone(StateHistoryIndexPrefix))
|
||||
func DeleteStateHistoryIndexes(db ethdb.KeyValueRangeDeleter) {
|
||||
DeleteHistoryByRange(db, StateHistoryAccountMetadataPrefix)
|
||||
DeleteHistoryByRange(db, StateHistoryStorageMetadataPrefix)
|
||||
DeleteHistoryByRange(db, StateHistoryAccountBlockPrefix)
|
||||
DeleteHistoryByRange(db, StateHistoryStorageBlockPrefix)
|
||||
}
|
||||
|
||||
// DeleteTrienodeHistoryIndexes completely removes all trienode history indexing data.
|
||||
func DeleteTrienodeHistoryIndexes(db ethdb.KeyValueRangeDeleter) {
|
||||
DeleteHistoryByRange(db, TrienodeHistoryMetadataPrefix)
|
||||
DeleteHistoryByRange(db, TrienodeHistoryBlockPrefix)
|
||||
}
|
||||
|
||||
// DeleteHistoryByRange completely removes all database entries with the specific prefix.
|
||||
// Note, this method assumes the space with the given prefix is exclusively occupied!
|
||||
func DeleteHistoryByRange(db ethdb.KeyValueRangeDeleter, prefix []byte) {
|
||||
start := prefix
|
||||
limit := increaseKey(bytes.Clone(prefix))
|
||||
|
||||
// Try to remove the data in the range by a loop, as the leveldb
|
||||
// doesn't support the native range deletion.
|
||||
|
|
|
|||
|
|
@ -170,9 +170,11 @@ func ReadStateHistoryMetaList(db ethdb.AncientReaderOp, start uint64, count uint
|
|||
return db.AncientRange(stateHistoryMeta, start-1, count, 0)
|
||||
}
|
||||
|
||||
// ReadStateAccountIndex retrieves the state root corresponding to the specified
|
||||
// state history. Compute the position of state history in freezer by minus one
|
||||
// since the id of first state history starts from one(zero for initial state).
|
||||
// ReadStateAccountIndex retrieves the account index blob for the specified
|
||||
// state history. The index contains fixed-size entries with offsets and lengths
|
||||
// into the concatenated account data table. Compute the position of state
|
||||
// history in freezer by minus one since the id of first state history starts
|
||||
// from one (zero for initial state).
|
||||
func ReadStateAccountIndex(db ethdb.AncientReaderOp, id uint64) []byte {
|
||||
blob, err := db.Ancient(stateHistoryAccountIndex, id-1)
|
||||
if err != nil {
|
||||
|
|
@ -181,9 +183,11 @@ func ReadStateAccountIndex(db ethdb.AncientReaderOp, id uint64) []byte {
|
|||
return blob
|
||||
}
|
||||
|
||||
// ReadStateStorageIndex retrieves the state root corresponding to the specified
|
||||
// state history. Compute the position of state history in freezer by minus one
|
||||
// since the id of first state history starts from one(zero for initial state).
|
||||
// ReadStateStorageIndex retrieves the storage index blob for the specified
|
||||
// state history. The index contains fixed-size entries that locate storage slot
|
||||
// data in the concatenated storage data table. Compute the position of state
|
||||
// history in freezer by minus one since the id of first state history starts
|
||||
// from one (zero for initial state).
|
||||
func ReadStateStorageIndex(db ethdb.AncientReaderOp, id uint64) []byte {
|
||||
blob, err := db.Ancient(stateHistoryStorageIndex, id-1)
|
||||
if err != nil {
|
||||
|
|
@ -192,9 +196,10 @@ func ReadStateStorageIndex(db ethdb.AncientReaderOp, id uint64) []byte {
|
|||
return blob
|
||||
}
|
||||
|
||||
// ReadStateAccountHistory retrieves the state root corresponding to the specified
|
||||
// state history. Compute the position of state history in freezer by minus one
|
||||
// since the id of first state history starts from one(zero for initial state).
|
||||
// ReadStateAccountHistory retrieves the concatenated account data blob for the
|
||||
// specified state history. Offsets and lengths are resolved via the account
|
||||
// index. Compute the position of state history in freezer by minus one since
|
||||
// the id of first state history starts from one (zero for initial state).
|
||||
func ReadStateAccountHistory(db ethdb.AncientReaderOp, id uint64) []byte {
|
||||
blob, err := db.Ancient(stateHistoryAccountData, id-1)
|
||||
if err != nil {
|
||||
|
|
@ -203,9 +208,11 @@ func ReadStateAccountHistory(db ethdb.AncientReaderOp, id uint64) []byte {
|
|||
return blob
|
||||
}
|
||||
|
||||
// ReadStateStorageHistory retrieves the state root corresponding to the specified
|
||||
// state history. Compute the position of state history in freezer by minus one
|
||||
// since the id of first state history starts from one(zero for initial state).
|
||||
// ReadStateStorageHistory retrieves the concatenated storage slot data blob for
|
||||
// the specified state history. Locations are resolved via the account and
|
||||
// storage indexes. Compute the position of state history in freezer by minus
|
||||
// one since the id of first state history starts from one (zero for initial
|
||||
// state).
|
||||
func ReadStateStorageHistory(db ethdb.AncientReaderOp, id uint64) []byte {
|
||||
blob, err := db.Ancient(stateHistoryStorageData, id-1)
|
||||
if err != nil {
|
||||
|
|
@ -292,3 +299,76 @@ func WriteStateHistory(db ethdb.AncientWriter, id uint64, meta []byte, accountIn
|
|||
})
|
||||
return err
|
||||
}
|
||||
|
||||
// ReadTrienodeHistory retrieves the trienode history corresponding to the specified id.
|
||||
// Compute the position of trienode history in freezer by minus one since the id of first
|
||||
// trienode history starts from one(zero for initial state).
|
||||
func ReadTrienodeHistory(db ethdb.AncientReaderOp, id uint64) ([]byte, []byte, []byte, error) {
|
||||
header, err := db.Ancient(trienodeHistoryHeaderTable, id-1)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
keySection, err := db.Ancient(trienodeHistoryKeySectionTable, id-1)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
valueSection, err := db.Ancient(trienodeHistoryValueSectionTable, id-1)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
return header, keySection, valueSection, nil
|
||||
}
|
||||
|
||||
// ReadTrienodeHistoryHeader retrieves the header section of trienode history.
|
||||
func ReadTrienodeHistoryHeader(db ethdb.AncientReaderOp, id uint64) ([]byte, error) {
|
||||
return db.Ancient(trienodeHistoryHeaderTable, id-1)
|
||||
}
|
||||
|
||||
// ReadTrienodeHistoryKeySection retrieves the key section of trienode history.
|
||||
func ReadTrienodeHistoryKeySection(db ethdb.AncientReaderOp, id uint64) ([]byte, error) {
|
||||
return db.Ancient(trienodeHistoryKeySectionTable, id-1)
|
||||
}
|
||||
|
||||
// ReadTrienodeHistoryValueSection retrieves the value section of trienode history.
|
||||
func ReadTrienodeHistoryValueSection(db ethdb.AncientReaderOp, id uint64) ([]byte, error) {
|
||||
return db.Ancient(trienodeHistoryValueSectionTable, id-1)
|
||||
}
|
||||
|
||||
// ReadTrienodeHistoryList retrieves the a list of trienode history corresponding
|
||||
// to the specified range.
|
||||
// Compute the position of trienode history in freezer by minus one since the id
|
||||
// of first trienode history starts from one(zero for initial state).
|
||||
func ReadTrienodeHistoryList(db ethdb.AncientReaderOp, start uint64, count uint64) ([][]byte, [][]byte, [][]byte, error) {
|
||||
header, err := db.AncientRange(trienodeHistoryHeaderTable, start-1, count, 0)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
keySection, err := db.AncientRange(trienodeHistoryKeySectionTable, start-1, count, 0)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
valueSection, err := db.AncientRange(trienodeHistoryValueSectionTable, start-1, count, 0)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
if len(header) != len(keySection) || len(header) != len(valueSection) {
|
||||
return nil, nil, nil, errors.New("trienode history is corrupted")
|
||||
}
|
||||
return header, keySection, valueSection, nil
|
||||
}
|
||||
|
||||
// WriteTrienodeHistory writes the provided trienode history to database.
|
||||
// Compute the position of trienode history in freezer by minus one since
|
||||
// the id of first state history starts from one(zero for initial state).
|
||||
func WriteTrienodeHistory(db ethdb.AncientWriter, id uint64, header []byte, keySection []byte, valueSection []byte) error {
|
||||
_, err := db.ModifyAncients(func(op ethdb.AncientWriteOp) error {
|
||||
if err := op.AppendRaw(trienodeHistoryHeaderTable, id-1, header); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := op.AppendRaw(trienodeHistoryKeySectionTable, id-1, keySection); err != nil {
|
||||
return err
|
||||
}
|
||||
return op.AppendRaw(trienodeHistoryValueSectionTable, id-1, valueSection)
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -75,15 +75,38 @@ var stateFreezerTableConfigs = map[string]freezerTableConfig{
|
|||
stateHistoryStorageData: {noSnappy: false, prunable: true},
|
||||
}
|
||||
|
||||
const (
|
||||
trienodeHistoryHeaderTable = "trienode.header"
|
||||
trienodeHistoryKeySectionTable = "trienode.key"
|
||||
trienodeHistoryValueSectionTable = "trienode.value"
|
||||
)
|
||||
|
||||
// trienodeFreezerTableConfigs configures the settings for tables in the trienode freezer.
|
||||
var trienodeFreezerTableConfigs = map[string]freezerTableConfig{
|
||||
trienodeHistoryHeaderTable: {noSnappy: false, prunable: true},
|
||||
|
||||
// Disable snappy compression to allow efficient partial read.
|
||||
trienodeHistoryKeySectionTable: {noSnappy: true, prunable: true},
|
||||
|
||||
// Disable snappy compression to allow efficient partial read.
|
||||
trienodeHistoryValueSectionTable: {noSnappy: true, prunable: true},
|
||||
}
|
||||
|
||||
// The list of identifiers of ancient stores.
|
||||
var (
|
||||
ChainFreezerName = "chain" // the folder name of chain segment ancient store.
|
||||
MerkleStateFreezerName = "state" // the folder name of state history ancient store.
|
||||
VerkleStateFreezerName = "state_verkle" // the folder name of state history ancient store.
|
||||
ChainFreezerName = "chain" // the folder name of chain segment ancient store.
|
||||
MerkleStateFreezerName = "state" // the folder name of state history ancient store.
|
||||
VerkleStateFreezerName = "state_verkle" // the folder name of state history ancient store.
|
||||
MerkleTrienodeFreezerName = "trienode" // the folder name of trienode history ancient store.
|
||||
VerkleTrienodeFreezerName = "trienode_verkle" // the folder name of trienode history ancient store.
|
||||
)
|
||||
|
||||
// freezers the collections of all builtin freezers.
|
||||
var freezers = []string{ChainFreezerName, MerkleStateFreezerName, VerkleStateFreezerName}
|
||||
var freezers = []string{
|
||||
ChainFreezerName,
|
||||
MerkleStateFreezerName, VerkleStateFreezerName,
|
||||
MerkleTrienodeFreezerName, VerkleTrienodeFreezerName,
|
||||
}
|
||||
|
||||
// NewStateFreezer initializes the ancient store for state history.
|
||||
//
|
||||
|
|
@ -103,3 +126,22 @@ func NewStateFreezer(ancientDir string, verkle bool, readOnly bool) (ethdb.Reset
|
|||
}
|
||||
return newResettableFreezer(name, "eth/db/state", readOnly, stateHistoryTableSize, stateFreezerTableConfigs)
|
||||
}
|
||||
|
||||
// NewTrienodeFreezer initializes the ancient store for trienode history.
|
||||
//
|
||||
// - if the empty directory is given, initializes the pure in-memory
|
||||
// trienode freezer (e.g. dev mode).
|
||||
// - if non-empty directory is given, initializes the regular file-based
|
||||
// trienode freezer.
|
||||
func NewTrienodeFreezer(ancientDir string, verkle bool, readOnly bool) (ethdb.ResettableAncientStore, error) {
|
||||
if ancientDir == "" {
|
||||
return NewMemoryFreezer(readOnly, trienodeFreezerTableConfigs), nil
|
||||
}
|
||||
var name string
|
||||
if verkle {
|
||||
name = filepath.Join(ancientDir, VerkleTrienodeFreezerName)
|
||||
} else {
|
||||
name = filepath.Join(ancientDir, MerkleTrienodeFreezerName)
|
||||
}
|
||||
return newResettableFreezer(name, "eth/db/trienode", readOnly, stateHistoryTableSize, trienodeFreezerTableConfigs)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -80,6 +80,10 @@ var (
|
|||
// been indexed.
|
||||
headStateHistoryIndexKey = []byte("LastStateHistoryIndex")
|
||||
|
||||
// headTrienodeHistoryIndexKey tracks the ID of the latest state history that has
|
||||
// been indexed.
|
||||
headTrienodeHistoryIndexKey = []byte("LastTrienodeHistoryIndex")
|
||||
|
||||
// txIndexTailKey tracks the oldest block whose transactions have been indexed.
|
||||
txIndexTailKey = []byte("TransactionIndexTail")
|
||||
|
||||
|
|
@ -125,8 +129,10 @@ var (
|
|||
StateHistoryIndexPrefix = []byte("m") // The global prefix of state history index data
|
||||
StateHistoryAccountMetadataPrefix = []byte("ma") // StateHistoryAccountMetadataPrefix + account address hash => account metadata
|
||||
StateHistoryStorageMetadataPrefix = []byte("ms") // StateHistoryStorageMetadataPrefix + account address hash + storage slot hash => slot metadata
|
||||
TrienodeHistoryMetadataPrefix = []byte("mt") // TrienodeHistoryMetadataPrefix + account address hash + trienode path => trienode metadata
|
||||
StateHistoryAccountBlockPrefix = []byte("mba") // StateHistoryAccountBlockPrefix + account address hash + blockID => account block
|
||||
StateHistoryStorageBlockPrefix = []byte("mbs") // StateHistoryStorageBlockPrefix + account address hash + storage slot hash + blockID => slot block
|
||||
TrienodeHistoryBlockPrefix = []byte("mbt") // TrienodeHistoryBlockPrefix + account address hash + trienode path + blockID => trienode block
|
||||
|
||||
// VerklePrefix is the database prefix for Verkle trie data, which includes:
|
||||
// (a) Trie nodes
|
||||
|
|
@ -395,27 +401,34 @@ func storageHistoryIndexKey(addressHash common.Hash, storageHash common.Hash) []
|
|||
return out
|
||||
}
|
||||
|
||||
// trienodeHistoryIndexKey = TrienodeHistoryMetadataPrefix + addressHash + trienode path
|
||||
func trienodeHistoryIndexKey(addressHash common.Hash, path []byte) []byte {
|
||||
totalLen := len(TrienodeHistoryMetadataPrefix) + common.HashLength + len(path)
|
||||
out := make([]byte, totalLen)
|
||||
|
||||
off := 0
|
||||
off += copy(out[off:], TrienodeHistoryMetadataPrefix)
|
||||
off += copy(out[off:], addressHash.Bytes())
|
||||
copy(out[off:], path)
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
// accountHistoryIndexBlockKey = StateHistoryAccountBlockPrefix + addressHash + blockID
|
||||
func accountHistoryIndexBlockKey(addressHash common.Hash, blockID uint32) []byte {
|
||||
var buf4 [4]byte
|
||||
binary.BigEndian.PutUint32(buf4[:], blockID)
|
||||
|
||||
totalLen := len(StateHistoryAccountBlockPrefix) + common.HashLength + 4
|
||||
out := make([]byte, totalLen)
|
||||
|
||||
off := 0
|
||||
off += copy(out[off:], StateHistoryAccountBlockPrefix)
|
||||
off += copy(out[off:], addressHash.Bytes())
|
||||
copy(out[off:], buf4[:])
|
||||
binary.BigEndian.PutUint32(out[off:], blockID)
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
// storageHistoryIndexBlockKey = StateHistoryStorageBlockPrefix + addressHash + storageHash + blockID
|
||||
func storageHistoryIndexBlockKey(addressHash common.Hash, storageHash common.Hash, blockID uint32) []byte {
|
||||
var buf4 [4]byte
|
||||
binary.BigEndian.PutUint32(buf4[:], blockID)
|
||||
|
||||
totalLen := len(StateHistoryStorageBlockPrefix) + 2*common.HashLength + 4
|
||||
out := make([]byte, totalLen)
|
||||
|
||||
|
|
@ -423,7 +436,21 @@ func storageHistoryIndexBlockKey(addressHash common.Hash, storageHash common.Has
|
|||
off += copy(out[off:], StateHistoryStorageBlockPrefix)
|
||||
off += copy(out[off:], addressHash.Bytes())
|
||||
off += copy(out[off:], storageHash.Bytes())
|
||||
copy(out[off:], buf4[:])
|
||||
binary.BigEndian.PutUint32(out[off:], blockID)
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
// trienodeHistoryIndexBlockKey = TrienodeHistoryBlockPrefix + addressHash + trienode path + blockID
|
||||
func trienodeHistoryIndexBlockKey(addressHash common.Hash, path []byte, blockID uint32) []byte {
|
||||
totalLen := len(TrienodeHistoryBlockPrefix) + common.HashLength + len(path) + 4
|
||||
out := make([]byte, totalLen)
|
||||
|
||||
off := 0
|
||||
off += copy(out[off:], TrienodeHistoryBlockPrefix)
|
||||
off += copy(out[off:], addressHash.Bytes())
|
||||
off += copy(out[off:], path)
|
||||
binary.BigEndian.PutUint32(out[off:], blockID)
|
||||
|
||||
return out
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,18 +35,21 @@ import (
|
|||
//
|
||||
// StateProcessor implements Processor.
|
||||
type StateProcessor struct {
|
||||
config *params.ChainConfig // Chain configuration options
|
||||
chain *HeaderChain // Canonical header chain
|
||||
chain ChainContext // Chain context interface
|
||||
}
|
||||
|
||||
// NewStateProcessor initialises a new StateProcessor.
|
||||
func NewStateProcessor(config *params.ChainConfig, chain *HeaderChain) *StateProcessor {
|
||||
func NewStateProcessor(chain ChainContext) *StateProcessor {
|
||||
return &StateProcessor{
|
||||
config: config,
|
||||
chain: chain,
|
||||
chain: chain,
|
||||
}
|
||||
}
|
||||
|
||||
// chainConfig returns the chain configuration.
|
||||
func (p *StateProcessor) chainConfig() *params.ChainConfig {
|
||||
return p.chain.Config()
|
||||
}
|
||||
|
||||
// Process processes the state changes according to the Ethereum rules by running
|
||||
// the transaction messages using the statedb and applying any rewards to both
|
||||
// the processor (coinbase) and any included uncles.
|
||||
|
|
@ -56,6 +59,7 @@ func NewStateProcessor(config *params.ChainConfig, chain *HeaderChain) *StatePro
|
|||
// transactions failed to execute due to insufficient gas it will return an error.
|
||||
func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg vm.Config) (*ProcessResult, error) {
|
||||
var (
|
||||
config = p.chainConfig()
|
||||
receipts types.Receipts
|
||||
usedGas = new(uint64)
|
||||
header = block.Header()
|
||||
|
|
@ -66,12 +70,12 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg
|
|||
)
|
||||
|
||||
// Mutate the block and state according to any hard-fork specs
|
||||
if p.config.DAOForkSupport && p.config.DAOForkBlock != nil && p.config.DAOForkBlock.Cmp(block.Number()) == 0 {
|
||||
if config.DAOForkSupport && config.DAOForkBlock != nil && config.DAOForkBlock.Cmp(block.Number()) == 0 {
|
||||
misc.ApplyDAOHardFork(statedb)
|
||||
}
|
||||
var (
|
||||
context vm.BlockContext
|
||||
signer = types.MakeSigner(p.config, header.Number, header.Time)
|
||||
signer = types.MakeSigner(config, header.Number, header.Time)
|
||||
)
|
||||
|
||||
// Apply pre-execution system calls.
|
||||
|
|
@ -80,12 +84,12 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg
|
|||
tracingStateDB = state.NewHookedState(statedb, hooks)
|
||||
}
|
||||
context = NewEVMBlockContext(header, p.chain, nil)
|
||||
evm := vm.NewEVM(context, tracingStateDB, p.config, cfg)
|
||||
evm := vm.NewEVM(context, tracingStateDB, config, cfg)
|
||||
|
||||
if beaconRoot := block.BeaconRoot(); beaconRoot != nil {
|
||||
ProcessBeaconBlockRoot(*beaconRoot, evm)
|
||||
}
|
||||
if p.config.IsPrague(block.Number(), block.Time()) || p.config.IsVerkle(block.Number(), block.Time()) {
|
||||
if config.IsPrague(block.Number(), block.Time()) || config.IsVerkle(block.Number(), block.Time()) {
|
||||
ProcessParentBlockHash(block.ParentHash(), evm)
|
||||
}
|
||||
|
||||
|
|
@ -106,10 +110,10 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg
|
|||
}
|
||||
// Read requests if Prague is enabled.
|
||||
var requests [][]byte
|
||||
if p.config.IsPrague(block.Number(), block.Time()) {
|
||||
if config.IsPrague(block.Number(), block.Time()) {
|
||||
requests = [][]byte{}
|
||||
// EIP-6110
|
||||
if err := ParseDepositLogs(&requests, allLogs, p.config); err != nil {
|
||||
if err := ParseDepositLogs(&requests, allLogs, config); err != nil {
|
||||
return nil, fmt.Errorf("failed to parse deposit logs: %w", err)
|
||||
}
|
||||
// EIP-7002
|
||||
|
|
@ -123,7 +127,7 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg
|
|||
}
|
||||
|
||||
// Finalize the block, applying any consensus engine specific extras (e.g. block rewards)
|
||||
p.chain.engine.Finalize(p.chain, header, tracingStateDB, block.Body())
|
||||
p.chain.Engine().Finalize(p.chain, header, tracingStateDB, block.Body())
|
||||
|
||||
return &ProcessResult{
|
||||
Receipts: receipts,
|
||||
|
|
|
|||
|
|
@ -62,7 +62,7 @@ func ExecuteStateless(config *params.ChainConfig, vmconfig vm.Config, block *typ
|
|||
headerCache: lru.NewCache[common.Hash, *types.Header](256),
|
||||
engine: beacon.New(ethash.NewFaker()),
|
||||
}
|
||||
processor := NewStateProcessor(config, chain)
|
||||
processor := NewStateProcessor(chain)
|
||||
validator := NewBlockValidator(config, nil) // No chain, we only validate the state, not the block
|
||||
|
||||
// Run the stateless blocks processing and self-validate certain fields
|
||||
|
|
|
|||
|
|
@ -984,17 +984,24 @@ func (pool *LegacyPool) Add(txs []*types.Transaction, sync bool) []error {
|
|||
|
||||
// addTxsLocked attempts to queue a batch of transactions if they are valid.
|
||||
// The transaction pool lock must be held.
|
||||
// Returns the error for each tx, and the set of accounts that might became promotable.
|
||||
func (pool *LegacyPool) addTxsLocked(txs []*types.Transaction) ([]error, *accountSet) {
|
||||
dirty := newAccountSet(pool.signer)
|
||||
errs := make([]error, len(txs))
|
||||
var (
|
||||
dirty = newAccountSet(pool.signer)
|
||||
errs = make([]error, len(txs))
|
||||
valid int64
|
||||
)
|
||||
for i, tx := range txs {
|
||||
replaced, err := pool.add(tx)
|
||||
errs[i] = err
|
||||
if err == nil && !replaced {
|
||||
dirty.addTx(tx)
|
||||
if err == nil {
|
||||
if !replaced {
|
||||
dirty.addTx(tx)
|
||||
}
|
||||
valid++
|
||||
}
|
||||
}
|
||||
validTxMeter.Mark(int64(len(dirty.accounts)))
|
||||
validTxMeter.Mark(valid)
|
||||
return errs, dirty
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -240,7 +240,7 @@ type extblock struct {
|
|||
//
|
||||
// The receipt's bloom must already calculated for the block's bloom to be
|
||||
// correctly calculated.
|
||||
func NewBlock(header *Header, body *Body, receipts []*Receipt, hasher TrieHasher) *Block {
|
||||
func NewBlock(header *Header, body *Body, receipts []*Receipt, hasher ListHasher) *Block {
|
||||
if body == nil {
|
||||
body = &Body{}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ import (
|
|||
"github.com/ethereum/go-ethereum/rlp"
|
||||
)
|
||||
|
||||
// hasherPool holds LegacyKeccak256 hashers for rlpHash.
|
||||
// hasherPool holds LegacyKeccak256 buffer for rlpHash.
|
||||
var hasherPool = sync.Pool{
|
||||
New: func() interface{} { return crypto.NewKeccakState() },
|
||||
}
|
||||
|
|
@ -75,11 +75,17 @@ func prefixedRlpHash(prefix byte, x interface{}) (h common.Hash) {
|
|||
return h
|
||||
}
|
||||
|
||||
// TrieHasher is the tool used to calculate the hash of derivable list.
|
||||
// This is internal, do not use.
|
||||
type TrieHasher interface {
|
||||
// ListHasher defines the interface for computing the hash of a derivable list.
|
||||
type ListHasher interface {
|
||||
// Reset clears the internal state of the hasher, preparing it for reuse.
|
||||
Reset()
|
||||
Update([]byte, []byte) error
|
||||
|
||||
// Update inserts the given key-value pair into the hasher.
|
||||
// The implementation must copy the provided slices, allowing the caller
|
||||
// to safely modify them after the call returns.
|
||||
Update(key []byte, value []byte) error
|
||||
|
||||
// Hash computes and returns the final hash of all inserted key-value pairs.
|
||||
Hash() common.Hash
|
||||
}
|
||||
|
||||
|
|
@ -91,19 +97,20 @@ type DerivableList interface {
|
|||
EncodeIndex(int, *bytes.Buffer)
|
||||
}
|
||||
|
||||
// encodeForDerive encodes the element in the list at the position i into the buffer.
|
||||
func encodeForDerive(list DerivableList, i int, buf *bytes.Buffer) []byte {
|
||||
buf.Reset()
|
||||
list.EncodeIndex(i, buf)
|
||||
// It's really unfortunate that we need to perform this copy.
|
||||
// StackTrie holds onto the values until Hash is called, so the values
|
||||
// written to it must not alias.
|
||||
return common.CopyBytes(buf.Bytes())
|
||||
return buf.Bytes()
|
||||
}
|
||||
|
||||
// DeriveSha creates the tree hashes of transactions, receipts, and withdrawals in a block header.
|
||||
func DeriveSha(list DerivableList, hasher TrieHasher) common.Hash {
|
||||
func DeriveSha(list DerivableList, hasher ListHasher) common.Hash {
|
||||
hasher.Reset()
|
||||
|
||||
// Allocate a buffer for value encoding. As the hasher is claimed that all
|
||||
// supplied key value pairs will be copied by hasher and safe to reuse the
|
||||
// encoding buffer.
|
||||
valueBuf := encodeBufferPool.Get().(*bytes.Buffer)
|
||||
defer encodeBufferPool.Put(valueBuf)
|
||||
|
||||
|
|
|
|||
|
|
@ -26,12 +26,10 @@ import (
|
|||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"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"
|
||||
"github.com/ethereum/go-ethereum/triedb"
|
||||
)
|
||||
|
||||
func TestDeriveSha(t *testing.T) {
|
||||
|
|
@ -40,7 +38,7 @@ func TestDeriveSha(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
for len(txs) < 1000 {
|
||||
exp := types.DeriveSha(txs, trie.NewEmpty(triedb.NewDatabase(rawdb.NewMemoryDatabase(), nil)))
|
||||
exp := types.DeriveSha(txs, trie.NewListHasher())
|
||||
got := types.DeriveSha(txs, trie.NewStackTrie(nil))
|
||||
if !bytes.Equal(got[:], exp[:]) {
|
||||
t.Fatalf("%d txs: got %x exp %x", len(txs), got, exp)
|
||||
|
|
@ -76,30 +74,45 @@ func TestEIP2718DeriveSha(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
// goos: darwin
|
||||
// goarch: arm64
|
||||
// pkg: github.com/ethereum/go-ethereum/core/types
|
||||
// cpu: Apple M1 Pro
|
||||
// BenchmarkDeriveSha200
|
||||
// BenchmarkDeriveSha200/std_trie
|
||||
// BenchmarkDeriveSha200/std_trie-8 6754 174074 ns/op 80054 B/op 1926 allocs/op
|
||||
// BenchmarkDeriveSha200/stack_trie
|
||||
// BenchmarkDeriveSha200/stack_trie-8 7296 162675 ns/op 745 B/op 19 allocs/op
|
||||
func BenchmarkDeriveSha200(b *testing.B) {
|
||||
txs, err := genTxs(200)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
var exp common.Hash
|
||||
var got common.Hash
|
||||
want := types.DeriveSha(txs, trie.NewListHasher())
|
||||
|
||||
b.Run("std_trie", func(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
var have common.Hash
|
||||
for b.Loop() {
|
||||
exp = types.DeriveSha(txs, trie.NewEmpty(triedb.NewDatabase(rawdb.NewMemoryDatabase(), nil)))
|
||||
have = types.DeriveSha(txs, trie.NewListHasher())
|
||||
}
|
||||
if have != want {
|
||||
b.Errorf("have %x want %x", have, want)
|
||||
}
|
||||
})
|
||||
|
||||
st := trie.NewStackTrie(nil)
|
||||
b.Run("stack_trie", func(b *testing.B) {
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
var have common.Hash
|
||||
for b.Loop() {
|
||||
got = types.DeriveSha(txs, trie.NewStackTrie(nil))
|
||||
st.Reset()
|
||||
have = types.DeriveSha(txs, st)
|
||||
}
|
||||
if have != want {
|
||||
b.Errorf("have %x want %x", have, want)
|
||||
}
|
||||
})
|
||||
if got != exp {
|
||||
b.Errorf("got %x exp %x", got, exp)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFuzzDeriveSha(t *testing.T) {
|
||||
|
|
@ -107,7 +120,7 @@ func TestFuzzDeriveSha(t *testing.T) {
|
|||
rndSeed := mrand.Int()
|
||||
for i := 0; i < 10; i++ {
|
||||
seed := rndSeed + i
|
||||
exp := types.DeriveSha(newDummy(i), trie.NewEmpty(triedb.NewDatabase(rawdb.NewMemoryDatabase(), nil)))
|
||||
exp := types.DeriveSha(newDummy(i), trie.NewListHasher())
|
||||
got := types.DeriveSha(newDummy(i), trie.NewStackTrie(nil))
|
||||
if !bytes.Equal(got[:], exp[:]) {
|
||||
printList(t, newDummy(seed))
|
||||
|
|
@ -135,7 +148,7 @@ func TestDerivableList(t *testing.T) {
|
|||
},
|
||||
}
|
||||
for i, tc := range tcs[1:] {
|
||||
exp := types.DeriveSha(flatList(tc), trie.NewEmpty(triedb.NewDatabase(rawdb.NewMemoryDatabase(), nil)))
|
||||
exp := types.DeriveSha(flatList(tc), trie.NewListHasher())
|
||||
got := types.DeriveSha(flatList(tc), trie.NewStackTrie(nil))
|
||||
if !bytes.Equal(got[:], exp[:]) {
|
||||
t.Fatalf("case %d: got %x exp %x", i, got, exp)
|
||||
|
|
|
|||
|
|
@ -312,6 +312,18 @@ func (d *dummyChain) Config() *params.ChainConfig {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (d *dummyChain) CurrentHeader() *types.Header {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *dummyChain) GetHeaderByNumber(n uint64) *types.Header {
|
||||
return d.GetHeader(common.Hash{}, n)
|
||||
}
|
||||
|
||||
func (d *dummyChain) GetHeaderByHash(h common.Hash) *types.Header {
|
||||
return nil
|
||||
}
|
||||
|
||||
// TestBlockhash tests the blockhash operation. It's a bit special, since it internally
|
||||
// requires access to a chain reader.
|
||||
func TestBlockhash(t *testing.T) {
|
||||
|
|
|
|||
|
|
@ -100,7 +100,7 @@ type SimulatedBeacon struct {
|
|||
|
||||
func payloadVersion(config *params.ChainConfig, time uint64) engine.PayloadVersion {
|
||||
switch config.LatestFork(time) {
|
||||
case forks.Prague, forks.Cancun:
|
||||
case forks.BPO5, forks.BPO4, forks.BPO3, forks.BPO2, forks.BPO1, forks.Osaka, forks.Prague, forks.Cancun:
|
||||
return engine.PayloadV3
|
||||
case forks.Paris, forks.Shanghai:
|
||||
return engine.PayloadV2
|
||||
|
|
|
|||
|
|
@ -43,6 +43,7 @@ var (
|
|||
errPendingLogsUnsupported = errors.New("pending logs are not supported")
|
||||
errExceedMaxTopics = errors.New("exceed max topics")
|
||||
errExceedLogQueryLimit = errors.New("exceed max addresses or topics per search position")
|
||||
errExceedMaxTxHashes = errors.New("exceed max number of transaction hashes allowed per transactionReceipts subscription")
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
@ -50,6 +51,8 @@ const (
|
|||
maxTopics = 4
|
||||
// The maximum number of allowed topics within a topic criteria
|
||||
maxSubTopics = 1000
|
||||
// The maximum number of transaction hash criteria allowed in a single subscription
|
||||
maxTxHashes = 200
|
||||
)
|
||||
|
||||
// filter is a helper struct that holds meta information over the filter type
|
||||
|
|
@ -296,6 +299,83 @@ func (api *FilterAPI) Logs(ctx context.Context, crit FilterCriteria) (*rpc.Subsc
|
|||
return rpcSub, nil
|
||||
}
|
||||
|
||||
// TransactionReceiptsQuery defines criteria for transaction receipts subscription.
|
||||
// Same as ethereum.TransactionReceiptsQuery but with UnmarshalJSON() method.
|
||||
type TransactionReceiptsQuery ethereum.TransactionReceiptsQuery
|
||||
|
||||
// UnmarshalJSON sets *args fields with given data.
|
||||
func (args *TransactionReceiptsQuery) UnmarshalJSON(data []byte) error {
|
||||
type input struct {
|
||||
TransactionHashes []common.Hash `json:"transactionHashes"`
|
||||
}
|
||||
|
||||
var raw input
|
||||
if err := json.Unmarshal(data, &raw); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
args.TransactionHashes = raw.TransactionHashes
|
||||
return nil
|
||||
}
|
||||
|
||||
// TransactionReceipts creates a subscription that fires transaction receipts when transactions are included in blocks.
|
||||
func (api *FilterAPI) TransactionReceipts(ctx context.Context, filter *TransactionReceiptsQuery) (*rpc.Subscription, error) {
|
||||
notifier, supported := rpc.NotifierFromContext(ctx)
|
||||
if !supported {
|
||||
return &rpc.Subscription{}, rpc.ErrNotificationsUnsupported
|
||||
}
|
||||
|
||||
// Validate transaction hashes limit
|
||||
if filter != nil && len(filter.TransactionHashes) > maxTxHashes {
|
||||
return nil, errExceedMaxTxHashes
|
||||
}
|
||||
|
||||
var (
|
||||
rpcSub = notifier.CreateSubscription()
|
||||
matchedReceipts = make(chan []*ReceiptWithTx)
|
||||
txHashes []common.Hash
|
||||
)
|
||||
|
||||
if filter != nil {
|
||||
txHashes = filter.TransactionHashes
|
||||
}
|
||||
|
||||
receiptsSub := api.events.SubscribeTransactionReceipts(txHashes, matchedReceipts)
|
||||
|
||||
go func() {
|
||||
defer receiptsSub.Unsubscribe()
|
||||
|
||||
signer := types.LatestSigner(api.sys.backend.ChainConfig())
|
||||
|
||||
for {
|
||||
select {
|
||||
case receiptsWithTxs := <-matchedReceipts:
|
||||
if len(receiptsWithTxs) > 0 {
|
||||
// Convert to the same format as eth_getTransactionReceipt
|
||||
marshaledReceipts := make([]map[string]interface{}, len(receiptsWithTxs))
|
||||
for i, receiptWithTx := range receiptsWithTxs {
|
||||
marshaledReceipts[i] = ethapi.MarshalReceipt(
|
||||
receiptWithTx.Receipt,
|
||||
receiptWithTx.Receipt.BlockHash,
|
||||
receiptWithTx.Receipt.BlockNumber.Uint64(),
|
||||
signer,
|
||||
receiptWithTx.Transaction,
|
||||
int(receiptWithTx.Receipt.TransactionIndex),
|
||||
)
|
||||
}
|
||||
|
||||
// Send a batch of tx receipts in one notification
|
||||
notifier.Notify(rpcSub.ID, marshaledReceipts)
|
||||
}
|
||||
case <-rpcSub.Err():
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
return rpcSub, nil
|
||||
}
|
||||
|
||||
// FilterCriteria represents a request to create a new filter.
|
||||
// Same as ethereum.FilterQuery but with UnmarshalJSON() method.
|
||||
type FilterCriteria ethereum.FilterQuery
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core"
|
||||
"github.com/ethereum/go-ethereum/core/filtermaps"
|
||||
"github.com/ethereum/go-ethereum/core/history"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
|
|
@ -551,3 +552,70 @@ func bloomFilter(bloom types.Bloom, addresses []common.Address, topics [][]commo
|
|||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// ReceiptWithTx contains a receipt and its corresponding transaction
|
||||
type ReceiptWithTx struct {
|
||||
Receipt *types.Receipt
|
||||
Transaction *types.Transaction
|
||||
}
|
||||
|
||||
// filterReceipts returns the receipts matching the given criteria
|
||||
// In addition to returning receipts, it also returns the corresponding transactions.
|
||||
// This is because receipts only contain low-level data, while user-facing data
|
||||
// may require additional information from the Transaction.
|
||||
func filterReceipts(txHashes []common.Hash, ev core.ChainEvent) []*ReceiptWithTx {
|
||||
var ret []*ReceiptWithTx
|
||||
|
||||
receipts := ev.Receipts
|
||||
txs := ev.Transactions
|
||||
|
||||
if len(receipts) != len(txs) {
|
||||
log.Warn("Receipts and transactions length mismatch", "receipts", len(receipts), "transactions", len(txs))
|
||||
return ret
|
||||
}
|
||||
|
||||
if len(txHashes) == 0 {
|
||||
// No filter, send all receipts with their transactions.
|
||||
ret = make([]*ReceiptWithTx, len(receipts))
|
||||
for i, receipt := range receipts {
|
||||
ret[i] = &ReceiptWithTx{
|
||||
Receipt: receipt,
|
||||
Transaction: txs[i],
|
||||
}
|
||||
}
|
||||
} else if len(txHashes) == 1 {
|
||||
// Filter by single transaction hash.
|
||||
// This is a common case, so we distinguish it from filtering by multiple tx hashes and made a small optimization.
|
||||
for i, receipt := range receipts {
|
||||
if receipt.TxHash == txHashes[0] {
|
||||
ret = append(ret, &ReceiptWithTx{
|
||||
Receipt: receipt,
|
||||
Transaction: txs[i],
|
||||
})
|
||||
break
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Filter by multiple transaction hashes.
|
||||
txHashMap := make(map[common.Hash]bool, len(txHashes))
|
||||
for _, hash := range txHashes {
|
||||
txHashMap[hash] = true
|
||||
}
|
||||
|
||||
for i, receipt := range receipts {
|
||||
if txHashMap[receipt.TxHash] {
|
||||
ret = append(ret, &ReceiptWithTx{
|
||||
Receipt: receipt,
|
||||
Transaction: txs[i],
|
||||
})
|
||||
|
||||
// Early exit if all receipts are found
|
||||
if len(ret) == len(txHashes) {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
|
|
|
|||
|
|
@ -158,6 +158,8 @@ const (
|
|||
PendingTransactionsSubscription
|
||||
// BlocksSubscription queries hashes for blocks that are imported
|
||||
BlocksSubscription
|
||||
// TransactionReceiptsSubscription queries for transaction receipts when transactions are included in blocks
|
||||
TransactionReceiptsSubscription
|
||||
// LastIndexSubscription keeps track of the last index
|
||||
LastIndexSubscription
|
||||
)
|
||||
|
|
@ -182,6 +184,8 @@ type subscription struct {
|
|||
logs chan []*types.Log
|
||||
txs chan []*types.Transaction
|
||||
headers chan *types.Header
|
||||
receipts chan []*ReceiptWithTx
|
||||
txHashes []common.Hash // contains transaction hashes for transactionReceipts subscription filtering
|
||||
installed chan struct{} // closed when the filter is installed
|
||||
err chan error // closed when the filter is uninstalled
|
||||
}
|
||||
|
|
@ -268,6 +272,7 @@ func (sub *Subscription) Unsubscribe() {
|
|||
case <-sub.f.logs:
|
||||
case <-sub.f.txs:
|
||||
case <-sub.f.headers:
|
||||
case <-sub.f.receipts:
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -353,6 +358,7 @@ func (es *EventSystem) subscribeLogs(crit ethereum.FilterQuery, logs chan []*typ
|
|||
logs: logs,
|
||||
txs: make(chan []*types.Transaction),
|
||||
headers: make(chan *types.Header),
|
||||
receipts: make(chan []*ReceiptWithTx),
|
||||
installed: make(chan struct{}),
|
||||
err: make(chan error),
|
||||
}
|
||||
|
|
@ -369,6 +375,7 @@ func (es *EventSystem) SubscribeNewHeads(headers chan *types.Header) *Subscripti
|
|||
logs: make(chan []*types.Log),
|
||||
txs: make(chan []*types.Transaction),
|
||||
headers: headers,
|
||||
receipts: make(chan []*ReceiptWithTx),
|
||||
installed: make(chan struct{}),
|
||||
err: make(chan error),
|
||||
}
|
||||
|
|
@ -385,6 +392,26 @@ func (es *EventSystem) SubscribePendingTxs(txs chan []*types.Transaction) *Subsc
|
|||
logs: make(chan []*types.Log),
|
||||
txs: txs,
|
||||
headers: make(chan *types.Header),
|
||||
receipts: make(chan []*ReceiptWithTx),
|
||||
installed: make(chan struct{}),
|
||||
err: make(chan error),
|
||||
}
|
||||
return es.subscribe(sub)
|
||||
}
|
||||
|
||||
// SubscribeTransactionReceipts creates a subscription that writes transaction receipts for
|
||||
// transactions when they are included in blocks. If txHashes is provided, only receipts
|
||||
// for those specific transaction hashes will be delivered.
|
||||
func (es *EventSystem) SubscribeTransactionReceipts(txHashes []common.Hash, receipts chan []*ReceiptWithTx) *Subscription {
|
||||
sub := &subscription{
|
||||
id: rpc.NewID(),
|
||||
typ: TransactionReceiptsSubscription,
|
||||
created: time.Now(),
|
||||
logs: make(chan []*types.Log),
|
||||
txs: make(chan []*types.Transaction),
|
||||
headers: make(chan *types.Header),
|
||||
receipts: receipts,
|
||||
txHashes: txHashes,
|
||||
installed: make(chan struct{}),
|
||||
err: make(chan error),
|
||||
}
|
||||
|
|
@ -415,6 +442,14 @@ func (es *EventSystem) handleChainEvent(filters filterIndex, ev core.ChainEvent)
|
|||
for _, f := range filters[BlocksSubscription] {
|
||||
f.headers <- ev.Header
|
||||
}
|
||||
|
||||
// Handle transaction receipts subscriptions when a new block is added
|
||||
for _, f := range filters[TransactionReceiptsSubscription] {
|
||||
matchedReceipts := filterReceipts(f.txHashes, ev)
|
||||
if len(matchedReceipts) > 0 {
|
||||
f.receipts <- matchedReceipts
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// eventLoop (un)installs filters and processes mux events.
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ import (
|
|||
"github.com/ethereum/go-ethereum/core/filtermaps"
|
||||
"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/ethdb"
|
||||
"github.com/ethereum/go-ethereum/event"
|
||||
"github.com/ethereum/go-ethereum/internal/ethapi"
|
||||
|
|
@ -781,3 +782,143 @@ func TestPendingTxFilterDeadlock(t *testing.T) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestTransactionReceiptsSubscription tests the transaction receipts subscription functionality
|
||||
func TestTransactionReceiptsSubscription(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
const txNum = 5
|
||||
|
||||
// Setup test environment
|
||||
var (
|
||||
db = rawdb.NewMemoryDatabase()
|
||||
backend, sys = newTestFilterSystem(db, Config{})
|
||||
api = NewFilterAPI(sys)
|
||||
key1, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
|
||||
addr1 = crypto.PubkeyToAddress(key1.PublicKey)
|
||||
signer = types.NewLondonSigner(big.NewInt(1))
|
||||
genesis = &core.Genesis{
|
||||
Alloc: types.GenesisAlloc{addr1: {Balance: big.NewInt(1000000000000000000)}}, // 1 ETH
|
||||
Config: params.TestChainConfig,
|
||||
BaseFee: big.NewInt(params.InitialBaseFee),
|
||||
}
|
||||
_, chain, _ = core.GenerateChainWithGenesis(genesis, ethash.NewFaker(), 1, func(i int, gen *core.BlockGen) {
|
||||
// Add transactions to the block
|
||||
for j := 0; j < txNum; j++ {
|
||||
toAddr := common.HexToAddress("0xb794f5ea0ba39494ce83a213fffba74279579268")
|
||||
tx, _ := types.SignTx(types.NewTx(&types.LegacyTx{
|
||||
Nonce: uint64(j),
|
||||
GasPrice: gen.BaseFee(),
|
||||
Gas: 21000,
|
||||
To: &toAddr,
|
||||
Value: big.NewInt(1000),
|
||||
Data: nil,
|
||||
}), signer, key1)
|
||||
gen.AddTx(tx)
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
// Insert the blocks into the chain
|
||||
blockchain, err := core.NewBlockChain(db, genesis, ethash.NewFaker(), nil)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create tester chain: %v", err)
|
||||
}
|
||||
if n, err := blockchain.InsertChain(chain); err != nil {
|
||||
t.Fatalf("block %d: failed to insert into chain: %v", n, err)
|
||||
}
|
||||
|
||||
// Prepare test data
|
||||
receipts := blockchain.GetReceiptsByHash(chain[0].Hash())
|
||||
if receipts == nil {
|
||||
t.Fatalf("failed to get receipts")
|
||||
}
|
||||
|
||||
chainEvent := core.ChainEvent{
|
||||
Header: chain[0].Header(),
|
||||
Receipts: receipts,
|
||||
Transactions: chain[0].Transactions(),
|
||||
}
|
||||
|
||||
txHashes := make([]common.Hash, txNum)
|
||||
for i := 0; i < txNum; i++ {
|
||||
txHashes[i] = chain[0].Transactions()[i].Hash()
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
filterTxHashes []common.Hash
|
||||
expectedReceiptTxHashes []common.Hash
|
||||
expectError bool
|
||||
}{
|
||||
{
|
||||
name: "no filter - should return all receipts",
|
||||
filterTxHashes: nil,
|
||||
expectedReceiptTxHashes: txHashes,
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
name: "single tx hash filter",
|
||||
filterTxHashes: []common.Hash{txHashes[0]},
|
||||
expectedReceiptTxHashes: []common.Hash{txHashes[0]},
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
name: "multiple tx hashes filter",
|
||||
filterTxHashes: []common.Hash{txHashes[0], txHashes[1], txHashes[2]},
|
||||
expectedReceiptTxHashes: []common.Hash{txHashes[0], txHashes[1], txHashes[2]},
|
||||
expectError: false,
|
||||
},
|
||||
}
|
||||
|
||||
// Run test cases
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
receiptsChan := make(chan []*ReceiptWithTx)
|
||||
sub := api.events.SubscribeTransactionReceipts(tc.filterTxHashes, receiptsChan)
|
||||
|
||||
// Send chain event
|
||||
backend.chainFeed.Send(chainEvent)
|
||||
|
||||
// Wait for receipts
|
||||
timeout := time.After(1 * time.Second)
|
||||
var receivedReceipts []*types.Receipt
|
||||
for {
|
||||
select {
|
||||
case receiptsWithTx := <-receiptsChan:
|
||||
for _, receiptWithTx := range receiptsWithTx {
|
||||
receivedReceipts = append(receivedReceipts, receiptWithTx.Receipt)
|
||||
}
|
||||
case <-timeout:
|
||||
t.Fatalf("timeout waiting for receipts")
|
||||
}
|
||||
if len(receivedReceipts) >= len(tc.expectedReceiptTxHashes) {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Verify receipt count
|
||||
if len(receivedReceipts) != len(tc.expectedReceiptTxHashes) {
|
||||
t.Errorf("Expected %d receipts, got %d", len(tc.expectedReceiptTxHashes), len(receivedReceipts))
|
||||
}
|
||||
|
||||
// Verify specific transaction hashes are present
|
||||
if tc.expectedReceiptTxHashes != nil {
|
||||
receivedHashes := make(map[common.Hash]bool)
|
||||
for _, receipt := range receivedReceipts {
|
||||
receivedHashes[receipt.TxHash] = true
|
||||
}
|
||||
|
||||
for _, expectedHash := range tc.expectedReceiptTxHashes {
|
||||
if !receivedHashes[expectedHash] {
|
||||
t.Errorf("Expected receipt for tx %x not found", expectedHash)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Cleanup
|
||||
sub.Unsubscribe()
|
||||
<-sub.Err()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,7 +22,6 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core"
|
||||
"github.com/ethereum/go-ethereum/core/forkid"
|
||||
"github.com/ethereum/go-ethereum/metrics"
|
||||
"github.com/ethereum/go-ethereum/p2p"
|
||||
|
|
@ -36,7 +35,7 @@ const (
|
|||
|
||||
// Handshake executes the eth protocol handshake, negotiating version number,
|
||||
// network IDs, difficulties, head and genesis blocks.
|
||||
func (p *Peer) Handshake(networkID uint64, chain *core.BlockChain, rangeMsg BlockRangeUpdatePacket) error {
|
||||
func (p *Peer) Handshake(networkID uint64, chain forkid.Blockchain, rangeMsg BlockRangeUpdatePacket) error {
|
||||
switch p.version {
|
||||
case ETH69:
|
||||
return p.handshake69(networkID, chain, rangeMsg)
|
||||
|
|
@ -47,10 +46,10 @@ func (p *Peer) Handshake(networkID uint64, chain *core.BlockChain, rangeMsg Bloc
|
|||
}
|
||||
}
|
||||
|
||||
func (p *Peer) handshake68(networkID uint64, chain *core.BlockChain) error {
|
||||
func (p *Peer) handshake68(networkID uint64, chain forkid.Blockchain) error {
|
||||
var (
|
||||
genesis = chain.Genesis()
|
||||
latest = chain.CurrentBlock()
|
||||
latest = chain.CurrentHeader()
|
||||
forkID = forkid.NewID(chain.Config(), genesis, latest.Number.Uint64(), latest.Time)
|
||||
forkFilter = forkid.NewFilter(chain)
|
||||
)
|
||||
|
|
@ -92,10 +91,10 @@ func (p *Peer) readStatus68(networkID uint64, status *StatusPacket68, genesis co
|
|||
return nil
|
||||
}
|
||||
|
||||
func (p *Peer) handshake69(networkID uint64, chain *core.BlockChain, rangeMsg BlockRangeUpdatePacket) error {
|
||||
func (p *Peer) handshake69(networkID uint64, chain forkid.Blockchain, rangeMsg BlockRangeUpdatePacket) error {
|
||||
var (
|
||||
genesis = chain.Genesis()
|
||||
latest = chain.CurrentBlock()
|
||||
latest = chain.CurrentHeader()
|
||||
forkID = forkid.NewID(chain.Config(), genesis, latest.Number.Uint64(), latest.Time)
|
||||
forkFilter = forkid.NewFilter(chain)
|
||||
)
|
||||
|
|
|
|||
|
|
@ -74,8 +74,11 @@ func (r *hashRange) End() common.Hash {
|
|||
|
||||
// incHash returns the next hash, in lexicographical order (a.k.a plus one)
|
||||
func incHash(h common.Hash) common.Hash {
|
||||
var a uint256.Int
|
||||
a.SetBytes32(h[:])
|
||||
a.AddUint64(&a, 1)
|
||||
return common.Hash(a.Bytes32())
|
||||
for i := len(h) - 1; i >= 0; i-- {
|
||||
h[i]++
|
||||
if h[i] != 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
return h
|
||||
}
|
||||
|
|
|
|||
|
|
@ -80,6 +80,7 @@ type StateReleaseFunc func()
|
|||
type Backend interface {
|
||||
HeaderByHash(ctx context.Context, hash common.Hash) (*types.Header, error)
|
||||
HeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Header, error)
|
||||
CurrentHeader() *types.Header
|
||||
BlockByHash(ctx context.Context, hash common.Hash) (*types.Block, error)
|
||||
BlockByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Block, error)
|
||||
GetCanonicalTransaction(txHash common.Hash) (bool, *types.Transaction, common.Hash, uint64, uint64)
|
||||
|
|
|
|||
|
|
@ -142,6 +142,10 @@ func (b *testBackend) ChainDb() ethdb.Database {
|
|||
return b.chaindb
|
||||
}
|
||||
|
||||
func (b *testBackend) CurrentHeader() *types.Header {
|
||||
return b.chain.CurrentHeader()
|
||||
}
|
||||
|
||||
// teardown releases the associated resources.
|
||||
func (b *testBackend) teardown() {
|
||||
b.chain.Stop()
|
||||
|
|
|
|||
|
|
@ -350,6 +350,15 @@ func (ec *Client) TransactionReceipt(ctx context.Context, txHash common.Hash) (*
|
|||
return r, err
|
||||
}
|
||||
|
||||
// SubscribeTransactionReceipts subscribes to notifications about transaction receipts.
|
||||
func (ec *Client) SubscribeTransactionReceipts(ctx context.Context, q *ethereum.TransactionReceiptsQuery, ch chan<- []*types.Receipt) (ethereum.Subscription, error) {
|
||||
sub, err := ec.c.EthSubscribe(ctx, ch, "transactionReceipts", q)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return sub, nil
|
||||
}
|
||||
|
||||
// SyncProgress retrieves the current progress of the sync algorithm. If there's
|
||||
// no sync currently running, it returns nil.
|
||||
func (ec *Client) SyncProgress(ctx context.Context) (*ethereum.SyncProgress, error) {
|
||||
|
|
|
|||
4
go.mod
4
go.mod
|
|
@ -5,7 +5,7 @@ go 1.24.0
|
|||
require (
|
||||
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.2.0
|
||||
github.com/Microsoft/go-winio v0.6.2
|
||||
github.com/VictoriaMetrics/fastcache v1.12.2
|
||||
github.com/VictoriaMetrics/fastcache v1.13.0
|
||||
github.com/aws/aws-sdk-go-v2 v1.21.2
|
||||
github.com/aws/aws-sdk-go-v2/config v1.18.45
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.13.43
|
||||
|
|
@ -31,7 +31,7 @@ require (
|
|||
github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff
|
||||
github.com/gofrs/flock v0.12.1
|
||||
github.com/golang-jwt/jwt/v4 v4.5.2
|
||||
github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb
|
||||
github.com/golang/snappy v1.0.0
|
||||
github.com/google/gofuzz v1.2.0
|
||||
github.com/google/uuid v1.3.0
|
||||
github.com/gorilla/websocket v1.4.2
|
||||
|
|
|
|||
10
go.sum
10
go.sum
|
|
@ -16,8 +16,8 @@ github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERo
|
|||
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
|
||||
github.com/StackExchange/wmi v1.2.1 h1:VIkavFPXSjcnS+O8yTq7NI32k0R5Aj+v39y29VYDOSA=
|
||||
github.com/StackExchange/wmi v1.2.1/go.mod h1:rcmrprowKIVzvc+NUiLncP2uuArMWLCbu9SBzvHz7e8=
|
||||
github.com/VictoriaMetrics/fastcache v1.12.2 h1:N0y9ASrJ0F6h0QaC3o6uJb3NIZ9VKLjCM7NQbSmF7WI=
|
||||
github.com/VictoriaMetrics/fastcache v1.12.2/go.mod h1:AmC+Nzz1+3G2eCPapF6UcsnkThDcMsQicp4xDukwJYI=
|
||||
github.com/VictoriaMetrics/fastcache v1.13.0 h1:AW4mheMR5Vd9FkAPUv+NH6Nhw+fmbTMGMsNAoA/+4G0=
|
||||
github.com/VictoriaMetrics/fastcache v1.13.0/go.mod h1:hHXhl4DA2fTL2HTZDJFXWgW0LNjo6B+4aj2Wmng3TjU=
|
||||
github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156 h1:eMwmnE/GDgah4HI848JfFxHt+iPb26b4zyfspmqY0/8=
|
||||
github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM=
|
||||
github.com/aws/aws-sdk-go-v2 v1.21.2 h1:+LXZ0sgo8quN9UOKXXzAWRT3FWd4NxeXWOZom9pE7GA=
|
||||
|
|
@ -52,7 +52,6 @@ github.com/bits-and-blooms/bitset v1.20.0 h1:2F+rfL86jE2d/bmw7OhqUg2Sj/1rURkBn3M
|
|||
github.com/bits-and-blooms/bitset v1.20.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8=
|
||||
github.com/cespare/cp v0.1.0 h1:SE+dxFebS7Iik5LK0tsi1k9ZCxEaFX4AjQmoyA+1dJk=
|
||||
github.com/cespare/cp v0.1.0/go.mod h1:SOGHArjBr4JWaSDEVpWpo/hNg6RoKrls6Oh40hiwW+s=
|
||||
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/chzyer/logex v1.2.0/go.mod h1:9+9sk7u7pGNWYMkh0hdiL++6OeibzJccyQU4p4MedaY=
|
||||
|
|
@ -165,8 +164,8 @@ github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw
|
|||
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
||||
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb h1:PBC98N2aIaM3XXiurYmW7fx4GZkL8feAMVq7nEjURHk=
|
||||
github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/golang/snappy v1.0.0 h1:Oy607GVXHs7RtbggtPBnr2RmDArIsAefDwvrdWvRhGs=
|
||||
github.com/golang/snappy v1.0.0/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/golangci/lint-1 v0.0.0-20181222135242-d2cdd8c08219/go.mod h1:/X8TswGSh1pIozq4ZwCfxS0WA5JGXguxk94ar/4c87Y=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
|
|
@ -450,7 +449,6 @@ golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|||
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k=
|
||||
golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||
|
|
|
|||
|
|
@ -62,6 +62,13 @@ type ChainReader interface {
|
|||
SubscribeNewHead(ctx context.Context, ch chan<- *types.Header) (Subscription, error)
|
||||
}
|
||||
|
||||
// TransactionReceiptsQuery defines criteria for transaction receipts subscription.
|
||||
// If TransactionHashes is empty, receipts for all transactions included in new blocks will be delivered.
|
||||
// Otherwise, only receipts for the specified transactions will be delivered.
|
||||
type TransactionReceiptsQuery struct {
|
||||
TransactionHashes []common.Hash
|
||||
}
|
||||
|
||||
// TransactionReader provides access to past transactions and their receipts.
|
||||
// Implementations may impose arbitrary restrictions on the transactions and receipts that
|
||||
// can be retrieved. Historic transactions may not be available.
|
||||
|
|
@ -81,6 +88,11 @@ type TransactionReader interface {
|
|||
// transaction may not be included in the current canonical chain even if a receipt
|
||||
// exists.
|
||||
TransactionReceipt(ctx context.Context, txHash common.Hash) (*types.Receipt, error)
|
||||
// SubscribeTransactionReceipts subscribes to notifications about transaction receipts.
|
||||
// The receipts are delivered in batches when transactions are included in blocks.
|
||||
// If q is nil or has empty TransactionHashes, all receipts from new blocks will be delivered.
|
||||
// Otherwise, only receipts for the specified transaction hashes will be delivered.
|
||||
SubscribeTransactionReceipts(ctx context.Context, q *TransactionReceiptsQuery, ch chan<- []*types.Receipt) (Subscription, error)
|
||||
}
|
||||
|
||||
// ChainStateReader wraps access to the state trie of the canonical blockchain. Note that
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@
|
|||
package blocktest
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"hash"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
|
|
@ -48,8 +49,8 @@ func (h *testHasher) Reset() {
|
|||
|
||||
// Update updates the hash state with the given key and value.
|
||||
func (h *testHasher) Update(key, val []byte) error {
|
||||
h.hasher.Write(key)
|
||||
h.hasher.Write(val)
|
||||
h.hasher.Write(bytes.Clone(key))
|
||||
h.hasher.Write(bytes.Clone(val))
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -627,7 +627,7 @@ func (api *BlockChainAPI) GetBlockReceipts(ctx context.Context, blockNrOrHash rp
|
|||
|
||||
result := make([]map[string]interface{}, len(receipts))
|
||||
for i, receipt := range receipts {
|
||||
result[i] = marshalReceipt(receipt, block.Hash(), block.NumberU64(), signer, txs[i], i)
|
||||
result[i] = MarshalReceipt(receipt, block.Hash(), block.NumberU64(), signer, txs[i], i)
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
|
@ -636,6 +636,8 @@ func (api *BlockChainAPI) GetBlockReceipts(ctx context.Context, blockNrOrHash rp
|
|||
type ChainContextBackend interface {
|
||||
Engine() consensus.Engine
|
||||
HeaderByNumber(context.Context, rpc.BlockNumber) (*types.Header, error)
|
||||
HeaderByHash(context.Context, common.Hash) (*types.Header, error)
|
||||
CurrentHeader() *types.Header
|
||||
ChainConfig() *params.ChainConfig
|
||||
}
|
||||
|
||||
|
|
@ -669,6 +671,20 @@ func (context *ChainContext) Config() *params.ChainConfig {
|
|||
return context.b.ChainConfig()
|
||||
}
|
||||
|
||||
func (context *ChainContext) CurrentHeader() *types.Header {
|
||||
return context.b.CurrentHeader()
|
||||
}
|
||||
|
||||
func (context *ChainContext) GetHeaderByNumber(number uint64) *types.Header {
|
||||
header, _ := context.b.HeaderByNumber(context.ctx, rpc.BlockNumber(number))
|
||||
return header
|
||||
}
|
||||
|
||||
func (context *ChainContext) GetHeaderByHash(hash common.Hash) *types.Header {
|
||||
header, _ := context.b.HeaderByHash(context.ctx, hash)
|
||||
return header
|
||||
}
|
||||
|
||||
func doCall(ctx context.Context, b Backend, args TransactionArgs, state *state.StateDB, header *types.Header, overrides *override.StateOverride, blockOverrides *override.BlockOverrides, timeout time.Duration, globalGasCap uint64) (*core.ExecutionResult, error) {
|
||||
blockCtx := core.NewEVMBlockContext(header, NewChainContext(ctx, b), nil)
|
||||
if blockOverrides != nil {
|
||||
|
|
@ -1472,11 +1488,11 @@ func (api *TransactionAPI) GetTransactionReceipt(ctx context.Context, hash commo
|
|||
return nil, err
|
||||
}
|
||||
// Derive the sender.
|
||||
return marshalReceipt(receipt, blockHash, blockNumber, api.signer, tx, int(index)), nil
|
||||
return MarshalReceipt(receipt, blockHash, blockNumber, api.signer, tx, int(index)), nil
|
||||
}
|
||||
|
||||
// marshalReceipt marshals a transaction receipt into a JSON object.
|
||||
func marshalReceipt(receipt *types.Receipt, blockHash common.Hash, blockNumber uint64, signer types.Signer, tx *types.Transaction, txIndex int) map[string]interface{} {
|
||||
// MarshalReceipt marshals a transaction receipt into a JSON object.
|
||||
func MarshalReceipt(receipt *types.Receipt, blockHash common.Hash, blockNumber uint64, signer types.Signer, tx *types.Transaction, txIndex int) map[string]interface{} {
|
||||
from, _ := types.Sender(signer, tx)
|
||||
|
||||
fields := map[string]interface{}{
|
||||
|
|
|
|||
|
|
@ -1327,10 +1327,11 @@ func TestSimulateV1(t *testing.T) {
|
|||
validation = true
|
||||
)
|
||||
type log struct {
|
||||
Address common.Address `json:"address"`
|
||||
Topics []common.Hash `json:"topics"`
|
||||
Data hexutil.Bytes `json:"data"`
|
||||
BlockNumber hexutil.Uint64 `json:"blockNumber"`
|
||||
Address common.Address `json:"address"`
|
||||
Topics []common.Hash `json:"topics"`
|
||||
Data hexutil.Bytes `json:"data"`
|
||||
BlockNumber hexutil.Uint64 `json:"blockNumber"`
|
||||
BlockTimestamp hexutil.Uint64 `json:"blockTimestamp"`
|
||||
// Skip txHash
|
||||
//TxHash common.Hash `json:"transactionHash" gencodec:"required"`
|
||||
TxIndex hexutil.Uint `json:"transactionIndex"`
|
||||
|
|
@ -1677,10 +1678,11 @@ func TestSimulateV1(t *testing.T) {
|
|||
Calls: []callRes{{
|
||||
ReturnValue: "0x",
|
||||
Logs: []log{{
|
||||
Address: randomAccounts[2].addr,
|
||||
Topics: []common.Hash{common.HexToHash("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")},
|
||||
BlockNumber: hexutil.Uint64(11),
|
||||
Data: hexutil.Bytes{},
|
||||
Address: randomAccounts[2].addr,
|
||||
Topics: []common.Hash{common.HexToHash("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")},
|
||||
BlockNumber: hexutil.Uint64(11),
|
||||
BlockTimestamp: hexutil.Uint64(0x70),
|
||||
Data: hexutil.Bytes{},
|
||||
}},
|
||||
GasUsed: "0x5508",
|
||||
Status: "0x1",
|
||||
|
|
@ -1853,8 +1855,9 @@ func TestSimulateV1(t *testing.T) {
|
|||
addressToHash(accounts[0].addr),
|
||||
addressToHash(randomAccounts[0].addr),
|
||||
},
|
||||
Data: hexutil.Bytes(common.BigToHash(big.NewInt(50)).Bytes()),
|
||||
BlockNumber: hexutil.Uint64(11),
|
||||
Data: hexutil.Bytes(common.BigToHash(big.NewInt(50)).Bytes()),
|
||||
BlockNumber: hexutil.Uint64(11),
|
||||
BlockTimestamp: hexutil.Uint64(0x70),
|
||||
}, {
|
||||
Address: transferAddress,
|
||||
Topics: []common.Hash{
|
||||
|
|
@ -1862,9 +1865,10 @@ func TestSimulateV1(t *testing.T) {
|
|||
addressToHash(randomAccounts[0].addr),
|
||||
addressToHash(fixedAccount.addr),
|
||||
},
|
||||
Data: hexutil.Bytes(common.BigToHash(big.NewInt(100)).Bytes()),
|
||||
BlockNumber: hexutil.Uint64(11),
|
||||
Index: hexutil.Uint(1),
|
||||
Data: hexutil.Bytes(common.BigToHash(big.NewInt(100)).Bytes()),
|
||||
BlockNumber: hexutil.Uint64(11),
|
||||
BlockTimestamp: hexutil.Uint64(0x70),
|
||||
Index: hexutil.Uint(1),
|
||||
}},
|
||||
Status: "0x1",
|
||||
}},
|
||||
|
|
|
|||
|
|
@ -53,15 +53,17 @@ type tracer struct {
|
|||
count int
|
||||
traceTransfers bool
|
||||
blockNumber uint64
|
||||
blockTimestamp uint64
|
||||
blockHash common.Hash
|
||||
txHash common.Hash
|
||||
txIdx uint
|
||||
}
|
||||
|
||||
func newTracer(traceTransfers bool, blockNumber uint64, blockHash, txHash common.Hash, txIndex uint) *tracer {
|
||||
func newTracer(traceTransfers bool, blockNumber uint64, blockTimestamp uint64, blockHash, txHash common.Hash, txIndex uint) *tracer {
|
||||
return &tracer{
|
||||
traceTransfers: traceTransfers,
|
||||
blockNumber: blockNumber,
|
||||
blockTimestamp: blockTimestamp,
|
||||
blockHash: blockHash,
|
||||
txHash: txHash,
|
||||
txIdx: txIndex,
|
||||
|
|
@ -115,14 +117,15 @@ func (t *tracer) onLog(log *types.Log) {
|
|||
|
||||
func (t *tracer) captureLog(address common.Address, topics []common.Hash, data []byte) {
|
||||
t.logs[len(t.logs)-1] = append(t.logs[len(t.logs)-1], &types.Log{
|
||||
Address: address,
|
||||
Topics: topics,
|
||||
Data: data,
|
||||
BlockNumber: t.blockNumber,
|
||||
BlockHash: t.blockHash,
|
||||
TxHash: t.txHash,
|
||||
TxIndex: t.txIdx,
|
||||
Index: uint(t.count),
|
||||
Address: address,
|
||||
Topics: topics,
|
||||
Data: data,
|
||||
BlockNumber: t.blockNumber,
|
||||
BlockTimestamp: t.blockTimestamp,
|
||||
BlockHash: t.blockHash,
|
||||
TxHash: t.txHash,
|
||||
TxIndex: t.txIdx,
|
||||
Index: uint(t.count),
|
||||
})
|
||||
t.count++
|
||||
}
|
||||
|
|
|
|||
|
|
@ -244,7 +244,7 @@ func (sim *simulator) processBlock(ctx context.Context, block *simBlock, header,
|
|||
callResults = make([]simCallResult, len(block.Calls))
|
||||
receipts = make([]*types.Receipt, len(block.Calls))
|
||||
// Block hash will be repaired after execution.
|
||||
tracer = newTracer(sim.traceTransfers, blockContext.BlockNumber.Uint64(), common.Hash{}, common.Hash{}, 0)
|
||||
tracer = newTracer(sim.traceTransfers, blockContext.BlockNumber.Uint64(), blockContext.Time, common.Hash{}, common.Hash{}, 0)
|
||||
vmConfig = &vm.Config{
|
||||
NoBaseFee: !sim.validate,
|
||||
Tracer: tracer.Hooks(),
|
||||
|
|
@ -541,3 +541,23 @@ func (b *simBackend) HeaderByNumber(ctx context.Context, number rpc.BlockNumber)
|
|||
func (b *simBackend) ChainConfig() *params.ChainConfig {
|
||||
return b.b.ChainConfig()
|
||||
}
|
||||
|
||||
func (b *simBackend) HeaderByHash(ctx context.Context, hash common.Hash) (*types.Header, error) {
|
||||
if b.base.Hash() == hash {
|
||||
return b.base, nil
|
||||
}
|
||||
if header, err := b.b.HeaderByHash(ctx, hash); err == nil {
|
||||
return header, nil
|
||||
}
|
||||
// Check simulated headers
|
||||
for _, header := range b.headers {
|
||||
if header.Hash() == hash {
|
||||
return header, nil
|
||||
}
|
||||
}
|
||||
return nil, errors.New("header not found")
|
||||
}
|
||||
|
||||
func (b *simBackend) CurrentHeader() *types.Header {
|
||||
return b.b.CurrentHeader()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -174,11 +174,12 @@ type AsyncFilterFunc func(context.Context, *Node) *Node
|
|||
// AsyncFilter creates an iterator which checks nodes in parallel.
|
||||
// The 'check' function is called on multiple goroutines to filter each node
|
||||
// from the upstream iterator. When check returns nil, the node will be skipped.
|
||||
// It can also return a new node to be returned by the iterator instead of the .
|
||||
// It can also return a new node to be returned by the iterator instead of the
|
||||
// original one.
|
||||
func AsyncFilter(it Iterator, check AsyncFilterFunc, workers int) Iterator {
|
||||
f := &asyncFilterIter{
|
||||
it: ensureSourceIter(it),
|
||||
slots: make(chan struct{}, workers+1),
|
||||
slots: make(chan struct{}, workers+1), // extra 1 slot to make sure all the goroutines can be completed
|
||||
passed: make(chan iteratorItem),
|
||||
}
|
||||
for range cap(f.slots) {
|
||||
|
|
@ -193,6 +194,9 @@ func AsyncFilter(it Iterator, check AsyncFilterFunc, workers int) Iterator {
|
|||
return
|
||||
case <-f.slots:
|
||||
}
|
||||
defer func() {
|
||||
f.slots <- struct{}{} // the iterator has ended
|
||||
}()
|
||||
// read from the iterator and start checking nodes in parallel
|
||||
// when a node is checked, it will be sent to the passed channel
|
||||
// and the slot will be released
|
||||
|
|
@ -201,7 +205,11 @@ func AsyncFilter(it Iterator, check AsyncFilterFunc, workers int) Iterator {
|
|||
nodeSource := f.it.NodeSource()
|
||||
|
||||
// check the node async, in a separate goroutine
|
||||
<-f.slots
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case <-f.slots:
|
||||
}
|
||||
go func() {
|
||||
if nn := check(ctx, node); nn != nil {
|
||||
item := iteratorItem{nn, nodeSource}
|
||||
|
|
@ -213,8 +221,6 @@ func AsyncFilter(it Iterator, check AsyncFilterFunc, workers int) Iterator {
|
|||
f.slots <- struct{}{}
|
||||
}()
|
||||
}
|
||||
// the iterator has ended
|
||||
f.slots <- struct{}{}
|
||||
}()
|
||||
|
||||
return f
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ func TestReadNodesCycle(t *testing.T) {
|
|||
nodes := ReadNodes(iter, 10)
|
||||
checkNodes(t, nodes, 3)
|
||||
if iter.count != 10 {
|
||||
t.Fatalf("%d calls to Next, want %d", iter.count, 100)
|
||||
t.Fatalf("%d calls to Next, want %d", iter.count, 10)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -624,7 +624,7 @@ func (c *ChainConfig) Description() string {
|
|||
}
|
||||
banner += fmt.Sprintf(" - Tangerine Whistle (EIP 150): #%-8v\n", c.EIP150Block)
|
||||
banner += fmt.Sprintf(" - Spurious Dragon/1 (EIP 155): #%-8v\n", c.EIP155Block)
|
||||
banner += fmt.Sprintf(" - Spurious Dragon/2 (EIP 158): #%-8v\n", c.EIP155Block)
|
||||
banner += fmt.Sprintf(" - Spurious Dragon/2 (EIP 158): #%-8v\n", c.EIP158Block)
|
||||
banner += fmt.Sprintf(" - Byzantium: #%-8v\n", c.ByzantiumBlock)
|
||||
banner += fmt.Sprintf(" - Constantinople: #%-8v\n", c.ConstantinopleBlock)
|
||||
banner += fmt.Sprintf(" - Petersburg: #%-8v\n", c.PetersburgBlock)
|
||||
|
|
|
|||
|
|
@ -264,7 +264,7 @@ func TestSignTx(t *testing.T) {
|
|||
t.Errorf("Expected nil-response, got %v", res)
|
||||
}
|
||||
if err != keystore.ErrDecrypt {
|
||||
t.Errorf("Expected ErrLocked! %v", err)
|
||||
t.Errorf("Expected ErrDecrypt! %v", err)
|
||||
}
|
||||
control.approveCh <- "No way"
|
||||
res, err = api.SignTransaction(t.Context(), tx, &methodSig)
|
||||
|
|
|
|||
|
|
@ -202,7 +202,7 @@ func TestSignData(t *testing.T) {
|
|||
t.Errorf("Expected nil-data, got %x", signature)
|
||||
}
|
||||
if err != keystore.ErrDecrypt {
|
||||
t.Errorf("Expected ErrLocked! '%v'", err)
|
||||
t.Errorf("Expected ErrDecrypt! '%v'", err)
|
||||
}
|
||||
control.approveCh <- "No way"
|
||||
signature, err = api.SignData(context.Background(), apitypes.TextPlain.Mime, a, hexutil.Encode([]byte("EHLO world")))
|
||||
|
|
|
|||
|
|
@ -559,3 +559,6 @@ type dummyChain struct {
|
|||
func (d *dummyChain) Engine() consensus.Engine { return nil }
|
||||
func (d *dummyChain) GetHeader(h common.Hash, n uint64) *types.Header { return nil }
|
||||
func (d *dummyChain) Config() *params.ChainConfig { return d.config }
|
||||
func (d *dummyChain) CurrentHeader() *types.Header { return nil }
|
||||
func (d *dummyChain) GetHeaderByNumber(n uint64) *types.Header { return nil }
|
||||
func (d *dummyChain) GetHeaderByHash(h common.Hash) *types.Header { return nil }
|
||||
|
|
|
|||
|
|
@ -32,8 +32,8 @@ func newBytesPool(sliceCap, nitems int) *bytesPool {
|
|||
}
|
||||
}
|
||||
|
||||
// Get returns a slice. Safe for concurrent use.
|
||||
func (bp *bytesPool) Get() []byte {
|
||||
// get returns a slice. Safe for concurrent use.
|
||||
func (bp *bytesPool) get() []byte {
|
||||
select {
|
||||
case b := <-bp.c:
|
||||
return b
|
||||
|
|
@ -42,18 +42,18 @@ func (bp *bytesPool) Get() []byte {
|
|||
}
|
||||
}
|
||||
|
||||
// GetWithSize returns a slice with specified byte slice size.
|
||||
func (bp *bytesPool) GetWithSize(s int) []byte {
|
||||
b := bp.Get()
|
||||
// getWithSize returns a slice with specified byte slice size.
|
||||
func (bp *bytesPool) getWithSize(s int) []byte {
|
||||
b := bp.get()
|
||||
if cap(b) < s {
|
||||
return make([]byte, s)
|
||||
}
|
||||
return b[:s]
|
||||
}
|
||||
|
||||
// Put returns a slice to the pool. Safe for concurrent use. This method
|
||||
// put returns a slice to the pool. Safe for concurrent use. This method
|
||||
// will ignore slices that are too small or too large (>3x the cap)
|
||||
func (bp *bytesPool) Put(b []byte) {
|
||||
func (bp *bytesPool) put(b []byte) {
|
||||
if c := cap(b); c < bp.w || c > 3*bp.w {
|
||||
return
|
||||
}
|
||||
|
|
@ -62,3 +62,40 @@ func (bp *bytesPool) Put(b []byte) {
|
|||
default:
|
||||
}
|
||||
}
|
||||
|
||||
// unsafeBytesPool is a pool for byte slices. It is not safe for concurrent use.
|
||||
type unsafeBytesPool struct {
|
||||
items [][]byte
|
||||
w int
|
||||
}
|
||||
|
||||
// newUnsafeBytesPool creates a new unsafeBytesPool. The sliceCap sets the
|
||||
// capacity of newly allocated slices, and the nitems determines how many
|
||||
// items the pool will hold, at maximum.
|
||||
func newUnsafeBytesPool(sliceCap, nitems int) *unsafeBytesPool {
|
||||
return &unsafeBytesPool{
|
||||
items: make([][]byte, 0, nitems),
|
||||
w: sliceCap,
|
||||
}
|
||||
}
|
||||
|
||||
// Get returns a slice with pre-allocated space.
|
||||
func (bp *unsafeBytesPool) get() []byte {
|
||||
if len(bp.items) > 0 {
|
||||
last := bp.items[len(bp.items)-1]
|
||||
bp.items = bp.items[:len(bp.items)-1]
|
||||
return last
|
||||
}
|
||||
return make([]byte, 0, bp.w)
|
||||
}
|
||||
|
||||
// put returns a slice to the pool. This method will ignore slices that are
|
||||
// too small or too large (>3x the cap)
|
||||
func (bp *unsafeBytesPool) put(b []byte) {
|
||||
if c := cap(b); c < bp.w || c > 3*bp.w {
|
||||
return
|
||||
}
|
||||
if len(bp.items) < cap(bp.items) {
|
||||
bp.items = append(bp.items, b)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
56
trie/list_hasher.go
Normal file
56
trie/list_hasher.go
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
// 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 trie
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
)
|
||||
|
||||
// ListHasher is a wrapper of the Merkle-Patricia-Trie, which implements
|
||||
// types.ListHasher. Compared to a Trie instance, the Update method of this
|
||||
// type always deep-copies its input slices.
|
||||
//
|
||||
// This implementation is very inefficient in terms of memory allocation,
|
||||
// compared with StackTrie. It exists only for correctness comparison purposes.
|
||||
type ListHasher struct {
|
||||
tr *Trie
|
||||
}
|
||||
|
||||
// NewListHasher initializes the list hasher.
|
||||
func NewListHasher() *ListHasher {
|
||||
return &ListHasher{
|
||||
tr: NewEmpty(nil),
|
||||
}
|
||||
}
|
||||
|
||||
// Reset clears the internal state prepares the ListHasher for reuse.
|
||||
func (h *ListHasher) Reset() {
|
||||
h.tr.reset()
|
||||
}
|
||||
|
||||
// Update inserts a key-value pair into the trie.
|
||||
func (h *ListHasher) Update(key []byte, value []byte) error {
|
||||
key, value = bytes.Clone(key), bytes.Clone(value)
|
||||
return h.tr.Update(key, value)
|
||||
}
|
||||
|
||||
// Hash computes the root hash of all inserted key-value pairs.
|
||||
func (h *ListHasher) Hash() common.Hash {
|
||||
return h.tr.Hash()
|
||||
}
|
||||
|
|
@ -28,7 +28,7 @@ import (
|
|||
var (
|
||||
stPool = sync.Pool{New: func() any { return new(stNode) }}
|
||||
bPool = newBytesPool(32, 100)
|
||||
_ = types.TrieHasher((*StackTrie)(nil))
|
||||
_ = types.ListHasher((*StackTrie)(nil))
|
||||
)
|
||||
|
||||
// OnTrieNode is a callback method invoked when a trie node is committed
|
||||
|
|
@ -50,6 +50,7 @@ type StackTrie struct {
|
|||
onTrieNode OnTrieNode
|
||||
kBuf []byte // buf space used for hex-key during insertions
|
||||
pBuf []byte // buf space used for path during insertions
|
||||
vPool *unsafeBytesPool
|
||||
}
|
||||
|
||||
// NewStackTrie allocates and initializes an empty trie. The committed nodes
|
||||
|
|
@ -61,6 +62,7 @@ func NewStackTrie(onTrieNode OnTrieNode) *StackTrie {
|
|||
onTrieNode: onTrieNode,
|
||||
kBuf: make([]byte, 64),
|
||||
pBuf: make([]byte, 64),
|
||||
vPool: newUnsafeBytesPool(300, 20),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -74,6 +76,9 @@ func (t *StackTrie) grow(key []byte) {
|
|||
}
|
||||
|
||||
// Update inserts a (key, value) pair into the stack trie.
|
||||
//
|
||||
// Note the supplied key value pair is copied and managed internally,
|
||||
// they are safe to be modified after this method returns.
|
||||
func (t *StackTrie) Update(key, value []byte) error {
|
||||
if len(value) == 0 {
|
||||
return errors.New("trying to insert empty (deletion)")
|
||||
|
|
@ -88,7 +93,14 @@ func (t *StackTrie) Update(key, value []byte) error {
|
|||
} else {
|
||||
t.last = append(t.last[:0], k...) // reuse key slice
|
||||
}
|
||||
t.insert(t.root, k, value, t.pBuf[:0])
|
||||
vBuf := t.vPool.get()
|
||||
if cap(vBuf) < len(value) {
|
||||
vBuf = common.CopyBytes(value)
|
||||
} else {
|
||||
vBuf = vBuf[:len(value)]
|
||||
copy(vBuf, value)
|
||||
}
|
||||
t.insert(t.root, k, vBuf, t.pBuf[:0])
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
@ -108,14 +120,16 @@ func (t *StackTrie) TrieKey(key []byte) []byte {
|
|||
// stNode represents a node within a StackTrie
|
||||
type stNode struct {
|
||||
typ uint8 // node type (as in branch, ext, leaf)
|
||||
key []byte // key chunk covered by this (leaf|ext) node
|
||||
val []byte // value contained by this node if it's a leaf
|
||||
children [16]*stNode // list of children (for branch and exts)
|
||||
key []byte // exclusive owned key chunk covered by this (leaf|ext) node
|
||||
val []byte // exclusive owned value contained by this node (leaf: value; hash: hash)
|
||||
children [16]*stNode // list of children (for branch and ext)
|
||||
}
|
||||
|
||||
// newLeaf constructs a leaf node with provided node key and value. The key
|
||||
// will be deep-copied in the function and safe to modify afterwards, but
|
||||
// value is not.
|
||||
// newLeaf constructs a leaf node with provided node key and value.
|
||||
//
|
||||
// The key is deep-copied within the function, so it can be safely modified
|
||||
// afterwards. The value is retained directly without copying, as it is
|
||||
// exclusively owned by the stackTrie.
|
||||
func newLeaf(key, val []byte) *stNode {
|
||||
st := stPool.Get().(*stNode)
|
||||
st.typ = leafNode
|
||||
|
|
@ -146,9 +160,9 @@ const (
|
|||
func (n *stNode) reset() *stNode {
|
||||
if n.typ == hashedNode {
|
||||
// On hashnodes, we 'own' the val: it is guaranteed to be not held
|
||||
// by external caller. Hence, when we arrive here, we can put it back
|
||||
// into the pool
|
||||
bPool.Put(n.val)
|
||||
// by external caller. Hence, when we arrive here, we can put it
|
||||
// back into the pool
|
||||
bPool.put(n.val)
|
||||
}
|
||||
n.key = n.key[:0]
|
||||
n.val = nil
|
||||
|
|
@ -172,11 +186,6 @@ func (n *stNode) getDiffIndex(key []byte) int {
|
|||
}
|
||||
|
||||
// Helper function to that inserts a (key, value) pair into the trie.
|
||||
//
|
||||
// - The key is not retained by this method, but always copied if needed.
|
||||
// - The value is retained by this method, as long as the leaf that it represents
|
||||
// remains unhashed. However: it is never modified.
|
||||
// - The path is not retained by this method.
|
||||
func (t *StackTrie) insert(st *stNode, key, value []byte, path []byte) {
|
||||
switch st.typ {
|
||||
case branchNode: /* Branch */
|
||||
|
|
@ -235,16 +244,14 @@ func (t *StackTrie) insert(st *stNode, key, value []byte, path []byte) {
|
|||
}
|
||||
var p *stNode
|
||||
if diffidx == 0 {
|
||||
// the break is on the first byte, so
|
||||
// the current node is converted into
|
||||
// a branch node.
|
||||
// the break is on the first byte, so the current node
|
||||
// is converted into a branch node.
|
||||
st.children[0] = nil
|
||||
p = st
|
||||
st.typ = branchNode
|
||||
p = st
|
||||
} else {
|
||||
// the common prefix is at least one byte
|
||||
// long, insert a new intermediate branch
|
||||
// node.
|
||||
// the common prefix is at least one byte long, insert
|
||||
// a new intermediate branch node.
|
||||
st.children[0] = stPool.Get().(*stNode)
|
||||
st.children[0].typ = branchNode
|
||||
p = st.children[0]
|
||||
|
|
@ -280,8 +287,8 @@ func (t *StackTrie) insert(st *stNode, key, value []byte, path []byte) {
|
|||
if diffidx == 0 {
|
||||
// Convert current leaf into a branch
|
||||
st.typ = branchNode
|
||||
p = st
|
||||
st.children[0] = nil
|
||||
p = st
|
||||
} else {
|
||||
// Convert current node into an ext,
|
||||
// and insert a child branch node.
|
||||
|
|
@ -307,9 +314,7 @@ func (t *StackTrie) insert(st *stNode, key, value []byte, path []byte) {
|
|||
st.val = nil
|
||||
|
||||
case emptyNode: /* Empty */
|
||||
st.typ = leafNode
|
||||
st.key = append(st.key, key...) // deep-copy the key as it's volatile
|
||||
st.val = value
|
||||
*st = *newLeaf(key, value)
|
||||
|
||||
case hashedNode:
|
||||
panic("trying to insert into hash")
|
||||
|
|
@ -393,18 +398,23 @@ func (t *StackTrie) hash(st *stNode, path []byte) {
|
|||
st.typ = hashedNode
|
||||
st.key = st.key[:0]
|
||||
|
||||
st.val = nil // Release reference to potentially externally held slice.
|
||||
// Release reference to value slice which is exclusively owned
|
||||
// by stackTrie itself.
|
||||
if cap(st.val) > 0 && t.vPool != nil {
|
||||
t.vPool.put(st.val)
|
||||
}
|
||||
st.val = nil
|
||||
|
||||
// Skip committing the non-root node if the size is smaller than 32 bytes
|
||||
// as tiny nodes are always embedded in their parent except root node.
|
||||
if len(blob) < 32 && len(path) > 0 {
|
||||
st.val = bPool.GetWithSize(len(blob))
|
||||
st.val = bPool.getWithSize(len(blob))
|
||||
copy(st.val, blob)
|
||||
return
|
||||
}
|
||||
// Write the hash to the 'val'. We allocate a new val here to not mutate
|
||||
// input values.
|
||||
st.val = bPool.GetWithSize(32)
|
||||
st.val = bPool.getWithSize(32)
|
||||
t.h.hashDataTo(st.val, blob)
|
||||
|
||||
// Invoke the callback it's provided. Notably, the path and blob slices are
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ package trie
|
|||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"slices"
|
||||
"sync"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
|
|
@ -553,7 +554,7 @@ func (s *Sync) children(req *nodeRequest, object node) ([]*nodeRequest, error) {
|
|||
}
|
||||
children = []childNode{{
|
||||
node: node.Val,
|
||||
path: append(append([]byte(nil), req.path...), key...),
|
||||
path: slices.Concat(req.path, key),
|
||||
}}
|
||||
// Mark all internal nodes between shortNode and its **in disk**
|
||||
// child as invalid. This is essential in the case of path mode
|
||||
|
|
@ -595,7 +596,7 @@ func (s *Sync) children(req *nodeRequest, object node) ([]*nodeRequest, error) {
|
|||
if node.Children[i] != nil {
|
||||
children = append(children, childNode{
|
||||
node: node.Children[i],
|
||||
path: append(append([]byte(nil), req.path...), byte(i)),
|
||||
path: append(slices.Clone(req.path), byte(i)),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -784,8 +784,8 @@ func (t *Trie) Witness() map[string][]byte {
|
|||
return t.prevalueTracer.Values()
|
||||
}
|
||||
|
||||
// Reset drops the referenced root node and cleans all internal state.
|
||||
func (t *Trie) Reset() {
|
||||
// reset drops the referenced root node and cleans all internal state.
|
||||
func (t *Trie) reset() {
|
||||
t.root = nil
|
||||
t.owner = common.Hash{}
|
||||
t.unhashed = 0
|
||||
|
|
|
|||
|
|
@ -232,7 +232,7 @@ func (db *Database) repairHistory() error {
|
|||
// Purge all state history indexing data first
|
||||
batch := db.diskdb.NewBatch()
|
||||
rawdb.DeleteStateHistoryIndexMetadata(batch)
|
||||
rawdb.DeleteStateHistoryIndex(batch)
|
||||
rawdb.DeleteStateHistoryIndexes(batch)
|
||||
if err := batch.Write(); err != nil {
|
||||
log.Crit("Failed to purge state history index", "err", err)
|
||||
}
|
||||
|
|
@ -426,7 +426,7 @@ func (db *Database) Enable(root common.Hash) error {
|
|||
// Purge all state history indexing data first
|
||||
batch.Reset()
|
||||
rawdb.DeleteStateHistoryIndexMetadata(batch)
|
||||
rawdb.DeleteStateHistoryIndex(batch)
|
||||
rawdb.DeleteStateHistoryIndexes(batch)
|
||||
if err := batch.Write(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,6 +32,9 @@ type historyType uint8
|
|||
const (
|
||||
// typeStateHistory indicates history data related to account or storage changes.
|
||||
typeStateHistory historyType = 0
|
||||
|
||||
// typeTrienodeHistory indicates history data related to trie node changes.
|
||||
typeTrienodeHistory historyType = 1
|
||||
)
|
||||
|
||||
// String returns the string format representation.
|
||||
|
|
@ -39,6 +42,8 @@ func (h historyType) String() string {
|
|||
switch h {
|
||||
case typeStateHistory:
|
||||
return "state"
|
||||
case typeTrienodeHistory:
|
||||
return "trienode"
|
||||
default:
|
||||
return fmt.Sprintf("unknown type: %d", h)
|
||||
}
|
||||
|
|
@ -48,8 +53,9 @@ func (h historyType) String() string {
|
|||
type elementType uint8
|
||||
|
||||
const (
|
||||
typeAccount elementType = 0 // represents the account data
|
||||
typeStorage elementType = 1 // represents the storage slot data
|
||||
typeAccount elementType = 0 // represents the account data
|
||||
typeStorage elementType = 1 // represents the storage slot data
|
||||
typeTrienode elementType = 2 // represents the trie node data
|
||||
)
|
||||
|
||||
// String returns the string format representation.
|
||||
|
|
@ -59,6 +65,8 @@ func (e elementType) String() string {
|
|||
return "account"
|
||||
case typeStorage:
|
||||
return "storage"
|
||||
case typeTrienode:
|
||||
return "trienode"
|
||||
default:
|
||||
return fmt.Sprintf("unknown element type: %d", e)
|
||||
}
|
||||
|
|
@ -69,11 +77,14 @@ func toHistoryType(typ elementType) historyType {
|
|||
if typ == typeAccount || typ == typeStorage {
|
||||
return typeStateHistory
|
||||
}
|
||||
if typ == typeTrienode {
|
||||
return typeTrienodeHistory
|
||||
}
|
||||
panic(fmt.Sprintf("unknown element type %v", typ))
|
||||
}
|
||||
|
||||
// stateIdent represents the identifier of a state element, which can be
|
||||
// an account or a storage slot.
|
||||
// an account, a storage slot or a trienode.
|
||||
type stateIdent struct {
|
||||
typ elementType
|
||||
|
||||
|
|
@ -91,6 +102,12 @@ type stateIdent struct {
|
|||
//
|
||||
// This field is null if the identifier refers to an account or a trie node.
|
||||
storageHash common.Hash
|
||||
|
||||
// The trie node path within the trie.
|
||||
//
|
||||
// This field is null if the identifier refers to an account or a storage slot.
|
||||
// String type is chosen to make stateIdent comparable.
|
||||
path string
|
||||
}
|
||||
|
||||
// String returns the string format state identifier.
|
||||
|
|
@ -98,7 +115,10 @@ func (ident stateIdent) String() string {
|
|||
if ident.typ == typeAccount {
|
||||
return ident.addressHash.Hex()
|
||||
}
|
||||
return ident.addressHash.Hex() + ident.storageHash.Hex()
|
||||
if ident.typ == typeStorage {
|
||||
return ident.addressHash.Hex() + ident.storageHash.Hex()
|
||||
}
|
||||
return ident.addressHash.Hex() + ident.path
|
||||
}
|
||||
|
||||
// newAccountIdent constructs a state identifier for an account.
|
||||
|
|
@ -120,8 +140,18 @@ func newStorageIdent(addressHash common.Hash, storageHash common.Hash) stateIden
|
|||
}
|
||||
}
|
||||
|
||||
// stateIdentQuery is the extension of stateIdent by adding the account address
|
||||
// and raw storage key.
|
||||
// newTrienodeIdent constructs a state identifier for a trie node.
|
||||
// The address denotes the address hash of the associated account;
|
||||
// the path denotes the path of the node within the trie;
|
||||
func newTrienodeIdent(addressHash common.Hash, path string) stateIdent {
|
||||
return stateIdent{
|
||||
typ: typeTrienode,
|
||||
addressHash: addressHash,
|
||||
path: path,
|
||||
}
|
||||
}
|
||||
|
||||
// stateIdentQuery is the extension of stateIdent by adding the raw storage key.
|
||||
type stateIdentQuery struct {
|
||||
stateIdent
|
||||
|
||||
|
|
@ -150,8 +180,19 @@ func newStorageIdentQuery(address common.Address, addressHash common.Hash, stora
|
|||
}
|
||||
}
|
||||
|
||||
// history defines the interface of historical data, implemented by stateHistory
|
||||
// and trienodeHistory (in the near future).
|
||||
// newTrienodeIdentQuery constructs a state identifier for a trie node.
|
||||
// the addressHash denotes the address hash of the associated account;
|
||||
// the path denotes the path of the node within the trie;
|
||||
//
|
||||
// nolint:unused
|
||||
func newTrienodeIdentQuery(addrHash common.Hash, path []byte) stateIdentQuery {
|
||||
return stateIdentQuery{
|
||||
stateIdent: newTrienodeIdent(addrHash, string(path)),
|
||||
}
|
||||
}
|
||||
|
||||
// history defines the interface of historical data, shared by stateHistory
|
||||
// and trienodeHistory.
|
||||
type history interface {
|
||||
// typ returns the historical data type held in the history.
|
||||
typ() historyType
|
||||
|
|
|
|||
|
|
@ -376,6 +376,8 @@ func readStateIndex(ident stateIdent, db ethdb.KeyValueReader) []byte {
|
|||
return rawdb.ReadAccountHistoryIndex(db, ident.addressHash)
|
||||
case typeStorage:
|
||||
return rawdb.ReadStorageHistoryIndex(db, ident.addressHash, ident.storageHash)
|
||||
case typeTrienode:
|
||||
return rawdb.ReadTrienodeHistoryIndex(db, ident.addressHash, []byte(ident.path))
|
||||
default:
|
||||
panic(fmt.Errorf("unknown type: %v", ident.typ))
|
||||
}
|
||||
|
|
@ -389,6 +391,8 @@ func writeStateIndex(ident stateIdent, db ethdb.KeyValueWriter, data []byte) {
|
|||
rawdb.WriteAccountHistoryIndex(db, ident.addressHash, data)
|
||||
case typeStorage:
|
||||
rawdb.WriteStorageHistoryIndex(db, ident.addressHash, ident.storageHash, data)
|
||||
case typeTrienode:
|
||||
rawdb.WriteTrienodeHistoryIndex(db, ident.addressHash, []byte(ident.path), data)
|
||||
default:
|
||||
panic(fmt.Errorf("unknown type: %v", ident.typ))
|
||||
}
|
||||
|
|
@ -402,6 +406,8 @@ func deleteStateIndex(ident stateIdent, db ethdb.KeyValueWriter) {
|
|||
rawdb.DeleteAccountHistoryIndex(db, ident.addressHash)
|
||||
case typeStorage:
|
||||
rawdb.DeleteStorageHistoryIndex(db, ident.addressHash, ident.storageHash)
|
||||
case typeTrienode:
|
||||
rawdb.DeleteTrienodeHistoryIndex(db, ident.addressHash, []byte(ident.path))
|
||||
default:
|
||||
panic(fmt.Errorf("unknown type: %v", ident.typ))
|
||||
}
|
||||
|
|
@ -415,6 +421,8 @@ func readStateIndexBlock(ident stateIdent, db ethdb.KeyValueReader, id uint32) [
|
|||
return rawdb.ReadAccountHistoryIndexBlock(db, ident.addressHash, id)
|
||||
case typeStorage:
|
||||
return rawdb.ReadStorageHistoryIndexBlock(db, ident.addressHash, ident.storageHash, id)
|
||||
case typeTrienode:
|
||||
return rawdb.ReadTrienodeHistoryIndexBlock(db, ident.addressHash, []byte(ident.path), id)
|
||||
default:
|
||||
panic(fmt.Errorf("unknown type: %v", ident.typ))
|
||||
}
|
||||
|
|
@ -428,6 +436,8 @@ func writeStateIndexBlock(ident stateIdent, db ethdb.KeyValueWriter, id uint32,
|
|||
rawdb.WriteAccountHistoryIndexBlock(db, ident.addressHash, id, data)
|
||||
case typeStorage:
|
||||
rawdb.WriteStorageHistoryIndexBlock(db, ident.addressHash, ident.storageHash, id, data)
|
||||
case typeTrienode:
|
||||
rawdb.WriteTrienodeHistoryIndexBlock(db, ident.addressHash, []byte(ident.path), id, data)
|
||||
default:
|
||||
panic(fmt.Errorf("unknown type: %v", ident.typ))
|
||||
}
|
||||
|
|
@ -441,6 +451,8 @@ func deleteStateIndexBlock(ident stateIdent, db ethdb.KeyValueWriter, id uint32)
|
|||
rawdb.DeleteAccountHistoryIndexBlock(db, ident.addressHash, id)
|
||||
case typeStorage:
|
||||
rawdb.DeleteStorageHistoryIndexBlock(db, ident.addressHash, ident.storageHash, id)
|
||||
case typeTrienode:
|
||||
rawdb.DeleteTrienodeHistoryIndexBlock(db, ident.addressHash, []byte(ident.path), id)
|
||||
default:
|
||||
panic(fmt.Errorf("unknown type: %v", ident.typ))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,8 +36,10 @@ const (
|
|||
// The batch size for reading state histories
|
||||
historyReadBatch = 1000
|
||||
|
||||
stateIndexV0 = uint8(0) // initial version of state index structure
|
||||
stateIndexVersion = stateIndexV0 // the current state index version
|
||||
stateHistoryIndexV0 = uint8(0) // initial version of state index structure
|
||||
stateHistoryIndexVersion = stateHistoryIndexV0 // the current state index version
|
||||
trienodeHistoryIndexV0 = uint8(0) // initial version of trienode index structure
|
||||
trienodeHistoryIndexVersion = trienodeHistoryIndexV0 // the current trienode index version
|
||||
)
|
||||
|
||||
// indexVersion returns the latest index version for the given history type.
|
||||
|
|
@ -45,7 +47,9 @@ const (
|
|||
func indexVersion(typ historyType) uint8 {
|
||||
switch typ {
|
||||
case typeStateHistory:
|
||||
return stateIndexVersion
|
||||
return stateHistoryIndexVersion
|
||||
case typeTrienodeHistory:
|
||||
return trienodeHistoryIndexVersion
|
||||
default:
|
||||
panic(fmt.Errorf("unknown history type: %d", typ))
|
||||
}
|
||||
|
|
@ -63,6 +67,8 @@ func loadIndexMetadata(db ethdb.KeyValueReader, typ historyType) *indexMetadata
|
|||
switch typ {
|
||||
case typeStateHistory:
|
||||
blob = rawdb.ReadStateHistoryIndexMetadata(db)
|
||||
case typeTrienodeHistory:
|
||||
blob = rawdb.ReadTrienodeHistoryIndexMetadata(db)
|
||||
default:
|
||||
panic(fmt.Errorf("unknown history type %d", typ))
|
||||
}
|
||||
|
|
@ -90,6 +96,8 @@ func storeIndexMetadata(db ethdb.KeyValueWriter, typ historyType, last uint64) {
|
|||
switch typ {
|
||||
case typeStateHistory:
|
||||
rawdb.WriteStateHistoryIndexMetadata(db, blob)
|
||||
case typeTrienodeHistory:
|
||||
rawdb.WriteTrienodeHistoryIndexMetadata(db, blob)
|
||||
default:
|
||||
panic(fmt.Errorf("unknown history type %d", typ))
|
||||
}
|
||||
|
|
@ -101,6 +109,8 @@ func deleteIndexMetadata(db ethdb.KeyValueWriter, typ historyType) {
|
|||
switch typ {
|
||||
case typeStateHistory:
|
||||
rawdb.DeleteStateHistoryIndexMetadata(db)
|
||||
case typeTrienodeHistory:
|
||||
rawdb.DeleteTrienodeHistoryIndexMetadata(db)
|
||||
default:
|
||||
panic(fmt.Errorf("unknown history type %d", typ))
|
||||
}
|
||||
|
|
@ -215,7 +225,11 @@ func (b *batchIndexer) finish(force bool) error {
|
|||
func indexSingle(historyID uint64, db ethdb.KeyValueStore, freezer ethdb.AncientReader, typ historyType) error {
|
||||
start := time.Now()
|
||||
defer func() {
|
||||
indexHistoryTimer.UpdateSince(start)
|
||||
if typ == typeStateHistory {
|
||||
stateIndexHistoryTimer.UpdateSince(start)
|
||||
} else if typ == typeTrienodeHistory {
|
||||
trienodeIndexHistoryTimer.UpdateSince(start)
|
||||
}
|
||||
}()
|
||||
|
||||
metadata := loadIndexMetadata(db, typ)
|
||||
|
|
@ -234,7 +248,7 @@ func indexSingle(historyID uint64, db ethdb.KeyValueStore, freezer ethdb.Ancient
|
|||
if typ == typeStateHistory {
|
||||
h, err = readStateHistory(freezer, historyID)
|
||||
} else {
|
||||
// h, err = readTrienodeHistory(freezer, historyID)
|
||||
h, err = readTrienodeHistory(freezer, historyID)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
@ -253,7 +267,11 @@ func indexSingle(historyID uint64, db ethdb.KeyValueStore, freezer ethdb.Ancient
|
|||
func unindexSingle(historyID uint64, db ethdb.KeyValueStore, freezer ethdb.AncientReader, typ historyType) error {
|
||||
start := time.Now()
|
||||
defer func() {
|
||||
unindexHistoryTimer.UpdateSince(start)
|
||||
if typ == typeStateHistory {
|
||||
stateUnindexHistoryTimer.UpdateSince(start)
|
||||
} else if typ == typeTrienodeHistory {
|
||||
trienodeUnindexHistoryTimer.UpdateSince(start)
|
||||
}
|
||||
}()
|
||||
|
||||
metadata := loadIndexMetadata(db, typ)
|
||||
|
|
@ -272,7 +290,7 @@ func unindexSingle(historyID uint64, db ethdb.KeyValueStore, freezer ethdb.Ancie
|
|||
if typ == typeStateHistory {
|
||||
h, err = readStateHistory(freezer, historyID)
|
||||
} else {
|
||||
// h, err = readTrienodeHistory(freezer, historyID)
|
||||
h, err = readTrienodeHistory(freezer, historyID)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
@ -546,13 +564,13 @@ func (i *indexIniter) index(done chan struct{}, interrupt *atomic.Int32, lastID
|
|||
return
|
||||
}
|
||||
} else {
|
||||
// histories, err = readTrienodeHistories(i.freezer, current, count)
|
||||
// if err != nil {
|
||||
// // The history read might fall if the history is truncated from
|
||||
// // head due to revert operation.
|
||||
// i.log.Error("Failed to read history for indexing", "current", current, "count", count, "err", err)
|
||||
// return
|
||||
// }
|
||||
histories, err = readTrienodeHistories(i.freezer, current, count)
|
||||
if err != nil {
|
||||
// The history read might fall if the history is truncated from
|
||||
// head due to revert operation.
|
||||
i.log.Error("Failed to read history for indexing", "current", current, "count", count, "err", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
for _, h := range histories {
|
||||
if err := batch.process(h, current); err != nil {
|
||||
|
|
@ -570,7 +588,7 @@ func (i *indexIniter) index(done chan struct{}, interrupt *atomic.Int32, lastID
|
|||
done = current - beginID
|
||||
)
|
||||
eta := common.CalculateETA(done, left, time.Since(start))
|
||||
i.log.Info("Indexing state history", "processed", done, "left", left, "elapsed", common.PrettyDuration(time.Since(start)), "eta", common.PrettyDuration(eta))
|
||||
i.log.Info("Indexing history", "processed", done, "left", left, "elapsed", common.PrettyDuration(time.Since(start)), "eta", common.PrettyDuration(eta))
|
||||
}
|
||||
}
|
||||
i.indexed.Store(current - 1) // update indexing progress
|
||||
|
|
@ -657,6 +675,8 @@ func checkVersion(disk ethdb.KeyValueStore, typ historyType) {
|
|||
var blob []byte
|
||||
if typ == typeStateHistory {
|
||||
blob = rawdb.ReadStateHistoryIndexMetadata(disk)
|
||||
} else if typ == typeTrienodeHistory {
|
||||
blob = rawdb.ReadTrienodeHistoryIndexMetadata(disk)
|
||||
} else {
|
||||
panic(fmt.Errorf("unknown history type: %v", typ))
|
||||
}
|
||||
|
|
@ -666,24 +686,32 @@ func checkVersion(disk ethdb.KeyValueStore, typ historyType) {
|
|||
return
|
||||
}
|
||||
// Short circuit if the metadata is found and the version is matched
|
||||
ver := stateHistoryIndexVersion
|
||||
if typ == typeTrienodeHistory {
|
||||
ver = trienodeHistoryIndexVersion
|
||||
}
|
||||
var m indexMetadata
|
||||
err := rlp.DecodeBytes(blob, &m)
|
||||
if err == nil && m.Version == stateIndexVersion {
|
||||
if err == nil && m.Version == ver {
|
||||
return
|
||||
}
|
||||
// Version is not matched, prune the existing data and re-index from scratch
|
||||
batch := disk.NewBatch()
|
||||
if typ == typeStateHistory {
|
||||
rawdb.DeleteStateHistoryIndexMetadata(batch)
|
||||
rawdb.DeleteStateHistoryIndexes(batch)
|
||||
} else {
|
||||
rawdb.DeleteTrienodeHistoryIndexMetadata(batch)
|
||||
rawdb.DeleteTrienodeHistoryIndexes(batch)
|
||||
}
|
||||
if err := batch.Write(); err != nil {
|
||||
log.Crit("Failed to purge history index", "type", typ, "err", err)
|
||||
}
|
||||
version := "unknown"
|
||||
if err == nil {
|
||||
version = fmt.Sprintf("%d", m.Version)
|
||||
}
|
||||
|
||||
batch := disk.NewBatch()
|
||||
rawdb.DeleteStateHistoryIndexMetadata(batch)
|
||||
rawdb.DeleteStateHistoryIndex(batch)
|
||||
if err := batch.Write(); err != nil {
|
||||
log.Crit("Failed to purge state history index", "err", err)
|
||||
}
|
||||
log.Info("Cleaned up obsolete state history index", "version", version, "want", stateIndexVersion)
|
||||
log.Info("Cleaned up obsolete history index", "type", typ, "version", version, "want", version)
|
||||
}
|
||||
|
||||
// newHistoryIndexer constructs the history indexer and launches the background
|
||||
|
|
|
|||
|
|
@ -605,9 +605,9 @@ func writeStateHistory(writer ethdb.AncientWriter, dl *diffLayer) error {
|
|||
if err := rawdb.WriteStateHistory(writer, dl.stateID(), history.meta.encode(), accountIndex, storageIndex, accountData, storageData); err != nil {
|
||||
return err
|
||||
}
|
||||
historyDataBytesMeter.Mark(int64(dataSize))
|
||||
historyIndexBytesMeter.Mark(int64(indexSize))
|
||||
historyBuildTimeMeter.UpdateSince(start)
|
||||
stateHistoryDataBytesMeter.Mark(int64(dataSize))
|
||||
stateHistoryIndexBytesMeter.Mark(int64(indexSize))
|
||||
stateHistoryBuildTimeMeter.UpdateSince(start)
|
||||
log.Debug("Stored state history", "id", dl.stateID(), "block", dl.block, "data", dataSize, "index", indexSize, "elapsed", common.PrettyDuration(time.Since(start)))
|
||||
|
||||
return nil
|
||||
|
|
|
|||
|
|
@ -98,13 +98,13 @@ func testEncodeDecodeStateHistory(t *testing.T, rawStorageKey bool) {
|
|||
if !compareSet(dec.accounts, obj.accounts) {
|
||||
t.Fatal("account data is mismatched")
|
||||
}
|
||||
if !compareStorages(dec.storages, obj.storages) {
|
||||
if !compareMapSet(dec.storages, obj.storages) {
|
||||
t.Fatal("storage data is mismatched")
|
||||
}
|
||||
if !compareList(dec.accountList, obj.accountList) {
|
||||
t.Fatal("account list is mismatched")
|
||||
}
|
||||
if !compareStorageList(dec.storageList, obj.storageList) {
|
||||
if !compareMapList(dec.storageList, obj.storageList) {
|
||||
t.Fatal("storage list is mismatched")
|
||||
}
|
||||
}
|
||||
|
|
@ -292,32 +292,32 @@ func compareList[k comparable](a, b []k) bool {
|
|||
return true
|
||||
}
|
||||
|
||||
func compareStorages(a, b map[common.Address]map[common.Hash][]byte) bool {
|
||||
func compareMapSet[K1 comparable, K2 comparable](a, b map[K1]map[K2][]byte) bool {
|
||||
if len(a) != len(b) {
|
||||
return false
|
||||
}
|
||||
for h, subA := range a {
|
||||
subB, ok := b[h]
|
||||
for key, subsetA := range a {
|
||||
subsetB, ok := b[key]
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
if !compareSet(subA, subB) {
|
||||
if !compareSet(subsetA, subsetB) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func compareStorageList(a, b map[common.Address][]common.Hash) bool {
|
||||
func compareMapList[K comparable, V comparable](a, b map[K][]V) bool {
|
||||
if len(a) != len(b) {
|
||||
return false
|
||||
}
|
||||
for h, la := range a {
|
||||
lb, ok := b[h]
|
||||
for key, listA := range a {
|
||||
listB, ok := b[key]
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
if !compareList(la, lb) {
|
||||
if !compareList(listA, listB) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
|
|
|||
730
triedb/pathdb/history_trienode.go
Normal file
730
triedb/pathdb/history_trienode.go
Normal file
|
|
@ -0,0 +1,730 @@
|
|||
// 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 pathdb
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"iter"
|
||||
"maps"
|
||||
"math"
|
||||
"slices"
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||
"github.com/ethereum/go-ethereum/ethdb"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
)
|
||||
|
||||
// Each trie node history entry consists of three parts (stored in three freezer
|
||||
// tables according):
|
||||
//
|
||||
// # Header
|
||||
// The header records metadata, including:
|
||||
//
|
||||
// - the history version (1 byte)
|
||||
// - the parent state root (32 bytes)
|
||||
// - the current state root (32 bytes)
|
||||
// - block number (8 bytes)
|
||||
//
|
||||
// - a lexicographically sorted list of trie IDs
|
||||
// - the corresponding offsets into the key and value sections for each trie data chunk
|
||||
//
|
||||
// Although some fields (e.g., parent state root, block number) are duplicated
|
||||
// between the state history and the trienode history, these two histories
|
||||
// operate independently. To ensure each remains self-contained and self-descriptive,
|
||||
// we have chosen to maintain these duplicate fields.
|
||||
//
|
||||
// # Key section
|
||||
// The key section stores trie node keys (paths) in a compressed format.
|
||||
// It also contains relative offsets into the value section for resolving
|
||||
// the corresponding trie node data. Note that these offsets are relative
|
||||
// to the data chunk for the trie; the chunk offset must be added to obtain
|
||||
// the absolute position.
|
||||
//
|
||||
// # Value section
|
||||
// The value section is a concatenated byte stream of all trie node data.
|
||||
// Each trie node can be retrieved using the offset and length specified
|
||||
// by its index entry.
|
||||
//
|
||||
// The header and key sections are sufficient for locating a trie node,
|
||||
// while a partial read of the value section is enough to retrieve its data.
|
||||
|
||||
// Header section:
|
||||
//
|
||||
// +----------+------------------+---------------------+---------------------+-------+------------------+---------------------+---------------------|
|
||||
// | metadata | TrieID(32 bytes) | key offset(4 bytes) | val offset(4 bytes) | ... | TrieID(32 bytes) | key offset(4 bytes) | val offset(4 bytes) |
|
||||
// +----------+------------------+---------------------+---------------------+-------+------------------+---------------------+---------------------|
|
||||
//
|
||||
//
|
||||
// Key section:
|
||||
//
|
||||
// + restart point + restart point (depends on restart interval)
|
||||
// / /
|
||||
// +---------------+---------------+---------------+---------------+---------+
|
||||
// | node entry 1 | node entry 2 | ... | node entry n | trailer |
|
||||
// +---------------+---------------+---------------+---------------+---------+
|
||||
// \ /
|
||||
// +---- restart block ------+
|
||||
//
|
||||
// node entry:
|
||||
//
|
||||
// +---- key len ----+
|
||||
// / \
|
||||
// +-------+---------+-----------+---------+-----------------------+-----------------+
|
||||
// | shared (varint) | not shared (varint) | value length (varlen) | key (varlen) |
|
||||
// +-----------------+---------------------+-----------------------+-----------------+
|
||||
//
|
||||
// trailer:
|
||||
//
|
||||
// +---- 4-bytes ----+ +---- 4-bytes ----+
|
||||
// / \ / \
|
||||
// +----------------------+------------------------+-----+--------------------------+
|
||||
// | restart_1 key offset | restart_1 value offset | ... | restart number (4-bytes) |
|
||||
// +----------------------+------------------------+-----+--------------------------+
|
||||
//
|
||||
// Note: Both the key offset and the value offset are relative to the start of
|
||||
// the trie data chunk. To obtain the absolute offset, add the offset of the
|
||||
// trie data chunk itself.
|
||||
//
|
||||
// Value section:
|
||||
//
|
||||
// +--------------+--------------+-------+---------------+
|
||||
// | node data 1 | node data 2 | ... | node data n |
|
||||
// +--------------+--------------+-------+---------------+
|
||||
//
|
||||
// NOTE: All fixed-length integer are big-endian.
|
||||
|
||||
const (
|
||||
trienodeHistoryV0 = uint8(0) // initial version of node history structure
|
||||
trienodeHistoryVersion = trienodeHistoryV0 // the default node history version
|
||||
trienodeMetadataSize = 1 + 2*common.HashLength + 8 // the size of metadata in the history
|
||||
trienodeTrieHeaderSize = 8 + common.HashLength // the size of a single trie header in history
|
||||
trienodeDataBlockRestartLen = 16 // The restart interval length of trie node block
|
||||
)
|
||||
|
||||
// trienodeMetadata describes the meta data of trienode history.
|
||||
type trienodeMetadata struct {
|
||||
version uint8 // version tag of history object
|
||||
parent common.Hash // prev-state root before the state transition
|
||||
root common.Hash // post-state root after the state transition
|
||||
block uint64 // associated block number
|
||||
}
|
||||
|
||||
// trienodeHistory represents a set of trie node changes resulting from a state
|
||||
// transition across the main account trie and all associated storage tries.
|
||||
type trienodeHistory struct {
|
||||
meta *trienodeMetadata // Metadata of the history
|
||||
owners []common.Hash // List of trie identifier sorted lexicographically
|
||||
nodeList map[common.Hash][]string // Set of node paths sorted lexicographically
|
||||
nodes map[common.Hash]map[string][]byte // Set of original value of trie nodes before state transition
|
||||
}
|
||||
|
||||
// newTrienodeHistory constructs a trienode history with the provided trie nodes.
|
||||
func newTrienodeHistory(root common.Hash, parent common.Hash, block uint64, nodes map[common.Hash]map[string][]byte) *trienodeHistory {
|
||||
nodeList := make(map[common.Hash][]string)
|
||||
for owner, subset := range nodes {
|
||||
keys := sort.StringSlice(slices.Collect(maps.Keys(subset)))
|
||||
keys.Sort()
|
||||
nodeList[owner] = keys
|
||||
}
|
||||
return &trienodeHistory{
|
||||
meta: &trienodeMetadata{
|
||||
version: trienodeHistoryVersion,
|
||||
parent: parent,
|
||||
root: root,
|
||||
block: block,
|
||||
},
|
||||
owners: slices.SortedFunc(maps.Keys(nodes), common.Hash.Cmp),
|
||||
nodeList: nodeList,
|
||||
nodes: nodes,
|
||||
}
|
||||
}
|
||||
|
||||
// sharedLen returns the length of the common prefix shared by a and b.
|
||||
func sharedLen(a, b []byte) int {
|
||||
n := min(len(a), len(b))
|
||||
for i := 0; i < n; i++ {
|
||||
if a[i] != b[i] {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
// typ implements the history interface, returning the historical data type held.
|
||||
func (h *trienodeHistory) typ() historyType {
|
||||
return typeTrienodeHistory
|
||||
}
|
||||
|
||||
// forEach implements the history interface, returning an iterator to traverse the
|
||||
// state entries in the history.
|
||||
func (h *trienodeHistory) forEach() iter.Seq[stateIdent] {
|
||||
return func(yield func(stateIdent) bool) {
|
||||
for _, owner := range h.owners {
|
||||
for _, path := range h.nodeList[owner] {
|
||||
if !yield(newTrienodeIdent(owner, path)) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// encode serializes the contained trie nodes into bytes.
|
||||
func (h *trienodeHistory) encode() ([]byte, []byte, []byte, error) {
|
||||
var (
|
||||
buf = make([]byte, 64)
|
||||
headerSection bytes.Buffer
|
||||
keySection bytes.Buffer
|
||||
valueSection bytes.Buffer
|
||||
)
|
||||
binary.Write(&headerSection, binary.BigEndian, h.meta.version) // 1 byte
|
||||
headerSection.Write(h.meta.parent.Bytes()) // 32 bytes
|
||||
headerSection.Write(h.meta.root.Bytes()) // 32 bytes
|
||||
binary.Write(&headerSection, binary.BigEndian, h.meta.block) // 8 byte
|
||||
|
||||
for _, owner := range h.owners {
|
||||
// Fill the header section with offsets at key and value section
|
||||
headerSection.Write(owner.Bytes()) // 32 bytes
|
||||
binary.Write(&headerSection, binary.BigEndian, uint32(keySection.Len())) // 4 bytes
|
||||
|
||||
// The offset to the value section is theoretically unnecessary, since the
|
||||
// individual value offset is already tracked in the key section. However,
|
||||
// we still keep it here for two reasons:
|
||||
// - It's cheap to store (only 4 bytes for each trie).
|
||||
// - It can be useful for decoding the trie data when key is not required (e.g., in hash mode).
|
||||
binary.Write(&headerSection, binary.BigEndian, uint32(valueSection.Len())) // 4 bytes
|
||||
|
||||
// Fill the key section with node index
|
||||
var (
|
||||
prevKey []byte
|
||||
restarts []uint32
|
||||
prefixLen int
|
||||
|
||||
internalKeyOffset uint32 // key offset for the trie internally
|
||||
internalValOffset uint32 // value offset for the trie internally
|
||||
)
|
||||
for i, path := range h.nodeList[owner] {
|
||||
key := []byte(path)
|
||||
if i%trienodeDataBlockRestartLen == 0 {
|
||||
restarts = append(restarts, internalKeyOffset)
|
||||
restarts = append(restarts, internalValOffset)
|
||||
prefixLen = 0
|
||||
} else {
|
||||
prefixLen = sharedLen(prevKey, key)
|
||||
}
|
||||
value := h.nodes[owner][path]
|
||||
|
||||
// key section
|
||||
n := binary.PutUvarint(buf[0:], uint64(prefixLen)) // key length shared (varint)
|
||||
n += binary.PutUvarint(buf[n:], uint64(len(key)-prefixLen)) // key length not shared (varint)
|
||||
n += binary.PutUvarint(buf[n:], uint64(len(value))) // value length (varint)
|
||||
|
||||
if _, err := keySection.Write(buf[:n]); err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
// unshared key
|
||||
if _, err := keySection.Write(key[prefixLen:]); err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
n += len(key) - prefixLen
|
||||
prevKey = key
|
||||
|
||||
// value section
|
||||
if _, err := valueSection.Write(value); err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
internalKeyOffset += uint32(n)
|
||||
internalValOffset += uint32(len(value))
|
||||
}
|
||||
|
||||
// Encode trailer, the number of restart sections is len(restarts))/2,
|
||||
// as we track the offsets of both key and value sections.
|
||||
var trailer []byte
|
||||
for _, number := range append(restarts, uint32(len(restarts))/2) {
|
||||
binary.BigEndian.PutUint32(buf[:4], number)
|
||||
trailer = append(trailer, buf[:4]...)
|
||||
}
|
||||
if _, err := keySection.Write(trailer); err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
}
|
||||
return headerSection.Bytes(), keySection.Bytes(), valueSection.Bytes(), nil
|
||||
}
|
||||
|
||||
// decodeHeader resolves the metadata from the header section. An error
|
||||
// should be returned if the header section is corrupted.
|
||||
func decodeHeader(data []byte) (*trienodeMetadata, []common.Hash, []uint32, []uint32, error) {
|
||||
if len(data) < trienodeMetadataSize {
|
||||
return nil, nil, nil, nil, fmt.Errorf("trienode history is too small, index size: %d", len(data))
|
||||
}
|
||||
version := data[0]
|
||||
if version != trienodeHistoryVersion {
|
||||
return nil, nil, nil, nil, fmt.Errorf("unregonized trienode history version: %d", version)
|
||||
}
|
||||
parent := common.BytesToHash(data[1 : common.HashLength+1]) // 32 bytes
|
||||
root := common.BytesToHash(data[common.HashLength+1 : common.HashLength*2+1]) // 32 bytes
|
||||
block := binary.BigEndian.Uint64(data[common.HashLength*2+1 : trienodeMetadataSize]) // 8 bytes
|
||||
|
||||
size := len(data) - trienodeMetadataSize
|
||||
if size%trienodeTrieHeaderSize != 0 {
|
||||
return nil, nil, nil, nil, fmt.Errorf("truncated trienode history data, size %d", len(data))
|
||||
}
|
||||
count := size / trienodeTrieHeaderSize
|
||||
|
||||
var (
|
||||
owners = make([]common.Hash, 0, count)
|
||||
keyOffsets = make([]uint32, 0, count)
|
||||
valOffsets = make([]uint32, 0, count)
|
||||
)
|
||||
for i := 0; i < count; i++ {
|
||||
n := trienodeMetadataSize + trienodeTrieHeaderSize*i
|
||||
owner := common.BytesToHash(data[n : n+common.HashLength])
|
||||
if i != 0 && bytes.Compare(owner.Bytes(), owners[i-1].Bytes()) <= 0 {
|
||||
return nil, nil, nil, nil, fmt.Errorf("trienode owners are out of order, prev: %v, cur: %v", owners[i-1], owner)
|
||||
}
|
||||
owners = append(owners, owner)
|
||||
|
||||
// Decode the offset to the key section
|
||||
keyOffset := binary.BigEndian.Uint32(data[n+common.HashLength : n+common.HashLength+4])
|
||||
if i != 0 && keyOffset <= keyOffsets[i-1] {
|
||||
return nil, nil, nil, nil, fmt.Errorf("key offset is out of order, prev: %v, cur: %v", keyOffsets[i-1], keyOffset)
|
||||
}
|
||||
keyOffsets = append(keyOffsets, keyOffset)
|
||||
|
||||
// Decode the offset into the value section. Note that identical value offsets
|
||||
// are valid if the node values in the last trie chunk are all zero (e.g., after
|
||||
// a trie deletion).
|
||||
valOffset := binary.BigEndian.Uint32(data[n+common.HashLength+4 : n+common.HashLength+8])
|
||||
if i != 0 && valOffset < valOffsets[i-1] {
|
||||
return nil, nil, nil, nil, fmt.Errorf("value offset is out of order, prev: %v, cur: %v", valOffsets[i-1], valOffset)
|
||||
}
|
||||
valOffsets = append(valOffsets, valOffset)
|
||||
}
|
||||
return &trienodeMetadata{
|
||||
version: version,
|
||||
parent: parent,
|
||||
root: root,
|
||||
block: block,
|
||||
}, owners, keyOffsets, valOffsets, nil
|
||||
}
|
||||
|
||||
func decodeSingle(keySection []byte, onValue func([]byte, int, int) error) ([]string, error) {
|
||||
var (
|
||||
prevKey []byte
|
||||
items int
|
||||
keyOffsets []uint32
|
||||
valOffsets []uint32
|
||||
|
||||
keyOff int // the key offset within the single trie data
|
||||
valOff int // the value offset within the single trie data
|
||||
|
||||
keys []string
|
||||
)
|
||||
// Decode the number of restart section
|
||||
if len(keySection) < 4 {
|
||||
return nil, fmt.Errorf("key section too short, size: %d", len(keySection))
|
||||
}
|
||||
nRestarts := binary.BigEndian.Uint32(keySection[len(keySection)-4:])
|
||||
|
||||
if len(keySection) < int(8*nRestarts)+4 {
|
||||
return nil, fmt.Errorf("key section too short, restarts: %d, size: %d", nRestarts, len(keySection))
|
||||
}
|
||||
for i := 0; i < int(nRestarts); i++ {
|
||||
o := len(keySection) - 4 - (int(nRestarts)-i)*8
|
||||
keyOffset := binary.BigEndian.Uint32(keySection[o : o+4])
|
||||
if i != 0 && keyOffset <= keyOffsets[i-1] {
|
||||
return nil, fmt.Errorf("key offset is out of order, prev: %v, cur: %v", keyOffsets[i-1], keyOffset)
|
||||
}
|
||||
keyOffsets = append(keyOffsets, keyOffset)
|
||||
|
||||
// Same value offset is allowed just in case all the trie nodes in the last
|
||||
// section have zero-size value.
|
||||
valOffset := binary.BigEndian.Uint32(keySection[o+4 : o+8])
|
||||
if i != 0 && valOffset < valOffsets[i-1] {
|
||||
return nil, fmt.Errorf("value offset is out of order, prev: %v, cur: %v", valOffsets[i-1], valOffset)
|
||||
}
|
||||
valOffsets = append(valOffsets, valOffset)
|
||||
}
|
||||
keyLimit := len(keySection) - 4 - int(nRestarts)*8
|
||||
|
||||
// Decode data
|
||||
for keyOff < keyLimit {
|
||||
// Validate the key and value offsets within the single trie data chunk
|
||||
if items%trienodeDataBlockRestartLen == 0 {
|
||||
if keyOff != int(keyOffsets[items/trienodeDataBlockRestartLen]) {
|
||||
return nil, fmt.Errorf("key offset is not matched, recorded: %d, want: %d", keyOffsets[items/trienodeDataBlockRestartLen], keyOff)
|
||||
}
|
||||
if valOff != int(valOffsets[items/trienodeDataBlockRestartLen]) {
|
||||
return nil, fmt.Errorf("value offset is not matched, recorded: %d, want: %d", valOffsets[items/trienodeDataBlockRestartLen], valOff)
|
||||
}
|
||||
}
|
||||
// Resolve the entry from key section
|
||||
nShared, nn := binary.Uvarint(keySection[keyOff:]) // key length shared (varint)
|
||||
keyOff += nn
|
||||
nUnshared, nn := binary.Uvarint(keySection[keyOff:]) // key length not shared (varint)
|
||||
keyOff += nn
|
||||
nValue, nn := binary.Uvarint(keySection[keyOff:]) // value length (varint)
|
||||
keyOff += nn
|
||||
|
||||
// Resolve unshared key
|
||||
if keyOff+int(nUnshared) > len(keySection) {
|
||||
return nil, fmt.Errorf("key length too long, unshared key length: %d, off: %d, section size: %d", nUnshared, keyOff, len(keySection))
|
||||
}
|
||||
unsharedKey := keySection[keyOff : keyOff+int(nUnshared)]
|
||||
keyOff += int(nUnshared)
|
||||
|
||||
// Assemble the full key
|
||||
var key []byte
|
||||
if items%trienodeDataBlockRestartLen == 0 {
|
||||
if nShared != 0 {
|
||||
return nil, fmt.Errorf("unexpected non-zero shared key prefix: %d", nShared)
|
||||
}
|
||||
key = unsharedKey
|
||||
} else {
|
||||
if int(nShared) > len(prevKey) {
|
||||
return nil, fmt.Errorf("unexpected shared key prefix: %d, prefix key length: %d", nShared, len(prevKey))
|
||||
}
|
||||
key = append([]byte{}, prevKey[:nShared]...)
|
||||
key = append(key, unsharedKey...)
|
||||
}
|
||||
if items != 0 && bytes.Compare(prevKey, key) >= 0 {
|
||||
return nil, fmt.Errorf("trienode paths are out of order, prev: %v, cur: %v", prevKey, key)
|
||||
}
|
||||
prevKey = key
|
||||
|
||||
// Resolve value
|
||||
if onValue != nil {
|
||||
if err := onValue(key, valOff, valOff+int(nValue)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
valOff += int(nValue)
|
||||
|
||||
items++
|
||||
keys = append(keys, string(key))
|
||||
}
|
||||
if keyOff != keyLimit {
|
||||
return nil, fmt.Errorf("excessive key data after decoding, offset: %d, size: %d", keyOff, keyLimit)
|
||||
}
|
||||
return keys, nil
|
||||
}
|
||||
|
||||
func decodeSingleWithValue(keySection []byte, valueSection []byte) ([]string, map[string][]byte, error) {
|
||||
var (
|
||||
offset int
|
||||
nodes = make(map[string][]byte)
|
||||
)
|
||||
paths, err := decodeSingle(keySection, func(key []byte, start int, limit int) error {
|
||||
if start != offset {
|
||||
return fmt.Errorf("gapped value section offset: %d, want: %d", start, offset)
|
||||
}
|
||||
// start == limit is allowed for zero-value trie node (e.g., non-existent node)
|
||||
if start > limit {
|
||||
return fmt.Errorf("invalid value offsets, start: %d, limit: %d", start, limit)
|
||||
}
|
||||
if start > len(valueSection) || limit > len(valueSection) {
|
||||
return fmt.Errorf("value section out of range: start: %d, limit: %d, size: %d", start, limit, len(valueSection))
|
||||
}
|
||||
nodes[string(key)] = valueSection[start:limit]
|
||||
|
||||
offset = limit
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if offset != len(valueSection) {
|
||||
return nil, nil, fmt.Errorf("excessive value data after decoding, offset: %d, size: %d", offset, len(valueSection))
|
||||
}
|
||||
return paths, nodes, nil
|
||||
}
|
||||
|
||||
// decode deserializes the contained trie nodes from the provided bytes.
|
||||
func (h *trienodeHistory) decode(header []byte, keySection []byte, valueSection []byte) error {
|
||||
metadata, owners, keyOffsets, valueOffsets, err := decodeHeader(header)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
h.meta = metadata
|
||||
h.owners = owners
|
||||
h.nodeList = make(map[common.Hash][]string)
|
||||
h.nodes = make(map[common.Hash]map[string][]byte)
|
||||
|
||||
for i := 0; i < len(owners); i++ {
|
||||
// Resolve the boundary of key section
|
||||
keyStart := keyOffsets[i]
|
||||
keyLimit := len(keySection)
|
||||
if i != len(owners)-1 {
|
||||
keyLimit = int(keyOffsets[i+1])
|
||||
}
|
||||
if int(keyStart) > len(keySection) || keyLimit > len(keySection) {
|
||||
return fmt.Errorf("invalid key offsets: keyStart: %d, keyLimit: %d, size: %d", keyStart, keyLimit, len(keySection))
|
||||
}
|
||||
|
||||
// Resolve the boundary of value section
|
||||
valStart := valueOffsets[i]
|
||||
valLimit := len(valueSection)
|
||||
if i != len(owners)-1 {
|
||||
valLimit = int(valueOffsets[i+1])
|
||||
}
|
||||
if int(valStart) > len(valueSection) || valLimit > len(valueSection) {
|
||||
return fmt.Errorf("invalid value offsets: valueStart: %d, valueLimit: %d, size: %d", valStart, valLimit, len(valueSection))
|
||||
}
|
||||
|
||||
// Decode the key and values for this specific trie
|
||||
paths, nodes, err := decodeSingleWithValue(keySection[keyStart:keyLimit], valueSection[valStart:valLimit])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
h.nodeList[owners[i]] = paths
|
||||
h.nodes[owners[i]] = nodes
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type iRange struct {
|
||||
start uint32
|
||||
limit uint32
|
||||
}
|
||||
|
||||
// singleTrienodeHistoryReader provides read access to a single trie within the
|
||||
// trienode history. It stores an offset to the trie's position in the history,
|
||||
// along with a set of per-node offsets that can be resolved on demand.
|
||||
type singleTrienodeHistoryReader struct {
|
||||
id uint64
|
||||
reader ethdb.AncientReader
|
||||
valueRange iRange // value range within the total value section
|
||||
valueInternalOffsets map[string]iRange // value offset within the single trie data
|
||||
}
|
||||
|
||||
func newSingleTrienodeHistoryReader(id uint64, reader ethdb.AncientReader, keyRange iRange, valueRange iRange) (*singleTrienodeHistoryReader, error) {
|
||||
// TODO(rjl493456442) partial freezer read should be supported
|
||||
keyData, err := rawdb.ReadTrienodeHistoryKeySection(reader, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
keyStart := int(keyRange.start)
|
||||
keyLimit := int(keyRange.limit)
|
||||
if keyLimit == math.MaxUint32 {
|
||||
keyLimit = len(keyData)
|
||||
}
|
||||
if len(keyData) < keyStart || len(keyData) < keyLimit {
|
||||
return nil, fmt.Errorf("key section too short, start: %d, limit: %d, size: %d", keyStart, keyLimit, len(keyData))
|
||||
}
|
||||
|
||||
valueOffsets := make(map[string]iRange)
|
||||
_, err = decodeSingle(keyData[keyStart:keyLimit], func(key []byte, start int, limit int) error {
|
||||
valueOffsets[string(key)] = iRange{
|
||||
start: uint32(start),
|
||||
limit: uint32(limit),
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &singleTrienodeHistoryReader{
|
||||
id: id,
|
||||
reader: reader,
|
||||
valueRange: valueRange,
|
||||
valueInternalOffsets: valueOffsets,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// read retrieves the trie node data with the provided node path.
|
||||
func (sr *singleTrienodeHistoryReader) read(path string) ([]byte, error) {
|
||||
offset, exists := sr.valueInternalOffsets[path]
|
||||
if !exists {
|
||||
return nil, fmt.Errorf("trienode %v not found", []byte(path))
|
||||
}
|
||||
// TODO(rjl493456442) partial freezer read should be supported
|
||||
valueData, err := rawdb.ReadTrienodeHistoryValueSection(sr.reader, sr.id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(valueData) < int(sr.valueRange.start) {
|
||||
return nil, fmt.Errorf("value section too short, start: %d, size: %d", sr.valueRange.start, len(valueData))
|
||||
}
|
||||
entryStart := sr.valueRange.start + offset.start
|
||||
entryLimit := sr.valueRange.start + offset.limit
|
||||
if len(valueData) < int(entryStart) || len(valueData) < int(entryLimit) {
|
||||
return nil, fmt.Errorf("value section too short, start: %d, limit: %d, size: %d", entryStart, entryLimit, len(valueData))
|
||||
}
|
||||
return valueData[int(entryStart):int(entryLimit)], nil
|
||||
}
|
||||
|
||||
// trienodeHistoryReader provides read access to node data in the trie node history.
|
||||
// It resolves data from the underlying ancient store only when needed, minimizing
|
||||
// I/O overhead.
|
||||
type trienodeHistoryReader struct {
|
||||
id uint64 // ID of the associated trienode history
|
||||
reader ethdb.AncientReader // Database reader of ancient store
|
||||
keyRanges map[common.Hash]iRange // Key ranges identifying trie chunks
|
||||
valRanges map[common.Hash]iRange // Value ranges identifying trie chunks
|
||||
iReaders map[common.Hash]*singleTrienodeHistoryReader // readers for each individual trie chunk
|
||||
}
|
||||
|
||||
// newTrienodeHistoryReader constructs the reader for specific trienode history.
|
||||
func newTrienodeHistoryReader(id uint64, reader ethdb.AncientReader) (*trienodeHistoryReader, error) {
|
||||
r := &trienodeHistoryReader{
|
||||
id: id,
|
||||
reader: reader,
|
||||
keyRanges: make(map[common.Hash]iRange),
|
||||
valRanges: make(map[common.Hash]iRange),
|
||||
iReaders: make(map[common.Hash]*singleTrienodeHistoryReader),
|
||||
}
|
||||
if err := r.decodeHeader(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return r, nil
|
||||
}
|
||||
|
||||
// decodeHeader decodes the header section of trienode history.
|
||||
func (r *trienodeHistoryReader) decodeHeader() error {
|
||||
header, err := rawdb.ReadTrienodeHistoryHeader(r.reader, r.id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, owners, keyOffsets, valOffsets, err := decodeHeader(header)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for i, owner := range owners {
|
||||
// Decode the key range for this trie chunk
|
||||
var keyLimit uint32
|
||||
if i == len(owners)-1 {
|
||||
keyLimit = math.MaxUint32
|
||||
} else {
|
||||
keyLimit = keyOffsets[i+1]
|
||||
}
|
||||
r.keyRanges[owner] = iRange{
|
||||
start: keyOffsets[i],
|
||||
limit: keyLimit,
|
||||
}
|
||||
|
||||
// Decode the value range for this trie chunk
|
||||
var valLimit uint32
|
||||
if i == len(owners)-1 {
|
||||
valLimit = math.MaxUint32
|
||||
} else {
|
||||
valLimit = valOffsets[i+1]
|
||||
}
|
||||
r.valRanges[owner] = iRange{
|
||||
start: valOffsets[i],
|
||||
limit: valLimit,
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// read retrieves the trie node data with the provided TrieID and node path.
|
||||
func (r *trienodeHistoryReader) read(owner common.Hash, path string) ([]byte, error) {
|
||||
ir, ok := r.iReaders[owner]
|
||||
if !ok {
|
||||
keyRange, exists := r.keyRanges[owner]
|
||||
if !exists {
|
||||
return nil, fmt.Errorf("trie %x is unknown", owner)
|
||||
}
|
||||
valRange, exists := r.valRanges[owner]
|
||||
if !exists {
|
||||
return nil, fmt.Errorf("trie %x is unknown", owner)
|
||||
}
|
||||
var err error
|
||||
ir, err = newSingleTrienodeHistoryReader(r.id, r.reader, keyRange, valRange)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
r.iReaders[owner] = ir
|
||||
}
|
||||
return ir.read(path)
|
||||
}
|
||||
|
||||
// writeTrienodeHistory persists the trienode history associated with the given diff layer.
|
||||
// nolint:unused
|
||||
func writeTrienodeHistory(writer ethdb.AncientWriter, dl *diffLayer) error {
|
||||
start := time.Now()
|
||||
h := newTrienodeHistory(dl.rootHash(), dl.parent.rootHash(), dl.block, dl.nodes.nodeOrigin)
|
||||
header, keySection, valueSection, err := h.encode()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Write history data into five freezer table respectively.
|
||||
if err := rawdb.WriteTrienodeHistory(writer, dl.stateID(), header, keySection, valueSection); err != nil {
|
||||
return err
|
||||
}
|
||||
trienodeHistoryDataBytesMeter.Mark(int64(len(valueSection)))
|
||||
trienodeHistoryIndexBytesMeter.Mark(int64(len(header) + len(keySection)))
|
||||
trienodeHistoryBuildTimeMeter.UpdateSince(start)
|
||||
|
||||
log.Debug(
|
||||
"Stored trienode history", "id", dl.stateID(), "block", dl.block,
|
||||
"header", common.StorageSize(len(header)),
|
||||
"keySection", common.StorageSize(len(keySection)),
|
||||
"valueSection", common.StorageSize(len(valueSection)),
|
||||
"elapsed", common.PrettyDuration(time.Since(start)),
|
||||
)
|
||||
return nil
|
||||
}
|
||||
|
||||
// readTrienodeMetadata resolves the metadata of the specified trienode history.
|
||||
// nolint:unused
|
||||
func readTrienodeMetadata(reader ethdb.AncientReader, id uint64) (*trienodeMetadata, error) {
|
||||
header, err := rawdb.ReadTrienodeHistoryHeader(reader, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
metadata, _, _, _, err := decodeHeader(header)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return metadata, nil
|
||||
}
|
||||
|
||||
// readTrienodeHistory resolves a single trienode history object with specific id.
|
||||
func readTrienodeHistory(reader ethdb.AncientReader, id uint64) (*trienodeHistory, error) {
|
||||
header, keySection, valueSection, err := rawdb.ReadTrienodeHistory(reader, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var h trienodeHistory
|
||||
if err := h.decode(header, keySection, valueSection); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &h, nil
|
||||
}
|
||||
|
||||
// readTrienodeHistories resolves a list of trienode histories with the specific range.
|
||||
func readTrienodeHistories(reader ethdb.AncientReader, start uint64, count uint64) ([]history, error) {
|
||||
headers, keySections, valueSections, err := rawdb.ReadTrienodeHistoryList(reader, start, count)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var res []history
|
||||
for i, header := range headers {
|
||||
var h trienodeHistory
|
||||
if err := h.decode(header, keySections[i], valueSections[i]); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res = append(res, &h)
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
736
triedb/pathdb/history_trienode_test.go
Normal file
736
triedb/pathdb/history_trienode_test.go
Normal file
|
|
@ -0,0 +1,736 @@
|
|||
// 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 pathdb
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"math/rand"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/internal/testrand"
|
||||
)
|
||||
|
||||
// randomTrienodes generates a random trienode set.
|
||||
func randomTrienodes(n int) (map[common.Hash]map[string][]byte, common.Hash) {
|
||||
var (
|
||||
root common.Hash
|
||||
nodes = make(map[common.Hash]map[string][]byte)
|
||||
)
|
||||
for i := 0; i < n; i++ {
|
||||
owner := testrand.Hash()
|
||||
if i == 0 {
|
||||
owner = common.Hash{}
|
||||
}
|
||||
nodes[owner] = make(map[string][]byte)
|
||||
|
||||
for j := 0; j < 10; j++ {
|
||||
path := testrand.Bytes(rand.Intn(10))
|
||||
for z := 0; z < len(path); z++ {
|
||||
nodes[owner][string(path[:z])] = testrand.Bytes(rand.Intn(128))
|
||||
}
|
||||
}
|
||||
// zero-size trie node, representing it was non-existent before
|
||||
for j := 0; j < 10; j++ {
|
||||
path := testrand.Bytes(32)
|
||||
nodes[owner][string(path)] = nil
|
||||
}
|
||||
// root node with zero-size path
|
||||
rnode := testrand.Bytes(256)
|
||||
nodes[owner][""] = rnode
|
||||
if owner == (common.Hash{}) {
|
||||
root = crypto.Keccak256Hash(rnode)
|
||||
}
|
||||
}
|
||||
return nodes, root
|
||||
}
|
||||
|
||||
func makeTrienodeHistory() *trienodeHistory {
|
||||
nodes, root := randomTrienodes(10)
|
||||
return newTrienodeHistory(root, common.Hash{}, 1, nodes)
|
||||
}
|
||||
|
||||
func makeTrienodeHistories(n int) []*trienodeHistory {
|
||||
var (
|
||||
parent common.Hash
|
||||
result []*trienodeHistory
|
||||
)
|
||||
for i := 0; i < n; i++ {
|
||||
nodes, root := randomTrienodes(10)
|
||||
result = append(result, newTrienodeHistory(root, parent, uint64(i+1), nodes))
|
||||
parent = root
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func TestEncodeDecodeTrienodeHistory(t *testing.T) {
|
||||
var (
|
||||
dec trienodeHistory
|
||||
obj = makeTrienodeHistory()
|
||||
)
|
||||
header, keySection, valueSection, err := obj.encode()
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to encode trienode history: %v", err)
|
||||
}
|
||||
if err := dec.decode(header, keySection, valueSection); err != nil {
|
||||
t.Fatalf("Failed to decode trienode history: %v", err)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(obj.meta, dec.meta) {
|
||||
t.Fatal("trienode metadata is mismatched")
|
||||
}
|
||||
if !compareList(dec.owners, obj.owners) {
|
||||
t.Fatal("trie owner list is mismatched")
|
||||
}
|
||||
if !compareMapList(dec.nodeList, obj.nodeList) {
|
||||
t.Fatal("trienode list is mismatched")
|
||||
}
|
||||
if !compareMapSet(dec.nodes, obj.nodes) {
|
||||
t.Fatal("trienode content is mismatched")
|
||||
}
|
||||
|
||||
// Re-encode again, ensuring the encoded blob still match
|
||||
header2, keySection2, valueSection2, err := dec.encode()
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to encode trienode history: %v", err)
|
||||
}
|
||||
if !bytes.Equal(header, header2) {
|
||||
t.Fatal("re-encoded header is mismatched")
|
||||
}
|
||||
if !bytes.Equal(keySection, keySection2) {
|
||||
t.Fatal("re-encoded key section is mismatched")
|
||||
}
|
||||
if !bytes.Equal(valueSection, valueSection2) {
|
||||
t.Fatal("re-encoded value section is mismatched")
|
||||
}
|
||||
}
|
||||
|
||||
func TestTrienodeHistoryReader(t *testing.T) {
|
||||
var (
|
||||
hs = makeTrienodeHistories(10)
|
||||
freezer, _ = rawdb.NewTrienodeFreezer(t.TempDir(), false, false)
|
||||
)
|
||||
defer freezer.Close()
|
||||
|
||||
for i, h := range hs {
|
||||
header, keySection, valueSection, _ := h.encode()
|
||||
if err := rawdb.WriteTrienodeHistory(freezer, uint64(i+1), header, keySection, valueSection); err != nil {
|
||||
t.Fatalf("Failed to write trienode history: %v", err)
|
||||
}
|
||||
}
|
||||
for i, h := range hs {
|
||||
tr, err := newTrienodeHistoryReader(uint64(i+1), freezer)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to construct the history reader: %v", err)
|
||||
}
|
||||
for _, owner := range h.owners {
|
||||
nodes := h.nodes[owner]
|
||||
for key, value := range nodes {
|
||||
blob, err := tr.read(owner, key)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to read trienode history: %v", err)
|
||||
}
|
||||
if !bytes.Equal(blob, value) {
|
||||
t.Fatalf("Unexpected trie node data, want: %v, got: %v", value, blob)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for i, h := range hs {
|
||||
metadata, err := readTrienodeMetadata(freezer, uint64(i+1))
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to read trienode history metadata: %v", err)
|
||||
}
|
||||
if !reflect.DeepEqual(h.meta, metadata) {
|
||||
t.Fatalf("Unexpected trienode metadata, want: %v, got: %v", h.meta, metadata)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestEmptyTrienodeHistory tests encoding/decoding of empty trienode history
|
||||
func TestEmptyTrienodeHistory(t *testing.T) {
|
||||
h := newTrienodeHistory(common.Hash{}, common.Hash{}, 1, make(map[common.Hash]map[string][]byte))
|
||||
|
||||
// Test encoding empty history
|
||||
header, keySection, valueSection, err := h.encode()
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to encode empty trienode history: %v", err)
|
||||
}
|
||||
|
||||
// Verify sections are minimal but valid
|
||||
if len(header) == 0 {
|
||||
t.Fatal("Header should not be empty")
|
||||
}
|
||||
if len(keySection) != 0 {
|
||||
t.Fatal("Key section should be empty for empty history")
|
||||
}
|
||||
if len(valueSection) != 0 {
|
||||
t.Fatal("Value section should be empty for empty history")
|
||||
}
|
||||
|
||||
// Test decoding empty history
|
||||
var decoded trienodeHistory
|
||||
if err := decoded.decode(header, keySection, valueSection); err != nil {
|
||||
t.Fatalf("Failed to decode empty trienode history: %v", err)
|
||||
}
|
||||
|
||||
if len(decoded.owners) != 0 {
|
||||
t.Fatal("Decoded history should have no owners")
|
||||
}
|
||||
if len(decoded.nodeList) != 0 {
|
||||
t.Fatal("Decoded history should have no node lists")
|
||||
}
|
||||
if len(decoded.nodes) != 0 {
|
||||
t.Fatal("Decoded history should have no nodes")
|
||||
}
|
||||
}
|
||||
|
||||
// TestSingleTrieHistory tests encoding/decoding of history with single trie
|
||||
func TestSingleTrieHistory(t *testing.T) {
|
||||
nodes := make(map[common.Hash]map[string][]byte)
|
||||
owner := testrand.Hash()
|
||||
nodes[owner] = make(map[string][]byte)
|
||||
|
||||
// Add some nodes with various sizes
|
||||
nodes[owner][""] = testrand.Bytes(32) // empty key
|
||||
nodes[owner]["a"] = testrand.Bytes(1) // small value
|
||||
nodes[owner]["bb"] = testrand.Bytes(100) // medium value
|
||||
nodes[owner]["ccc"] = testrand.Bytes(1000) // large value
|
||||
nodes[owner]["dddd"] = testrand.Bytes(0) // empty value
|
||||
|
||||
h := newTrienodeHistory(common.Hash{}, common.Hash{}, 1, nodes)
|
||||
testEncodeDecode(t, h)
|
||||
}
|
||||
|
||||
// TestMultipleTries tests multiple tries with different node counts
|
||||
func TestMultipleTries(t *testing.T) {
|
||||
nodes := make(map[common.Hash]map[string][]byte)
|
||||
|
||||
// First trie with many small nodes
|
||||
owner1 := testrand.Hash()
|
||||
nodes[owner1] = make(map[string][]byte)
|
||||
for i := 0; i < 100; i++ {
|
||||
key := string(testrand.Bytes(rand.Intn(10)))
|
||||
nodes[owner1][key] = testrand.Bytes(rand.Intn(50))
|
||||
}
|
||||
|
||||
// Second trie with few large nodes
|
||||
owner2 := testrand.Hash()
|
||||
nodes[owner2] = make(map[string][]byte)
|
||||
for i := 0; i < 5; i++ {
|
||||
key := string(testrand.Bytes(rand.Intn(20)))
|
||||
nodes[owner2][key] = testrand.Bytes(1000 + rand.Intn(1000))
|
||||
}
|
||||
|
||||
// Third trie with nil values (zero-size nodes)
|
||||
owner3 := testrand.Hash()
|
||||
nodes[owner3] = make(map[string][]byte)
|
||||
for i := 0; i < 10; i++ {
|
||||
key := string(testrand.Bytes(rand.Intn(15)))
|
||||
nodes[owner3][key] = nil
|
||||
}
|
||||
|
||||
h := newTrienodeHistory(common.Hash{}, common.Hash{}, 1, nodes)
|
||||
testEncodeDecode(t, h)
|
||||
}
|
||||
|
||||
// TestLargeNodeValues tests encoding/decoding with very large node values
|
||||
func TestLargeNodeValues(t *testing.T) {
|
||||
nodes := make(map[common.Hash]map[string][]byte)
|
||||
owner := testrand.Hash()
|
||||
nodes[owner] = make(map[string][]byte)
|
||||
|
||||
// Test with progressively larger values
|
||||
sizes := []int{1024, 10 * 1024, 100 * 1024, 1024 * 1024} // 1KB, 10KB, 100KB, 1MB
|
||||
for _, size := range sizes {
|
||||
key := string(testrand.Bytes(10))
|
||||
nodes[owner][key] = testrand.Bytes(size)
|
||||
|
||||
h := newTrienodeHistory(common.Hash{}, common.Hash{}, 1, nodes)
|
||||
testEncodeDecode(t, h)
|
||||
t.Logf("Successfully tested encoding/decoding with %dKB value", size/1024)
|
||||
}
|
||||
}
|
||||
|
||||
// TestNilNodeValues tests encoding/decoding with nil (zero-length) node values
|
||||
func TestNilNodeValues(t *testing.T) {
|
||||
nodes := make(map[common.Hash]map[string][]byte)
|
||||
owner := testrand.Hash()
|
||||
nodes[owner] = make(map[string][]byte)
|
||||
|
||||
// Mix of nil and non-nil values
|
||||
nodes[owner]["nil"] = nil
|
||||
nodes[owner]["data1"] = []byte("some data")
|
||||
nodes[owner]["data2"] = []byte("more data")
|
||||
|
||||
h := newTrienodeHistory(common.Hash{}, common.Hash{}, 1, nodes)
|
||||
testEncodeDecode(t, h)
|
||||
|
||||
// Verify nil values are preserved
|
||||
_, ok := h.nodes[owner]["nil"]
|
||||
if !ok {
|
||||
t.Fatal("Nil value should be preserved")
|
||||
}
|
||||
}
|
||||
|
||||
// TestCorruptedHeader tests error handling for corrupted header data
|
||||
func TestCorruptedHeader(t *testing.T) {
|
||||
h := makeTrienodeHistory()
|
||||
header, keySection, valueSection, _ := h.encode()
|
||||
|
||||
// Test corrupted version
|
||||
corruptedHeader := make([]byte, len(header))
|
||||
copy(corruptedHeader, header)
|
||||
corruptedHeader[0] = 0xFF // Invalid version
|
||||
|
||||
var decoded trienodeHistory
|
||||
if err := decoded.decode(corruptedHeader, keySection, valueSection); err == nil {
|
||||
t.Fatal("Expected error for corrupted version")
|
||||
}
|
||||
|
||||
// Test truncated header
|
||||
truncatedHeader := header[:len(header)-5]
|
||||
if err := decoded.decode(truncatedHeader, keySection, valueSection); err == nil {
|
||||
t.Fatal("Expected error for truncated header")
|
||||
}
|
||||
|
||||
// Test header with invalid trie header size
|
||||
invalidHeader := make([]byte, len(header))
|
||||
copy(invalidHeader, header)
|
||||
invalidHeader = invalidHeader[:trienodeMetadataSize+5] // Not divisible by trie header size
|
||||
|
||||
if err := decoded.decode(invalidHeader, keySection, valueSection); err == nil {
|
||||
t.Fatal("Expected error for invalid header size")
|
||||
}
|
||||
}
|
||||
|
||||
// TestCorruptedKeySection tests error handling for corrupted key section data
|
||||
func TestCorruptedKeySection(t *testing.T) {
|
||||
h := makeTrienodeHistory()
|
||||
header, keySection, valueSection, _ := h.encode()
|
||||
|
||||
// Test empty key section when header indicates data
|
||||
if len(keySection) > 0 {
|
||||
var decoded trienodeHistory
|
||||
if err := decoded.decode(header, []byte{}, valueSection); err == nil {
|
||||
t.Fatal("Expected error for empty key section with non-empty header")
|
||||
}
|
||||
}
|
||||
|
||||
// Test truncated key section
|
||||
if len(keySection) > 10 {
|
||||
truncatedKeySection := keySection[:len(keySection)-10]
|
||||
var decoded trienodeHistory
|
||||
if err := decoded.decode(header, truncatedKeySection, valueSection); err == nil {
|
||||
t.Fatal("Expected error for truncated key section")
|
||||
}
|
||||
}
|
||||
|
||||
// Test corrupted key section with invalid varint
|
||||
corruptedKeySection := make([]byte, len(keySection))
|
||||
copy(corruptedKeySection, keySection)
|
||||
if len(corruptedKeySection) > 5 {
|
||||
corruptedKeySection[5] = 0xFF // Corrupt varint encoding
|
||||
var decoded trienodeHistory
|
||||
if err := decoded.decode(header, corruptedKeySection, valueSection); err == nil {
|
||||
t.Fatal("Expected error for corrupted varint in key section")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestCorruptedValueSection tests error handling for corrupted value section data
|
||||
func TestCorruptedValueSection(t *testing.T) {
|
||||
h := makeTrienodeHistory()
|
||||
header, keySection, valueSection, _ := h.encode()
|
||||
|
||||
// Test truncated value section
|
||||
if len(valueSection) > 10 {
|
||||
truncatedValueSection := valueSection[:len(valueSection)-10]
|
||||
var decoded trienodeHistory
|
||||
if err := decoded.decode(header, keySection, truncatedValueSection); err == nil {
|
||||
t.Fatal("Expected error for truncated value section")
|
||||
}
|
||||
}
|
||||
|
||||
// Test empty value section when key section indicates data exists
|
||||
if len(valueSection) > 0 {
|
||||
var decoded trienodeHistory
|
||||
if err := decoded.decode(header, keySection, []byte{}); err == nil {
|
||||
t.Fatal("Expected error for empty value section with non-empty key section")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestInvalidOffsets tests error handling for invalid offsets in encoded data
|
||||
func TestInvalidOffsets(t *testing.T) {
|
||||
h := makeTrienodeHistory()
|
||||
header, keySection, valueSection, _ := h.encode()
|
||||
|
||||
// Corrupt key offset in header (make it larger than key section)
|
||||
corruptedHeader := make([]byte, len(header))
|
||||
copy(corruptedHeader, header)
|
||||
corruptedHeader[trienodeMetadataSize+common.HashLength] = 0xff
|
||||
|
||||
var dec1 trienodeHistory
|
||||
if err := dec1.decode(corruptedHeader, keySection, valueSection); err == nil {
|
||||
t.Fatal("Expected error for invalid key offset")
|
||||
}
|
||||
|
||||
// Corrupt value offset in header (make it larger than value section)
|
||||
corruptedHeader = make([]byte, len(header))
|
||||
copy(corruptedHeader, header)
|
||||
corruptedHeader[trienodeMetadataSize+common.HashLength+4] = 0xff
|
||||
|
||||
var dec2 trienodeHistory
|
||||
if err := dec2.decode(corruptedHeader, keySection, valueSection); err == nil {
|
||||
t.Fatal("Expected error for invalid value offset")
|
||||
}
|
||||
}
|
||||
|
||||
// TestTrienodeHistoryReaderNonExistentPath tests reading non-existent paths
|
||||
func TestTrienodeHistoryReaderNonExistentPath(t *testing.T) {
|
||||
var (
|
||||
h = makeTrienodeHistory()
|
||||
freezer, _ = rawdb.NewTrienodeFreezer(t.TempDir(), false, false)
|
||||
)
|
||||
defer freezer.Close()
|
||||
|
||||
header, keySection, valueSection, _ := h.encode()
|
||||
if err := rawdb.WriteTrienodeHistory(freezer, 1, header, keySection, valueSection); err != nil {
|
||||
t.Fatalf("Failed to write trienode history: %v", err)
|
||||
}
|
||||
|
||||
tr, err := newTrienodeHistoryReader(1, freezer)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to construct history reader: %v", err)
|
||||
}
|
||||
|
||||
// Try to read a non-existent path
|
||||
_, err = tr.read(testrand.Hash(), "nonexistent")
|
||||
if err == nil {
|
||||
t.Fatal("Expected error for non-existent trie owner")
|
||||
}
|
||||
|
||||
// Try to read from existing owner but non-existent path
|
||||
owner := h.owners[0]
|
||||
_, err = tr.read(owner, "nonexistent-path")
|
||||
if err == nil {
|
||||
t.Fatal("Expected error for non-existent path")
|
||||
}
|
||||
}
|
||||
|
||||
// TestTrienodeHistoryReaderNilValues tests reading nil (zero-length) values
|
||||
func TestTrienodeHistoryReaderNilValues(t *testing.T) {
|
||||
nodes := make(map[common.Hash]map[string][]byte)
|
||||
owner := testrand.Hash()
|
||||
nodes[owner] = make(map[string][]byte)
|
||||
|
||||
// Add some nil values
|
||||
nodes[owner]["nil1"] = nil
|
||||
nodes[owner]["nil2"] = nil
|
||||
nodes[owner]["data1"] = []byte("some data")
|
||||
|
||||
h := newTrienodeHistory(common.Hash{}, common.Hash{}, 1, nodes)
|
||||
|
||||
var freezer, _ = rawdb.NewTrienodeFreezer(t.TempDir(), false, false)
|
||||
defer freezer.Close()
|
||||
|
||||
header, keySection, valueSection, _ := h.encode()
|
||||
if err := rawdb.WriteTrienodeHistory(freezer, 1, header, keySection, valueSection); err != nil {
|
||||
t.Fatalf("Failed to write trienode history: %v", err)
|
||||
}
|
||||
|
||||
tr, err := newTrienodeHistoryReader(1, freezer)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to construct history reader: %v", err)
|
||||
}
|
||||
|
||||
// Test reading nil values
|
||||
data1, err := tr.read(owner, "nil1")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to read nil value: %v", err)
|
||||
}
|
||||
if len(data1) != 0 {
|
||||
t.Fatal("Expected nil data for nil value")
|
||||
}
|
||||
|
||||
data2, err := tr.read(owner, "nil2")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to read nil value: %v", err)
|
||||
}
|
||||
if len(data2) != 0 {
|
||||
t.Fatal("Expected nil data for nil value")
|
||||
}
|
||||
|
||||
// Test reading non-nil value
|
||||
data3, err := tr.read(owner, "data1")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to read non-nil value: %v", err)
|
||||
}
|
||||
if !bytes.Equal(data3, []byte("some data")) {
|
||||
t.Fatal("Data mismatch for non-nil value")
|
||||
}
|
||||
}
|
||||
|
||||
// TestTrienodeHistoryReaderNilKey tests reading nil (zero-length) key
|
||||
func TestTrienodeHistoryReaderNilKey(t *testing.T) {
|
||||
nodes := make(map[common.Hash]map[string][]byte)
|
||||
owner := testrand.Hash()
|
||||
nodes[owner] = make(map[string][]byte)
|
||||
|
||||
// Add some nil values
|
||||
nodes[owner][""] = []byte("some data")
|
||||
nodes[owner]["data1"] = []byte("some data")
|
||||
|
||||
h := newTrienodeHistory(common.Hash{}, common.Hash{}, 1, nodes)
|
||||
|
||||
var freezer, _ = rawdb.NewTrienodeFreezer(t.TempDir(), false, false)
|
||||
defer freezer.Close()
|
||||
|
||||
header, keySection, valueSection, _ := h.encode()
|
||||
if err := rawdb.WriteTrienodeHistory(freezer, 1, header, keySection, valueSection); err != nil {
|
||||
t.Fatalf("Failed to write trienode history: %v", err)
|
||||
}
|
||||
|
||||
tr, err := newTrienodeHistoryReader(1, freezer)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to construct history reader: %v", err)
|
||||
}
|
||||
|
||||
// Test reading nil values
|
||||
data1, err := tr.read(owner, "")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to read nil value: %v", err)
|
||||
}
|
||||
if !bytes.Equal(data1, []byte("some data")) {
|
||||
t.Fatal("Data mismatch for nil key")
|
||||
}
|
||||
|
||||
// Test reading non-nil value
|
||||
data2, err := tr.read(owner, "data1")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to read non-nil value: %v", err)
|
||||
}
|
||||
if !bytes.Equal(data2, []byte("some data")) {
|
||||
t.Fatal("Data mismatch for non-nil key")
|
||||
}
|
||||
}
|
||||
|
||||
// TestTrienodeHistoryReaderIterator tests the iterator functionality
|
||||
func TestTrienodeHistoryReaderIterator(t *testing.T) {
|
||||
h := makeTrienodeHistory()
|
||||
|
||||
// Count expected entries
|
||||
expectedCount := 0
|
||||
expectedNodes := make(map[stateIdent]bool)
|
||||
for owner, nodeList := range h.nodeList {
|
||||
expectedCount += len(nodeList)
|
||||
for _, node := range nodeList {
|
||||
expectedNodes[stateIdent{
|
||||
typ: typeTrienode,
|
||||
addressHash: owner,
|
||||
path: node,
|
||||
}] = true
|
||||
}
|
||||
}
|
||||
|
||||
// Test the iterator
|
||||
actualCount := 0
|
||||
for x := range h.forEach() {
|
||||
_ = x
|
||||
actualCount++
|
||||
}
|
||||
if actualCount != expectedCount {
|
||||
t.Fatalf("Iterator count mismatch: expected %d, got %d", expectedCount, actualCount)
|
||||
}
|
||||
|
||||
// Test that iterator yields expected state identifiers
|
||||
seen := make(map[stateIdent]bool)
|
||||
for ident := range h.forEach() {
|
||||
if ident.typ != typeTrienode {
|
||||
t.Fatal("Iterator should only yield trienode history identifiers")
|
||||
}
|
||||
key := stateIdent{typ: ident.typ, addressHash: ident.addressHash, path: ident.path}
|
||||
if seen[key] {
|
||||
t.Fatal("Iterator yielded duplicate identifier")
|
||||
}
|
||||
seen[key] = true
|
||||
|
||||
if !expectedNodes[key] {
|
||||
t.Fatalf("Unexpected yielded identifier %v", key)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestSharedLen tests the sharedLen helper function
|
||||
func TestSharedLen(t *testing.T) {
|
||||
tests := []struct {
|
||||
a, b []byte
|
||||
expected int
|
||||
}{
|
||||
// Empty strings
|
||||
{[]byte(""), []byte(""), 0},
|
||||
// One empty string
|
||||
{[]byte(""), []byte("abc"), 0},
|
||||
{[]byte("abc"), []byte(""), 0},
|
||||
// No common prefix
|
||||
{[]byte("abc"), []byte("def"), 0},
|
||||
// Partial common prefix
|
||||
{[]byte("abc"), []byte("abx"), 2},
|
||||
{[]byte("prefix"), []byte("pref"), 4},
|
||||
// Complete common prefix (shorter first)
|
||||
{[]byte("ab"), []byte("abcd"), 2},
|
||||
// Complete common prefix (longer first)
|
||||
{[]byte("abcd"), []byte("ab"), 2},
|
||||
// Identical strings
|
||||
{[]byte("identical"), []byte("identical"), 9},
|
||||
// Binary data
|
||||
{[]byte{0x00, 0x01, 0x02}, []byte{0x00, 0x01, 0x03}, 2},
|
||||
// Large strings
|
||||
{bytes.Repeat([]byte("a"), 1000), bytes.Repeat([]byte("a"), 1000), 1000},
|
||||
{bytes.Repeat([]byte("a"), 1000), append(bytes.Repeat([]byte("a"), 999), []byte("b")...), 999},
|
||||
}
|
||||
|
||||
for i, test := range tests {
|
||||
result := sharedLen(test.a, test.b)
|
||||
if result != test.expected {
|
||||
t.Errorf("Test %d: sharedLen(%q, %q) = %d, expected %d",
|
||||
i, test.a, test.b, result, test.expected)
|
||||
}
|
||||
// Test commutativity
|
||||
resultReverse := sharedLen(test.b, test.a)
|
||||
if result != resultReverse {
|
||||
t.Errorf("Test %d: sharedLen is not commutative: sharedLen(a,b)=%d, sharedLen(b,a)=%d",
|
||||
i, result, resultReverse)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestDecodeHeaderCorruptedData tests decodeHeader with corrupted data
|
||||
func TestDecodeHeaderCorruptedData(t *testing.T) {
|
||||
// Create valid header data first
|
||||
h := makeTrienodeHistory()
|
||||
header, _, _, _ := h.encode()
|
||||
|
||||
// Test with empty header
|
||||
_, _, _, _, err := decodeHeader([]byte{})
|
||||
if err == nil {
|
||||
t.Fatal("Expected error for empty header")
|
||||
}
|
||||
|
||||
// Test with invalid version
|
||||
corruptedVersion := make([]byte, len(header))
|
||||
copy(corruptedVersion, header)
|
||||
corruptedVersion[0] = 0xFF
|
||||
_, _, _, _, err = decodeHeader(corruptedVersion)
|
||||
if err == nil {
|
||||
t.Fatal("Expected error for invalid version")
|
||||
}
|
||||
|
||||
// Test with truncated header (not divisible by trie header size)
|
||||
truncated := header[:trienodeMetadataSize+5]
|
||||
_, _, _, _, err = decodeHeader(truncated)
|
||||
if err == nil {
|
||||
t.Fatal("Expected error for truncated header")
|
||||
}
|
||||
|
||||
// Test with unordered trie owners
|
||||
unordered := make([]byte, len(header))
|
||||
copy(unordered, header)
|
||||
|
||||
// Swap two owner hashes to make them unordered
|
||||
hash1Start := trienodeMetadataSize
|
||||
hash2Start := trienodeMetadataSize + trienodeTrieHeaderSize
|
||||
hash1 := unordered[hash1Start : hash1Start+common.HashLength]
|
||||
hash2 := unordered[hash2Start : hash2Start+common.HashLength]
|
||||
|
||||
// Only swap if they would be out of order
|
||||
copy(unordered[hash1Start:hash1Start+common.HashLength], hash2)
|
||||
copy(unordered[hash2Start:hash2Start+common.HashLength], hash1)
|
||||
|
||||
_, _, _, _, err = decodeHeader(unordered)
|
||||
if err == nil {
|
||||
t.Fatal("Expected error for unordered trie owners")
|
||||
}
|
||||
}
|
||||
|
||||
// TestDecodeSingleCorruptedData tests decodeSingle with corrupted data
|
||||
func TestDecodeSingleCorruptedData(t *testing.T) {
|
||||
h := makeTrienodeHistory()
|
||||
_, keySection, _, _ := h.encode()
|
||||
|
||||
// Test with empty key section
|
||||
_, err := decodeSingle([]byte{}, nil)
|
||||
if err == nil {
|
||||
t.Fatal("Expected error for empty key section")
|
||||
}
|
||||
|
||||
// Test with key section too small for trailer
|
||||
if len(keySection) > 0 {
|
||||
_, err := decodeSingle(keySection[:3], nil) // Less than 4 bytes for trailer
|
||||
if err == nil {
|
||||
t.Fatal("Expected error for key section too small for trailer")
|
||||
}
|
||||
}
|
||||
|
||||
// Test with corrupted varint in key section
|
||||
corrupted := make([]byte, len(keySection))
|
||||
copy(corrupted, keySection)
|
||||
corrupted[5] = 0xFF // Corrupt varint
|
||||
_, err = decodeSingle(corrupted, nil)
|
||||
if err == nil {
|
||||
t.Fatal("Expected error for corrupted varint")
|
||||
}
|
||||
|
||||
// Test with corrupted trailer (invalid restart count)
|
||||
corrupted = make([]byte, len(keySection))
|
||||
copy(corrupted, keySection)
|
||||
// Set restart count to something too large
|
||||
binary.BigEndian.PutUint32(corrupted[len(corrupted)-4:], 10000)
|
||||
_, err = decodeSingle(corrupted, nil)
|
||||
if err == nil {
|
||||
t.Fatal("Expected error for invalid restart count")
|
||||
}
|
||||
}
|
||||
|
||||
// Helper function to test encode/decode cycle
|
||||
func testEncodeDecode(t *testing.T, h *trienodeHistory) {
|
||||
header, keySection, valueSection, err := h.encode()
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to encode trienode history: %v", err)
|
||||
}
|
||||
|
||||
var decoded trienodeHistory
|
||||
if err := decoded.decode(header, keySection, valueSection); err != nil {
|
||||
t.Fatalf("Failed to decode trienode history: %v", err)
|
||||
}
|
||||
|
||||
// Compare the decoded history with original
|
||||
if !compareList(decoded.owners, h.owners) {
|
||||
t.Fatal("Trie owner list mismatch")
|
||||
}
|
||||
if !compareMapList(decoded.nodeList, h.nodeList) {
|
||||
t.Fatal("Trienode list mismatch")
|
||||
}
|
||||
if !compareMapSet(decoded.nodes, h.nodes) {
|
||||
t.Fatal("Trienode content mismatch")
|
||||
}
|
||||
}
|
||||
|
|
@ -69,12 +69,21 @@ var (
|
|||
gcStorageMeter = metrics.NewRegisteredMeter("pathdb/gc/storage/count", nil)
|
||||
gcStorageBytesMeter = metrics.NewRegisteredMeter("pathdb/gc/storage/bytes", nil)
|
||||
|
||||
historyBuildTimeMeter = metrics.NewRegisteredResettingTimer("pathdb/history/time", nil)
|
||||
historyDataBytesMeter = metrics.NewRegisteredMeter("pathdb/history/bytes/data", nil)
|
||||
historyIndexBytesMeter = metrics.NewRegisteredMeter("pathdb/history/bytes/index", nil)
|
||||
stateHistoryBuildTimeMeter = metrics.NewRegisteredResettingTimer("pathdb/history/state/time", nil)
|
||||
stateHistoryDataBytesMeter = metrics.NewRegisteredMeter("pathdb/history/state/bytes/data", nil)
|
||||
stateHistoryIndexBytesMeter = metrics.NewRegisteredMeter("pathdb/history/state/bytes/index", nil)
|
||||
|
||||
indexHistoryTimer = metrics.NewRegisteredResettingTimer("pathdb/history/index/time", nil)
|
||||
unindexHistoryTimer = metrics.NewRegisteredResettingTimer("pathdb/history/unindex/time", nil)
|
||||
//nolint:unused
|
||||
trienodeHistoryBuildTimeMeter = metrics.NewRegisteredResettingTimer("pathdb/history/trienode/time", nil)
|
||||
//nolint:unused
|
||||
trienodeHistoryDataBytesMeter = metrics.NewRegisteredMeter("pathdb/history/trienode/bytes/data", nil)
|
||||
//nolint:unused
|
||||
trienodeHistoryIndexBytesMeter = metrics.NewRegisteredMeter("pathdb/history/trienode/bytes/index", nil)
|
||||
|
||||
stateIndexHistoryTimer = metrics.NewRegisteredResettingTimer("pathdb/history/state/index/time", nil)
|
||||
stateUnindexHistoryTimer = metrics.NewRegisteredResettingTimer("pathdb/history/state/unindex/time", nil)
|
||||
trienodeIndexHistoryTimer = metrics.NewRegisteredResettingTimer("pathdb/history/trienode/index/time", nil)
|
||||
trienodeUnindexHistoryTimer = metrics.NewRegisteredResettingTimer("pathdb/history/trienode/unindex/time", nil)
|
||||
|
||||
lookupAddLayerTimer = metrics.NewRegisteredResettingTimer("pathdb/lookup/add/time", nil)
|
||||
lookupRemoveLayerTimer = metrics.NewRegisteredResettingTimer("pathdb/lookup/remove/time", nil)
|
||||
|
|
|
|||
Loading…
Reference in a new issue