mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-02-28 08:37:22 +00:00
Merge branch 'master' into release/1.16
This commit is contained in:
commit
737ffd1bf0
111 changed files with 21313 additions and 11798 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
|
||||
|
|
|
|||
|
|
@ -485,13 +485,13 @@ var bindTests = []struct {
|
|||
contract Defaulter {
|
||||
address public caller;
|
||||
|
||||
function() {
|
||||
fallback() external payable {
|
||||
caller = msg.sender;
|
||||
}
|
||||
}
|
||||
`,
|
||||
[]string{`6060604052606a8060106000396000f360606040523615601d5760e060020a6000350463fc9c8d3981146040575b605e6000805473ffffffffffffffffffffffffffffffffffffffff191633179055565b606060005473ffffffffffffffffffffffffffffffffffffffff1681565b005b6060908152602090f3`},
|
||||
[]string{`[{"constant":true,"inputs":[],"name":"caller","outputs":[{"name":"","type":"address"}],"type":"function"}]`},
|
||||
[]string{`608060405234801561000f575f80fd5b5061013d8061001d5f395ff3fe608060405260043610610021575f3560e01c8063fc9c8d391461006257610022565b5b335f806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055005b34801561006d575f80fd5b5061007661008c565b60405161008391906100ee565b60405180910390f35b5f8054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f6100d8826100af565b9050919050565b6100e8816100ce565b82525050565b5f6020820190506101015f8301846100df565b9291505056fea26469706673582212201e9273ecfb1f534644c77f09a25c21baaba81cf1c444ebc071e12a225a23c72964736f6c63430008140033`},
|
||||
[]string{`[{"stateMutability":"payable","type":"fallback"},{"inputs":[],"name":"caller","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"}]`},
|
||||
`
|
||||
"math/big"
|
||||
|
||||
|
|
|
|||
|
|
@ -277,8 +277,10 @@ func (c *BoundContract) RawCreationTransact(opts *TransactOpts, calldata []byte)
|
|||
// Transfer initiates a plain transaction to move funds to the contract, calling
|
||||
// its default method if one is available.
|
||||
func (c *BoundContract) Transfer(opts *TransactOpts) (*types.Transaction, error) {
|
||||
// todo(rjl493456442) check the payable fallback or receive is defined
|
||||
// or not, reject invalid transaction at the first place
|
||||
// Check if payable fallback or receive is defined
|
||||
if !c.abi.HasReceive() && !(c.abi.HasFallback() && c.abi.Fallback.IsPayable()) {
|
||||
return nil, fmt.Errorf("contract does not have a payable fallback or receive function")
|
||||
}
|
||||
return c.transact(opts, &c.address, nil)
|
||||
}
|
||||
|
||||
|
|
|
|||
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
|
||||
|
|
|
|||
33
build/ci.go
33
build/ci.go
|
|
@ -57,6 +57,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/cespare/cp"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/crypto/signify"
|
||||
"github.com/ethereum/go-ethereum/internal/build"
|
||||
"github.com/ethereum/go-ethereum/internal/download"
|
||||
|
|
@ -148,7 +149,7 @@ func executablePath(name string) string {
|
|||
func main() {
|
||||
log.SetFlags(log.Lshortfile)
|
||||
|
||||
if !build.FileExist(filepath.Join("build", "ci.go")) {
|
||||
if !common.FileExist(filepath.Join("build", "ci.go")) {
|
||||
log.Fatal("this script must be run from the root of the repository")
|
||||
}
|
||||
if len(os.Args) < 2 {
|
||||
|
|
@ -280,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}
|
||||
|
|
@ -314,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")
|
||||
}
|
||||
|
|
@ -895,7 +902,7 @@ func ppaUpload(workdir, ppa, sshUser string, files []string) {
|
|||
var idfile string
|
||||
if sshkey := getenvBase64("PPA_SSH_KEY"); len(sshkey) > 0 {
|
||||
idfile = filepath.Join(workdir, "sshkey")
|
||||
if !build.FileExist(idfile) {
|
||||
if !common.FileExist(idfile) {
|
||||
os.WriteFile(idfile, sshkey, 0600)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
|
@ -374,7 +374,10 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig,
|
|||
func MakePreState(db ethdb.Database, accounts types.GenesisAlloc) *state.StateDB {
|
||||
tdb := triedb.NewDatabase(db, &triedb.Config{Preimages: true})
|
||||
sdb := state.NewDatabase(tdb, nil)
|
||||
statedb, _ := state.New(types.EmptyRootHash, sdb)
|
||||
statedb, err := state.New(types.EmptyRootHash, sdb)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("failed to create initial state: %v", err))
|
||||
}
|
||||
for addr, a := range accounts {
|
||||
statedb.SetCode(addr, a.Code, tracing.CodeChangeUnspecified)
|
||||
statedb.SetNonce(addr, a.Nonce, tracing.NonceChangeGenesis)
|
||||
|
|
@ -384,8 +387,14 @@ func MakePreState(db ethdb.Database, accounts types.GenesisAlloc) *state.StateDB
|
|||
}
|
||||
}
|
||||
// Commit and re-open to start with a clean state.
|
||||
root, _ := statedb.Commit(0, false, false)
|
||||
statedb, _ = state.New(root, sdb)
|
||||
root, err := statedb.Commit(0, false, false)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("failed to commit initial state: %v", err))
|
||||
}
|
||||
statedb, err = state.New(root, sdb)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("failed to reopen state after commit: %v", err))
|
||||
}
|
||||
return statedb
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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=
|
||||
|
|
|
|||
|
|
@ -182,13 +182,14 @@ func (s *filterTestSuite) loadQueries() error {
|
|||
|
||||
// filterQuery is a single query for testing.
|
||||
type filterQuery struct {
|
||||
FromBlock int64 `json:"fromBlock"`
|
||||
ToBlock int64 `json:"toBlock"`
|
||||
Address []common.Address `json:"address"`
|
||||
Topics [][]common.Hash `json:"topics"`
|
||||
ResultHash *common.Hash `json:"resultHash,omitempty"`
|
||||
results []types.Log
|
||||
Err error `json:"error,omitempty"`
|
||||
FromBlock int64 `json:"fromBlock"`
|
||||
ToBlock int64 `json:"toBlock"`
|
||||
lastBlockHash common.Hash
|
||||
Address []common.Address `json:"address"`
|
||||
Topics [][]common.Hash `json:"topics"`
|
||||
ResultHash *common.Hash `json:"resultHash,omitempty"`
|
||||
results []types.Log
|
||||
Err error `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
func (fq *filterQuery) isWildcard() bool {
|
||||
|
|
|
|||
337
cmd/workload/filtertestfuzz.go
Normal file
337
cmd/workload/filtertestfuzz.go
Normal file
|
|
@ -0,0 +1,337 @@
|
|||
// Copyright 2025 The go-ethereum Authors
|
||||
// This file is part of go-ethereum.
|
||||
//
|
||||
// go-ethereum is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// go-ethereum is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"reflect"
|
||||
"slices"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/lru"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/rpc"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
const maxFilterRangeForTestFuzz = 300
|
||||
|
||||
var (
|
||||
filterFuzzCommand = &cli.Command{
|
||||
Name: "filterfuzz",
|
||||
Usage: "Generates queries and compares results against matches derived from receipts",
|
||||
ArgsUsage: "<RPC endpoint URL>",
|
||||
Action: filterFuzzCmd,
|
||||
Flags: []cli.Flag{},
|
||||
}
|
||||
)
|
||||
|
||||
// filterFuzzCmd is the main function of the filter fuzzer.
|
||||
func filterFuzzCmd(ctx *cli.Context) error {
|
||||
f := newFilterTestGen(ctx, maxFilterRangeForTestFuzz)
|
||||
var lastHead *types.Header
|
||||
headerCache := lru.NewCache[common.Hash, *types.Header](200)
|
||||
|
||||
commonAncestor := func(oldPtr, newPtr *types.Header) *types.Header {
|
||||
if oldPtr == nil || newPtr == nil {
|
||||
return nil
|
||||
}
|
||||
if newPtr.Number.Uint64() > oldPtr.Number.Uint64()+100 || oldPtr.Number.Uint64() > newPtr.Number.Uint64()+100 {
|
||||
return nil
|
||||
}
|
||||
for oldPtr.Hash() != newPtr.Hash() {
|
||||
if newPtr.Number.Uint64() >= oldPtr.Number.Uint64() {
|
||||
if parent, _ := headerCache.Get(newPtr.ParentHash); parent != nil {
|
||||
newPtr = parent
|
||||
} else {
|
||||
newPtr, _ = getHeaderByHash(f.client, newPtr.ParentHash)
|
||||
if newPtr == nil {
|
||||
return nil
|
||||
}
|
||||
headerCache.Add(newPtr.Hash(), newPtr)
|
||||
}
|
||||
}
|
||||
if oldPtr.Number.Uint64() > newPtr.Number.Uint64() {
|
||||
oldPtr, _ = headerCache.Get(oldPtr.ParentHash)
|
||||
if oldPtr == nil {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
return newPtr
|
||||
}
|
||||
|
||||
fetchHead := func() (*types.Header, bool) {
|
||||
currentHead, err := getLatestHeader(f.client)
|
||||
if err != nil {
|
||||
fmt.Println("Could not fetch head block", err)
|
||||
return nil, false
|
||||
}
|
||||
headerCache.Add(currentHead.Hash(), currentHead)
|
||||
if lastHead != nil && currentHead.Hash() == lastHead.Hash() {
|
||||
return currentHead, false
|
||||
}
|
||||
f.blockLimit = currentHead.Number.Int64()
|
||||
ca := commonAncestor(lastHead, currentHead)
|
||||
fmt.Print("*** New head ", f.blockLimit)
|
||||
if ca == nil {
|
||||
fmt.Println(" <no common ancestor>")
|
||||
} else {
|
||||
if reorged := lastHead.Number.Uint64() - ca.Number.Uint64(); reorged > 0 {
|
||||
fmt.Print(" reorged ", reorged)
|
||||
}
|
||||
if missed := currentHead.Number.Uint64() - ca.Number.Uint64() - 1; missed > 0 {
|
||||
fmt.Print(" missed ", missed)
|
||||
}
|
||||
fmt.Println()
|
||||
}
|
||||
lastHead = currentHead
|
||||
return currentHead, true
|
||||
}
|
||||
|
||||
tryExtendQuery := func(query *filterQuery) *filterQuery {
|
||||
for {
|
||||
extQuery := f.extendRange(query)
|
||||
if extQuery == nil {
|
||||
return query
|
||||
}
|
||||
extQuery.checkLastBlockHash(f.client)
|
||||
extQuery.run(f.client, nil)
|
||||
if extQuery.Err == nil && len(extQuery.results) == 0 {
|
||||
// query is useless now due to major reorg; abandon and continue
|
||||
fmt.Println("Zero length results")
|
||||
return nil
|
||||
}
|
||||
if extQuery.Err != nil {
|
||||
extQuery.printError()
|
||||
return nil
|
||||
}
|
||||
if len(extQuery.results) > maxFilterResultSize {
|
||||
return query
|
||||
}
|
||||
query = extQuery
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
mmQuery *filterQuery
|
||||
mmRetry, mmNextRetry int
|
||||
)
|
||||
|
||||
mainLoop:
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return nil
|
||||
default:
|
||||
}
|
||||
var query *filterQuery
|
||||
if mmQuery != nil {
|
||||
if mmRetry == 0 {
|
||||
query = mmQuery
|
||||
mmRetry = mmNextRetry
|
||||
mmNextRetry *= 2
|
||||
query.checkLastBlockHash(f.client)
|
||||
query.run(f.client, nil)
|
||||
if query.Err != nil {
|
||||
query.printError()
|
||||
continue
|
||||
}
|
||||
fmt.Println("Retrying query from:", query.FromBlock, "to:", query.ToBlock, "results:", len(query.results))
|
||||
} else {
|
||||
mmRetry--
|
||||
}
|
||||
}
|
||||
if query == nil {
|
||||
currentHead, isNewHead := fetchHead()
|
||||
if currentHead == nil {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return nil
|
||||
case <-time.After(time.Second):
|
||||
}
|
||||
continue mainLoop
|
||||
}
|
||||
if isNewHead {
|
||||
query = f.newHeadSeedQuery(currentHead.Number.Int64())
|
||||
} else {
|
||||
query = f.newQuery()
|
||||
}
|
||||
query.checkLastBlockHash(f.client)
|
||||
query.run(f.client, nil)
|
||||
if query.Err != nil {
|
||||
query.printError()
|
||||
continue
|
||||
}
|
||||
fmt.Println("New query from:", query.FromBlock, "to:", query.ToBlock, "results:", len(query.results))
|
||||
if len(query.results) == 0 || len(query.results) > maxFilterResultSize {
|
||||
continue mainLoop
|
||||
}
|
||||
if query = tryExtendQuery(query); query == nil {
|
||||
continue mainLoop
|
||||
}
|
||||
}
|
||||
if !query.checkLastBlockHash(f.client) {
|
||||
fmt.Println("Reorg during search")
|
||||
continue mainLoop
|
||||
}
|
||||
// now we have a new query; check results
|
||||
results, err := query.getResultsFromReceipts(f.client)
|
||||
if err != nil {
|
||||
fmt.Println("Could not fetch results from receipts", err)
|
||||
continue mainLoop
|
||||
}
|
||||
if !query.checkLastBlockHash(f.client) {
|
||||
fmt.Println("Reorg during search")
|
||||
continue mainLoop
|
||||
}
|
||||
if !reflect.DeepEqual(query.results, results) {
|
||||
fmt.Println("Results mismatch from:", query.FromBlock, "to:", query.ToBlock, "addresses:", query.Address, "topics:", query.Topics)
|
||||
resShared, resGetLogs, resReceipts := compareResults(query.results, results)
|
||||
fmt.Println(" shared:", len(resShared))
|
||||
fmt.Println(" only from getLogs:", len(resGetLogs), resGetLogs)
|
||||
fmt.Println(" only from receipts:", len(resReceipts), resReceipts)
|
||||
if mmQuery != query {
|
||||
mmQuery = query
|
||||
mmRetry = 0
|
||||
mmNextRetry = 1
|
||||
}
|
||||
continue mainLoop
|
||||
}
|
||||
fmt.Println("Successful query from:", query.FromBlock, "to:", query.ToBlock, "results:", len(query.results))
|
||||
f.storeQuery(query)
|
||||
}
|
||||
}
|
||||
|
||||
func compareResults(a, b []types.Log) (shared, onlya, onlyb []types.Log) {
|
||||
for len(a) > 0 && len(b) > 0 {
|
||||
if reflect.DeepEqual(a[0], b[0]) {
|
||||
shared = append(shared, a[0])
|
||||
a = a[1:]
|
||||
b = b[1:]
|
||||
} else {
|
||||
for i := 1; ; i++ {
|
||||
if i >= len(a) { // b[0] not found in a
|
||||
onlyb = append(onlyb, b[0])
|
||||
b = b[1:]
|
||||
break
|
||||
}
|
||||
if i >= len(b) { // a[0] not found in b
|
||||
onlya = append(onlya, a[0])
|
||||
a = a[1:]
|
||||
break
|
||||
}
|
||||
if reflect.DeepEqual(b[0], a[i]) { // a[:i] not found in b
|
||||
onlya = append(onlya, a[:i]...)
|
||||
a = a[i:]
|
||||
break
|
||||
}
|
||||
if reflect.DeepEqual(a[0], b[i]) { // b[:i] not found in a
|
||||
onlyb = append(onlyb, b[:i]...)
|
||||
b = b[i:]
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
onlya = append(onlya, a...)
|
||||
onlyb = append(onlyb, b...)
|
||||
return
|
||||
}
|
||||
|
||||
func getLatestHeader(client *client) (*types.Header, error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
|
||||
defer cancel()
|
||||
|
||||
return client.Eth.HeaderByNumber(ctx, big.NewInt(int64(rpc.LatestBlockNumber)))
|
||||
}
|
||||
|
||||
func getHeaderByHash(client *client, hash common.Hash) (*types.Header, error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
|
||||
defer cancel()
|
||||
|
||||
return client.Eth.HeaderByHash(ctx, hash)
|
||||
}
|
||||
|
||||
// newHeadSeedQuery creates a query that gets all logs from the latest head.
|
||||
func (s *filterTestGen) newHeadSeedQuery(head int64) *filterQuery {
|
||||
return &filterQuery{
|
||||
FromBlock: head,
|
||||
ToBlock: head,
|
||||
}
|
||||
}
|
||||
|
||||
func (fq *filterQuery) checkLastBlockHash(client *client) bool {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*30)
|
||||
defer cancel()
|
||||
|
||||
header, err := client.Eth.HeaderByNumber(ctx, big.NewInt(fq.ToBlock))
|
||||
if err != nil {
|
||||
fmt.Println("Cound not fetch last block hash of query number:", fq.ToBlock, "error:", err)
|
||||
fq.lastBlockHash = common.Hash{}
|
||||
return false
|
||||
}
|
||||
hash := header.Hash()
|
||||
if fq.lastBlockHash == hash {
|
||||
return true
|
||||
}
|
||||
fq.lastBlockHash = hash
|
||||
return false
|
||||
}
|
||||
|
||||
func (fq *filterQuery) filterLog(log *types.Log) bool {
|
||||
if len(fq.Address) > 0 && !slices.Contains(fq.Address, log.Address) {
|
||||
return false
|
||||
}
|
||||
// If the to filtered topics is greater than the amount of topics in logs, skip.
|
||||
if len(fq.Topics) > len(log.Topics) {
|
||||
return false
|
||||
}
|
||||
for i, sub := range fq.Topics {
|
||||
if len(sub) == 0 {
|
||||
continue // empty rule set == wildcard
|
||||
}
|
||||
if !slices.Contains(sub, log.Topics[i]) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (fq *filterQuery) getResultsFromReceipts(client *client) ([]types.Log, error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*30)
|
||||
defer cancel()
|
||||
|
||||
var results []types.Log
|
||||
for blockNumber := fq.FromBlock; blockNumber <= fq.ToBlock; blockNumber++ {
|
||||
receipts, err := client.Eth.BlockReceipts(ctx, rpc.BlockNumberOrHashWithNumber(rpc.BlockNumber(blockNumber)))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, receipt := range receipts {
|
||||
for _, log := range receipt.Logs {
|
||||
if fq.filterLog(log) {
|
||||
results = append(results, *log)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return results, nil
|
||||
}
|
||||
|
|
@ -32,6 +32,17 @@ import (
|
|||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
const (
|
||||
// Parameter of the random filter query generator.
|
||||
maxFilterRangeForTestGen = 100000000000
|
||||
maxFilterResultSize = 1000
|
||||
filterBuckets = 10
|
||||
maxFilterBucketSize = 100
|
||||
filterSeedChance = 10
|
||||
filterMergeChance = 45
|
||||
filterExtendChance = 50
|
||||
)
|
||||
|
||||
var (
|
||||
filterGenerateCommand = &cli.Command{
|
||||
Name: "filtergen",
|
||||
|
|
@ -58,7 +69,7 @@ var (
|
|||
|
||||
// filterGenCmd is the main function of the filter tests generator.
|
||||
func filterGenCmd(ctx *cli.Context) error {
|
||||
f := newFilterTestGen(ctx)
|
||||
f := newFilterTestGen(ctx, maxFilterRangeForTestGen)
|
||||
lastWrite := time.Now()
|
||||
for {
|
||||
select {
|
||||
|
|
@ -67,7 +78,7 @@ func filterGenCmd(ctx *cli.Context) error {
|
|||
default:
|
||||
}
|
||||
|
||||
f.updateFinalizedBlock()
|
||||
f.setLimitToFinalizedBlock()
|
||||
query := f.newQuery()
|
||||
query.run(f.client, nil)
|
||||
if query.Err != nil {
|
||||
|
|
@ -75,7 +86,7 @@ func filterGenCmd(ctx *cli.Context) error {
|
|||
exit("filter query failed")
|
||||
}
|
||||
if len(query.results) > 0 && len(query.results) <= maxFilterResultSize {
|
||||
for {
|
||||
for rand.Intn(100) < filterExtendChance {
|
||||
extQuery := f.extendRange(query)
|
||||
if extQuery == nil {
|
||||
break
|
||||
|
|
@ -108,39 +119,32 @@ func filterGenCmd(ctx *cli.Context) error {
|
|||
|
||||
// filterTestGen is the filter query test generator.
|
||||
type filterTestGen struct {
|
||||
client *client
|
||||
queryFile string
|
||||
client *client
|
||||
queryFile string
|
||||
maxFilterRange int64
|
||||
|
||||
finalizedBlock int64
|
||||
queries [filterBuckets][]*filterQuery
|
||||
blockLimit int64
|
||||
queries [filterBuckets][]*filterQuery
|
||||
}
|
||||
|
||||
func newFilterTestGen(ctx *cli.Context) *filterTestGen {
|
||||
func newFilterTestGen(ctx *cli.Context, maxFilterRange int64) *filterTestGen {
|
||||
return &filterTestGen{
|
||||
client: makeClient(ctx),
|
||||
queryFile: ctx.String(filterQueryFileFlag.Name),
|
||||
client: makeClient(ctx),
|
||||
queryFile: ctx.String(filterQueryFileFlag.Name),
|
||||
maxFilterRange: maxFilterRange,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *filterTestGen) updateFinalizedBlock() {
|
||||
s.finalizedBlock = mustGetFinalizedBlock(s.client)
|
||||
func (s *filterTestGen) setLimitToFinalizedBlock() {
|
||||
s.blockLimit = mustGetFinalizedBlock(s.client)
|
||||
}
|
||||
|
||||
const (
|
||||
// Parameter of the random filter query generator.
|
||||
maxFilterRange = 10000000
|
||||
maxFilterResultSize = 300
|
||||
filterBuckets = 10
|
||||
maxFilterBucketSize = 100
|
||||
filterSeedChance = 10
|
||||
filterMergeChance = 45
|
||||
)
|
||||
|
||||
// storeQuery adds a filter query to the output file.
|
||||
func (s *filterTestGen) storeQuery(query *filterQuery) {
|
||||
query.ResultHash = new(common.Hash)
|
||||
*query.ResultHash = query.calculateHash()
|
||||
logRatio := math.Log(float64(len(query.results))*float64(s.finalizedBlock)/float64(query.ToBlock+1-query.FromBlock)) / math.Log(float64(s.finalizedBlock)*maxFilterResultSize)
|
||||
maxFilterRange := min(s.maxFilterRange, s.blockLimit)
|
||||
logRatio := math.Log(float64(len(query.results))*float64(maxFilterRange)/float64(query.ToBlock+1-query.FromBlock)) / math.Log(float64(maxFilterRange)*maxFilterResultSize)
|
||||
bucket := int(math.Floor(logRatio * filterBuckets))
|
||||
if bucket >= filterBuckets {
|
||||
bucket = filterBuckets - 1
|
||||
|
|
@ -160,13 +164,13 @@ func (s *filterTestGen) storeQuery(query *filterQuery) {
|
|||
func (s *filterTestGen) extendRange(q *filterQuery) *filterQuery {
|
||||
rangeLen := q.ToBlock + 1 - q.FromBlock
|
||||
extLen := rand.Int63n(rangeLen) + 1
|
||||
if rangeLen+extLen > s.finalizedBlock {
|
||||
if rangeLen+extLen > min(s.maxFilterRange, s.blockLimit) {
|
||||
return nil
|
||||
}
|
||||
extBefore := min(rand.Int63n(extLen+1), q.FromBlock)
|
||||
extAfter := extLen - extBefore
|
||||
if q.ToBlock+extAfter > s.finalizedBlock {
|
||||
d := q.ToBlock + extAfter - s.finalizedBlock
|
||||
if q.ToBlock+extAfter > s.blockLimit {
|
||||
d := q.ToBlock + extAfter - s.blockLimit
|
||||
extAfter -= d
|
||||
if extBefore+d <= q.FromBlock {
|
||||
extBefore += d
|
||||
|
|
@ -203,7 +207,7 @@ func (s *filterTestGen) newQuery() *filterQuery {
|
|||
|
||||
// newSeedQuery creates a query that gets all logs in a random non-finalized block.
|
||||
func (s *filterTestGen) newSeedQuery() *filterQuery {
|
||||
block := rand.Int63n(s.finalizedBlock + 1)
|
||||
block := rand.Int63n(s.blockLimit + 1)
|
||||
return &filterQuery{
|
||||
FromBlock: block,
|
||||
ToBlock: block,
|
||||
|
|
@ -358,6 +362,7 @@ func (s *filterTestGen) writeQueries() {
|
|||
func mustGetFinalizedBlock(client *client) int64 {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
|
||||
defer cancel()
|
||||
|
||||
header, err := client.Eth.HeaderByNumber(ctx, big.NewInt(int64(rpc.FinalizedBlockNumber)))
|
||||
if err != nil {
|
||||
exit(fmt.Errorf("could not fetch finalized header (error: %v)", err))
|
||||
|
|
|
|||
|
|
@ -49,6 +49,7 @@ func init() {
|
|||
filterGenerateCommand,
|
||||
traceGenerateCommand,
|
||||
filterPerfCommand,
|
||||
filterFuzzCommand,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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,37 +183,30 @@ 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).
|
||||
func ReadStateStorageIndex(db ethdb.AncientReaderOp, id uint64) []byte {
|
||||
blob, err := db.Ancient(stateHistoryStorageIndex, id-1)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
return blob
|
||||
// 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, offset, length int) ([]byte, error) {
|
||||
return db.AncientBytes(stateHistoryStorageIndex, id-1, uint64(offset), uint64(length))
|
||||
}
|
||||
|
||||
// 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).
|
||||
func ReadStateAccountHistory(db ethdb.AncientReaderOp, id uint64) []byte {
|
||||
blob, err := db.Ancient(stateHistoryAccountData, id-1)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
return blob
|
||||
// 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, offset, length int) ([]byte, error) {
|
||||
return db.AncientBytes(stateHistoryAccountData, id-1, uint64(offset), uint64(length))
|
||||
}
|
||||
|
||||
// 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).
|
||||
func ReadStateStorageHistory(db ethdb.AncientReaderOp, id uint64) []byte {
|
||||
blob, err := db.Ancient(stateHistoryStorageData, id-1)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
return blob
|
||||
// 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, offset, length int) ([]byte, error) {
|
||||
return db.AncientBytes(stateHistoryStorageData, id-1, uint64(offset), uint64(length))
|
||||
}
|
||||
|
||||
// ReadStateHistory retrieves the state history from database with provided id.
|
||||
|
|
@ -292,3 +287,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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -403,6 +403,10 @@ func (f *chainFreezer) AncientRange(kind string, start, count, maxBytes uint64)
|
|||
return f.ancients.AncientRange(kind, start, count, maxBytes)
|
||||
}
|
||||
|
||||
func (f *chainFreezer) AncientBytes(kind string, id, offset, length uint64) ([]byte, error) {
|
||||
return f.ancients.AncientBytes(kind, id, offset, length)
|
||||
}
|
||||
|
||||
func (f *chainFreezer) ModifyAncients(fn func(ethdb.AncientWriteOp) error) (int64, error) {
|
||||
return f.ancients.ModifyAncients(fn)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -100,6 +100,12 @@ func (db *nofreezedb) AncientRange(kind string, start, max, maxByteSize uint64)
|
|||
return nil, errNotSupported
|
||||
}
|
||||
|
||||
// AncientBytes retrieves the value segment of the element specified by the id
|
||||
// and value offsets.
|
||||
func (db *nofreezedb) AncientBytes(kind string, id, offset, length uint64) ([]byte, error) {
|
||||
return nil, errNotSupported
|
||||
}
|
||||
|
||||
// Ancients returns an error as we don't have a backing chain freezer.
|
||||
func (db *nofreezedb) Ancients() (uint64, error) {
|
||||
return 0, errNotSupported
|
||||
|
|
|
|||
|
|
@ -76,8 +76,9 @@ type Freezer struct {
|
|||
// NewFreezer creates a freezer instance for maintaining immutable ordered
|
||||
// data according to the given parameters.
|
||||
//
|
||||
// The 'tables' argument defines the data tables. If the value of a map
|
||||
// entry is true, snappy compression is disabled for the table.
|
||||
// The 'tables' argument defines the freezer tables and their configuration.
|
||||
// Each value is a freezerTableConfig specifying whether snappy compression is
|
||||
// disabled (noSnappy) and whether the table is prunable (prunable).
|
||||
func NewFreezer(datadir string, namespace string, readonly bool, maxTableSize uint32, tables map[string]freezerTableConfig) (*Freezer, error) {
|
||||
// Create the initial freezer object
|
||||
var (
|
||||
|
|
@ -201,6 +202,15 @@ func (f *Freezer) AncientRange(kind string, start, count, maxBytes uint64) ([][]
|
|||
return nil, errUnknownTable
|
||||
}
|
||||
|
||||
// AncientBytes retrieves the value segment of the element specified by the id
|
||||
// and value offsets.
|
||||
func (f *Freezer) AncientBytes(kind string, id, offset, length uint64) ([]byte, error) {
|
||||
if table := f.tables[kind]; table != nil {
|
||||
return table.RetrieveBytes(id, offset, length)
|
||||
}
|
||||
return nil, errUnknownTable
|
||||
}
|
||||
|
||||
// Ancients returns the length of the frozen items.
|
||||
func (f *Freezer) Ancients() (uint64, error) {
|
||||
return f.frozen.Load(), nil
|
||||
|
|
|
|||
|
|
@ -412,3 +412,28 @@ func (f *MemoryFreezer) Reset() error {
|
|||
func (f *MemoryFreezer) AncientDatadir() (string, error) {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
// AncientBytes retrieves the value segment of the element specified by the id
|
||||
// and value offsets.
|
||||
func (f *MemoryFreezer) AncientBytes(kind string, id, offset, length uint64) ([]byte, error) {
|
||||
f.lock.RLock()
|
||||
defer f.lock.RUnlock()
|
||||
|
||||
table := f.tables[kind]
|
||||
if table == nil {
|
||||
return nil, errUnknownTable
|
||||
}
|
||||
entries, err := table.retrieve(id, 1, 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(entries) == 0 {
|
||||
return nil, errOutOfBounds
|
||||
}
|
||||
data := entries[0]
|
||||
|
||||
if offset > uint64(len(data)) || offset+length > uint64(len(data)) {
|
||||
return nil, fmt.Errorf("requested range out of bounds: item size %d, offset %d, length %d", len(data), offset, length)
|
||||
}
|
||||
return data[offset : offset+length], nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -126,6 +126,15 @@ func (f *resettableFreezer) AncientRange(kind string, start, count, maxBytes uin
|
|||
return f.freezer.AncientRange(kind, start, count, maxBytes)
|
||||
}
|
||||
|
||||
// AncientBytes retrieves the value segment of the element specified by the id
|
||||
// and value offsets.
|
||||
func (f *resettableFreezer) AncientBytes(kind string, id, offset, length uint64) ([]byte, error) {
|
||||
f.lock.RLock()
|
||||
defer f.lock.RUnlock()
|
||||
|
||||
return f.freezer.AncientBytes(kind, id, offset, length)
|
||||
}
|
||||
|
||||
// Ancients returns the length of the frozen items.
|
||||
func (f *resettableFreezer) Ancients() (uint64, error) {
|
||||
f.lock.RLock()
|
||||
|
|
|
|||
|
|
@ -100,7 +100,7 @@ type freezerTable struct {
|
|||
// should never be lower than itemOffset.
|
||||
itemHidden atomic.Uint64
|
||||
|
||||
config freezerTableConfig // if true, disables snappy compression. Note: does not work retroactively
|
||||
config freezerTableConfig // table configuration (compression, prunability). Note: compression flag does not apply retroactively to existing files
|
||||
readonly bool
|
||||
maxFileSize uint32 // Max file size for data-files
|
||||
name string
|
||||
|
|
@ -1107,6 +1107,71 @@ func (t *freezerTable) retrieveItems(start, count, maxBytes uint64) ([]byte, []i
|
|||
return output, sizes, nil
|
||||
}
|
||||
|
||||
// RetrieveBytes retrieves the value segment of the element specified by the id
|
||||
// and value offsets.
|
||||
func (t *freezerTable) RetrieveBytes(item, offset, length uint64) ([]byte, error) {
|
||||
t.lock.RLock()
|
||||
defer t.lock.RUnlock()
|
||||
|
||||
if t.index == nil || t.head == nil || t.metadata.file == nil {
|
||||
return nil, errClosed
|
||||
}
|
||||
items, hidden := t.items.Load(), t.itemHidden.Load()
|
||||
if items <= item || hidden > item {
|
||||
return nil, errOutOfBounds
|
||||
}
|
||||
|
||||
// Retrieves the index entries for the specified ID and its immediate successor
|
||||
indices, err := t.getIndices(item, 1)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
index0, index1 := indices[0], indices[1]
|
||||
|
||||
itemStart, itemLimit, fileId := index0.bounds(index1)
|
||||
itemSize := itemLimit - itemStart
|
||||
|
||||
dataFile, exist := t.files[fileId]
|
||||
if !exist {
|
||||
return nil, fmt.Errorf("missing data file %d", fileId)
|
||||
}
|
||||
|
||||
// Perform the partial read if no-compression was enabled upon
|
||||
if t.config.noSnappy {
|
||||
if offset > uint64(itemSize) || offset+length > uint64(itemSize) {
|
||||
return nil, fmt.Errorf("requested range out of bounds: item size %d, offset %d, length %d", itemSize, offset, length)
|
||||
}
|
||||
itemStart += uint32(offset)
|
||||
|
||||
buf := make([]byte, length)
|
||||
_, err = dataFile.ReadAt(buf, int64(itemStart))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
t.readMeter.Mark(int64(length))
|
||||
return buf, nil
|
||||
} else {
|
||||
// If compressed, read the full item, decompress, then slice.
|
||||
// Unfortunately, in this case, there is no performance gain
|
||||
// by performing the partial read at all.
|
||||
buf := make([]byte, itemSize)
|
||||
_, err = dataFile.ReadAt(buf, int64(itemStart))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
t.readMeter.Mark(int64(itemSize))
|
||||
|
||||
data, err := snappy.Decode(nil, buf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if offset > uint64(len(data)) || offset+length > uint64(len(data)) {
|
||||
return nil, fmt.Errorf("requested range out of bounds: item size %d, offset %d, length %d", len(data), offset, length)
|
||||
}
|
||||
return data[offset : offset+length], nil
|
||||
}
|
||||
}
|
||||
|
||||
// size returns the total data size in the freezer table.
|
||||
func (t *freezerTable) size() (uint64, error) {
|
||||
t.lock.RLock()
|
||||
|
|
|
|||
|
|
@ -1571,3 +1571,65 @@ func TestTailTruncationCrash(t *testing.T) {
|
|||
t.Fatalf("Unexpected index flush offset, want: %d, got: %d", 26*indexEntrySize, f.metadata.flushOffset)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFreezerAncientBytes(t *testing.T) {
|
||||
t.Parallel()
|
||||
types := []struct {
|
||||
name string
|
||||
config freezerTableConfig
|
||||
}{
|
||||
{"uncompressed", freezerTableConfig{noSnappy: true}},
|
||||
{"compressed", freezerTableConfig{noSnappy: false}},
|
||||
}
|
||||
for _, typ := range types {
|
||||
t.Run(typ.name, func(t *testing.T) {
|
||||
f, err := newTable(os.TempDir(), fmt.Sprintf("ancientbytes-%s-%d", typ.name, rand.Uint64()), metrics.NewMeter(), metrics.NewMeter(), metrics.NewGauge(), 1000, typ.config, false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
for i := 0; i < 10; i++ {
|
||||
data := getChunk(100, i)
|
||||
batch := f.newBatch()
|
||||
require.NoError(t, batch.AppendRaw(uint64(i), data))
|
||||
require.NoError(t, batch.commit())
|
||||
}
|
||||
|
||||
for i := 0; i < 10; i++ {
|
||||
full, err := f.Retrieve(uint64(i))
|
||||
require.NoError(t, err)
|
||||
|
||||
// Full read
|
||||
got, err := f.RetrieveBytes(uint64(i), 0, uint64(len(full)))
|
||||
require.NoError(t, err)
|
||||
if !bytes.Equal(got, full) {
|
||||
t.Fatalf("full read mismatch for entry %d", i)
|
||||
}
|
||||
// Empty read
|
||||
got, err = f.RetrieveBytes(uint64(i), 0, 0)
|
||||
require.NoError(t, err)
|
||||
if !bytes.Equal(got, full[:0]) {
|
||||
t.Fatalf("empty read mismatch for entry %d", i)
|
||||
}
|
||||
// Middle slice
|
||||
got, err = f.RetrieveBytes(uint64(i), 10, 50)
|
||||
require.NoError(t, err)
|
||||
if !bytes.Equal(got, full[10:60]) {
|
||||
t.Fatalf("middle slice mismatch for entry %d", i)
|
||||
}
|
||||
// Single byte
|
||||
got, err = f.RetrieveBytes(uint64(i), 99, 1)
|
||||
require.NoError(t, err)
|
||||
if !bytes.Equal(got, full[99:100]) {
|
||||
t.Fatalf("single byte mismatch for entry %d", i)
|
||||
}
|
||||
// Out of bounds
|
||||
_, err = f.RetrieveBytes(uint64(i), 100, 1)
|
||||
if err == nil {
|
||||
t.Fatalf("expected error for out-of-bounds read for entry %d", i)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -62,6 +62,12 @@ func (t *table) AncientRange(kind string, start, count, maxBytes uint64) ([][]by
|
|||
return t.db.AncientRange(kind, start, count, maxBytes)
|
||||
}
|
||||
|
||||
// AncientBytes is a noop passthrough that just forwards the request to the underlying
|
||||
// database.
|
||||
func (t *table) AncientBytes(kind string, id, offset, length uint64) ([]byte, error) {
|
||||
return t.db.AncientBytes(kind, id, offset, length)
|
||||
}
|
||||
|
||||
// Ancients is a noop passthrough that just forwards the request to the underlying
|
||||
// database.
|
||||
func (t *table) Ancients() (uint64, error) {
|
||||
|
|
|
|||
|
|
@ -131,7 +131,7 @@ func TestMessageCallGas(t *testing.T) {
|
|||
}
|
||||
gas = ae.CodeHashGas(testAddr, false, math.MaxUint64, false)
|
||||
if gas != params.WitnessChunkReadCost {
|
||||
t.Fatalf("incorrect gas computed, got %d, want %d", gas, 0)
|
||||
t.Fatalf("incorrect gas computed, got %d, want %d", gas, params.WitnessChunkReadCost)
|
||||
}
|
||||
|
||||
// Check warm read cost
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -23,7 +23,6 @@ import (
|
|||
"math"
|
||||
"math/big"
|
||||
"slices"
|
||||
"sort"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
|
@ -238,11 +237,10 @@ type LegacyPool struct {
|
|||
pendingNonces *noncer // Pending state tracking virtual nonces
|
||||
reserver txpool.Reserver // Address reserver to ensure exclusivity across subpools
|
||||
|
||||
pending map[common.Address]*list // All currently processable transactions
|
||||
queue map[common.Address]*list // Queued but non-processable transactions
|
||||
beats map[common.Address]time.Time // Last heartbeat from each known account
|
||||
all *lookup // All transactions to allow lookups
|
||||
priced *pricedList // All transactions sorted by price
|
||||
pending map[common.Address]*list // All currently processable transactions
|
||||
queue *queue
|
||||
all *lookup // All transactions to allow lookups
|
||||
priced *pricedList // All transactions sorted by price
|
||||
|
||||
reqResetCh chan *txpoolResetRequest
|
||||
reqPromoteCh chan *accountSet
|
||||
|
|
@ -266,14 +264,14 @@ func New(config Config, chain BlockChain) *LegacyPool {
|
|||
config = (&config).sanitize()
|
||||
|
||||
// Create the transaction pool with its initial settings
|
||||
signer := types.LatestSigner(chain.Config())
|
||||
pool := &LegacyPool{
|
||||
config: config,
|
||||
chain: chain,
|
||||
chainconfig: chain.Config(),
|
||||
signer: types.LatestSigner(chain.Config()),
|
||||
signer: signer,
|
||||
pending: make(map[common.Address]*list),
|
||||
queue: make(map[common.Address]*list),
|
||||
beats: make(map[common.Address]time.Time),
|
||||
queue: newQueue(config, signer),
|
||||
all: newLookup(),
|
||||
reqResetCh: make(chan *txpoolResetRequest),
|
||||
reqPromoteCh: make(chan *accountSet),
|
||||
|
|
@ -369,15 +367,8 @@ func (pool *LegacyPool) loop() {
|
|||
// Handle inactive account transaction eviction
|
||||
case <-evict.C:
|
||||
pool.mu.Lock()
|
||||
for addr := range pool.queue {
|
||||
// Any old enough should be removed
|
||||
if time.Since(pool.beats[addr]) > pool.config.Lifetime {
|
||||
list := pool.queue[addr].Flatten()
|
||||
for _, tx := range list {
|
||||
pool.removeTx(tx.Hash(), true, true)
|
||||
}
|
||||
queuedEvictionMeter.Mark(int64(len(list)))
|
||||
}
|
||||
for _, hash := range pool.queue.evictList() {
|
||||
pool.removeTx(hash, true, true)
|
||||
}
|
||||
pool.mu.Unlock()
|
||||
}
|
||||
|
|
@ -459,11 +450,7 @@ func (pool *LegacyPool) stats() (int, int) {
|
|||
for _, list := range pool.pending {
|
||||
pending += list.Len()
|
||||
}
|
||||
queued := 0
|
||||
for _, list := range pool.queue {
|
||||
queued += list.Len()
|
||||
}
|
||||
return pending, queued
|
||||
return pending, pool.queue.stats()
|
||||
}
|
||||
|
||||
// Content retrieves the data content of the transaction pool, returning all the
|
||||
|
|
@ -476,10 +463,7 @@ func (pool *LegacyPool) Content() (map[common.Address][]*types.Transaction, map[
|
|||
for addr, list := range pool.pending {
|
||||
pending[addr] = list.Flatten()
|
||||
}
|
||||
queued := make(map[common.Address][]*types.Transaction, len(pool.queue))
|
||||
for addr, list := range pool.queue {
|
||||
queued[addr] = list.Flatten()
|
||||
}
|
||||
queued := pool.queue.content()
|
||||
return pending, queued
|
||||
}
|
||||
|
||||
|
|
@ -493,10 +477,7 @@ func (pool *LegacyPool) ContentFrom(addr common.Address) ([]*types.Transaction,
|
|||
if list, ok := pool.pending[addr]; ok {
|
||||
pending = list.Flatten()
|
||||
}
|
||||
var queued []*types.Transaction
|
||||
if list, ok := pool.queue[addr]; ok {
|
||||
queued = list.Flatten()
|
||||
}
|
||||
queued := pool.queue.contentFrom(addr)
|
||||
return pending, queued
|
||||
}
|
||||
|
||||
|
|
@ -644,7 +625,7 @@ func (pool *LegacyPool) validateAuth(tx *types.Transaction) error {
|
|||
if pending := pool.pending[auth]; pending != nil {
|
||||
count += pending.Len()
|
||||
}
|
||||
if queue := pool.queue[auth]; queue != nil {
|
||||
if queue, ok := pool.queue.get(auth); ok {
|
||||
count += queue.Len()
|
||||
}
|
||||
if count > 1 {
|
||||
|
|
@ -691,7 +672,7 @@ func (pool *LegacyPool) add(tx *types.Transaction) (replaced bool, err error) {
|
|||
// only by this subpool until all transactions are evicted
|
||||
var (
|
||||
_, hasPending = pool.pending[from]
|
||||
_, hasQueued = pool.queue[from]
|
||||
_, hasQueued = pool.queue.get(from)
|
||||
)
|
||||
if !hasPending && !hasQueued {
|
||||
if err := pool.reserver.Hold(from); err != nil {
|
||||
|
|
@ -790,7 +771,7 @@ func (pool *LegacyPool) add(tx *types.Transaction) (replaced bool, err error) {
|
|||
log.Trace("Pooled new executable transaction", "hash", hash, "from", from, "to", tx.To())
|
||||
|
||||
// Successful promotion, bump the heartbeat
|
||||
pool.beats[from] = time.Now()
|
||||
pool.queue.bump(from)
|
||||
return old != nil, nil
|
||||
}
|
||||
// New transaction isn't replacing a pending one, push into queue
|
||||
|
|
@ -815,7 +796,7 @@ func (pool *LegacyPool) isGapped(from common.Address, tx *types.Transaction) boo
|
|||
}
|
||||
// The transaction has a nonce gap with pending list, it's only considered
|
||||
// as executable if transactions in queue can fill up the nonce gap.
|
||||
queue, ok := pool.queue[from]
|
||||
queue, ok := pool.queue.get(from)
|
||||
if !ok {
|
||||
return true
|
||||
}
|
||||
|
|
@ -831,25 +812,12 @@ func (pool *LegacyPool) isGapped(from common.Address, tx *types.Transaction) boo
|
|||
//
|
||||
// Note, this method assumes the pool lock is held!
|
||||
func (pool *LegacyPool) enqueueTx(hash common.Hash, tx *types.Transaction, addAll bool) (bool, error) {
|
||||
// Try to insert the transaction into the future queue
|
||||
from, _ := types.Sender(pool.signer, tx) // already validated
|
||||
if pool.queue[from] == nil {
|
||||
pool.queue[from] = newList(false)
|
||||
replaced, err := pool.queue.add(tx)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
inserted, old := pool.queue[from].Add(tx, pool.config.PriceBump)
|
||||
if !inserted {
|
||||
// An older transaction was better, discard this
|
||||
queuedDiscardMeter.Mark(1)
|
||||
return false, txpool.ErrReplaceUnderpriced
|
||||
}
|
||||
// Discard any previous transaction and mark this
|
||||
if old != nil {
|
||||
pool.all.Remove(old.Hash())
|
||||
pool.priced.Removed(1)
|
||||
queuedReplaceMeter.Mark(1)
|
||||
} else {
|
||||
// Nothing was replaced, bump the queued counter
|
||||
queuedGauge.Inc(1)
|
||||
if replaced != nil {
|
||||
pool.removeTx(*replaced, true, true)
|
||||
}
|
||||
// If the transaction isn't in lookup set but it's expected to be there,
|
||||
// show the error log.
|
||||
|
|
@ -860,11 +828,7 @@ func (pool *LegacyPool) enqueueTx(hash common.Hash, tx *types.Transaction, addAl
|
|||
pool.all.Add(tx)
|
||||
pool.priced.Put(tx)
|
||||
}
|
||||
// If we never record the heartbeat, do it right now.
|
||||
if _, exist := pool.beats[from]; !exist {
|
||||
pool.beats[from] = time.Now()
|
||||
}
|
||||
return old != nil, nil
|
||||
return replaced != nil, nil
|
||||
}
|
||||
|
||||
// promoteTx adds a transaction to the pending (processable) list of transactions
|
||||
|
|
@ -899,7 +863,7 @@ func (pool *LegacyPool) promoteTx(addr common.Address, hash common.Hash, tx *typ
|
|||
pool.pendingNonces.set(addr, tx.Nonce()+1)
|
||||
|
||||
// Successful promotion, bump the heartbeat
|
||||
pool.beats[addr] = time.Now()
|
||||
pool.queue.bump(addr)
|
||||
return true
|
||||
}
|
||||
|
||||
|
|
@ -984,17 +948,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
|
||||
}
|
||||
|
||||
|
|
@ -1012,7 +983,7 @@ func (pool *LegacyPool) Status(hash common.Hash) txpool.TxStatus {
|
|||
|
||||
if txList := pool.pending[from]; txList != nil && txList.txs.items[tx.Nonce()] != nil {
|
||||
return txpool.TxStatusPending
|
||||
} else if txList := pool.queue[from]; txList != nil && txList.txs.items[tx.Nonce()] != nil {
|
||||
} else if txList, ok := pool.queue.get(from); ok && txList.txs.items[tx.Nonce()] != nil {
|
||||
return txpool.TxStatusQueued
|
||||
}
|
||||
return txpool.TxStatusUnknown
|
||||
|
|
@ -1089,7 +1060,7 @@ func (pool *LegacyPool) removeTx(hash common.Hash, outofbound bool, unreserve bo
|
|||
defer func() {
|
||||
var (
|
||||
_, hasPending = pool.pending[addr]
|
||||
_, hasQueued = pool.queue[addr]
|
||||
_, hasQueued = pool.queue.get(addr)
|
||||
)
|
||||
if !hasPending && !hasQueued {
|
||||
pool.reserver.Release(addr)
|
||||
|
|
@ -1121,16 +1092,7 @@ func (pool *LegacyPool) removeTx(hash common.Hash, outofbound bool, unreserve bo
|
|||
}
|
||||
}
|
||||
// Transaction is in the future queue
|
||||
if future := pool.queue[addr]; future != nil {
|
||||
if removed, _ := future.Remove(tx); removed {
|
||||
// Reduce the queued counter
|
||||
queuedGauge.Dec(1)
|
||||
}
|
||||
if future.Empty() {
|
||||
delete(pool.queue, addr)
|
||||
delete(pool.beats, addr)
|
||||
}
|
||||
}
|
||||
pool.queue.remove(addr, tx)
|
||||
return 0
|
||||
}
|
||||
|
||||
|
|
@ -1278,10 +1240,7 @@ func (pool *LegacyPool) runReorg(done chan struct{}, reset *txpoolResetRequest,
|
|||
}
|
||||
}
|
||||
// Reset needs promote for all addresses
|
||||
promoteAddrs = make([]common.Address, 0, len(pool.queue))
|
||||
for addr := range pool.queue {
|
||||
promoteAddrs = append(promoteAddrs, addr)
|
||||
}
|
||||
promoteAddrs = pool.queue.addresses()
|
||||
}
|
||||
// Check for pending transactions for every account that sent new ones
|
||||
promoted := pool.promoteExecutables(promoteAddrs)
|
||||
|
|
@ -1435,60 +1394,29 @@ func (pool *LegacyPool) reset(oldHead, newHead *types.Header) {
|
|||
// future queue to the set of pending transactions. During this process, all
|
||||
// invalidated transactions (low nonce, low balance) are deleted.
|
||||
func (pool *LegacyPool) promoteExecutables(accounts []common.Address) []*types.Transaction {
|
||||
// Track the promoted transactions to broadcast them at once
|
||||
var promoted []*types.Transaction
|
||||
|
||||
// Iterate over all accounts and promote any executable transactions
|
||||
gasLimit := pool.currentHead.Load().GasLimit
|
||||
for _, addr := range accounts {
|
||||
list := pool.queue[addr]
|
||||
if list == nil {
|
||||
continue // Just in case someone calls with a non existing account
|
||||
}
|
||||
// Drop all transactions that are deemed too old (low nonce)
|
||||
forwards := list.Forward(pool.currentState.GetNonce(addr))
|
||||
for _, tx := range forwards {
|
||||
pool.all.Remove(tx.Hash())
|
||||
}
|
||||
log.Trace("Removed old queued transactions", "count", len(forwards))
|
||||
// Drop all transactions that are too costly (low balance or out of gas)
|
||||
drops, _ := list.Filter(pool.currentState.GetBalance(addr), gasLimit)
|
||||
for _, tx := range drops {
|
||||
pool.all.Remove(tx.Hash())
|
||||
}
|
||||
log.Trace("Removed unpayable queued transactions", "count", len(drops))
|
||||
queuedNofundsMeter.Mark(int64(len(drops)))
|
||||
promotable, dropped, removedAddresses := pool.queue.promoteExecutables(accounts, gasLimit, pool.currentState, pool.pendingNonces)
|
||||
|
||||
// Gather all executable transactions and promote them
|
||||
readies := list.Ready(pool.pendingNonces.get(addr))
|
||||
for _, tx := range readies {
|
||||
hash := tx.Hash()
|
||||
if pool.promoteTx(addr, hash, tx) {
|
||||
promoted = append(promoted, tx)
|
||||
}
|
||||
// promote all promotable transactions
|
||||
promoted := make([]*types.Transaction, 0, len(promotable))
|
||||
for _, tx := range promotable {
|
||||
from, _ := pool.signer.Sender(tx)
|
||||
if pool.promoteTx(from, tx.Hash(), tx) {
|
||||
promoted = append(promoted, tx)
|
||||
}
|
||||
log.Trace("Promoted queued transactions", "count", len(promoted))
|
||||
queuedGauge.Dec(int64(len(readies)))
|
||||
}
|
||||
|
||||
// Drop all transactions over the allowed limit
|
||||
var caps = list.Cap(int(pool.config.AccountQueue))
|
||||
for _, tx := range caps {
|
||||
hash := tx.Hash()
|
||||
pool.all.Remove(hash)
|
||||
log.Trace("Removed cap-exceeding queued transaction", "hash", hash)
|
||||
}
|
||||
queuedRateLimitMeter.Mark(int64(len(caps)))
|
||||
// Mark all the items dropped as removed
|
||||
pool.priced.Removed(len(forwards) + len(drops) + len(caps))
|
||||
queuedGauge.Dec(int64(len(forwards) + len(drops) + len(caps)))
|
||||
// remove all removable transactions
|
||||
for _, hash := range dropped {
|
||||
pool.all.Remove(hash)
|
||||
}
|
||||
pool.priced.Removed(len(dropped))
|
||||
|
||||
// Delete the entire queue entry if it became empty.
|
||||
if list.Empty() {
|
||||
delete(pool.queue, addr)
|
||||
delete(pool.beats, addr)
|
||||
if _, ok := pool.pending[addr]; !ok {
|
||||
pool.reserver.Release(addr)
|
||||
}
|
||||
// release all accounts that have no more transactions in the pool
|
||||
for _, addr := range removedAddresses {
|
||||
_, hasPending := pool.pending[addr]
|
||||
if !hasPending {
|
||||
pool.reserver.Release(addr)
|
||||
}
|
||||
}
|
||||
return promoted
|
||||
|
|
@ -1578,43 +1506,18 @@ func (pool *LegacyPool) truncatePending() {
|
|||
|
||||
// truncateQueue drops the oldest transactions in the queue if the pool is above the global queue limit.
|
||||
func (pool *LegacyPool) truncateQueue() {
|
||||
queued := uint64(0)
|
||||
for _, list := range pool.queue {
|
||||
queued += uint64(list.Len())
|
||||
}
|
||||
if queued <= pool.config.GlobalQueue {
|
||||
return
|
||||
removed, removedAddresses := pool.queue.truncate()
|
||||
|
||||
// Remove all removable transactions from the lookup and global price list
|
||||
for _, hash := range removed {
|
||||
pool.all.Remove(hash)
|
||||
}
|
||||
pool.priced.Removed(len(removed))
|
||||
|
||||
// Sort all accounts with queued transactions by heartbeat
|
||||
addresses := make(addressesByHeartbeat, 0, len(pool.queue))
|
||||
for addr := range pool.queue {
|
||||
addresses = append(addresses, addressByHeartbeat{addr, pool.beats[addr]})
|
||||
}
|
||||
sort.Sort(sort.Reverse(addresses))
|
||||
|
||||
// Drop transactions until the total is below the limit
|
||||
for drop := queued - pool.config.GlobalQueue; drop > 0 && len(addresses) > 0; {
|
||||
addr := addresses[len(addresses)-1]
|
||||
list := pool.queue[addr.address]
|
||||
|
||||
addresses = addresses[:len(addresses)-1]
|
||||
|
||||
// Drop all transactions if they are less than the overflow
|
||||
if size := uint64(list.Len()); size <= drop {
|
||||
for _, tx := range list.Flatten() {
|
||||
pool.removeTx(tx.Hash(), true, true)
|
||||
}
|
||||
drop -= size
|
||||
queuedRateLimitMeter.Mark(int64(size))
|
||||
continue
|
||||
}
|
||||
// Otherwise drop only last few transactions
|
||||
txs := list.Flatten()
|
||||
for i := len(txs) - 1; i >= 0 && drop > 0; i-- {
|
||||
pool.removeTx(txs[i].Hash(), true, true)
|
||||
drop--
|
||||
queuedRateLimitMeter.Mark(1)
|
||||
for _, addr := range removedAddresses {
|
||||
_, hasPending := pool.pending[addr]
|
||||
if !hasPending {
|
||||
pool.reserver.Release(addr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1672,25 +1575,13 @@ func (pool *LegacyPool) demoteUnexecutables() {
|
|||
// Delete the entire pending entry if it became empty.
|
||||
if list.Empty() {
|
||||
delete(pool.pending, addr)
|
||||
if _, ok := pool.queue[addr]; !ok {
|
||||
if _, ok := pool.queue.get(addr); !ok {
|
||||
pool.reserver.Release(addr)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// addressByHeartbeat is an account address tagged with its last activity timestamp.
|
||||
type addressByHeartbeat struct {
|
||||
address common.Address
|
||||
heartbeat time.Time
|
||||
}
|
||||
|
||||
type addressesByHeartbeat []addressByHeartbeat
|
||||
|
||||
func (a addressesByHeartbeat) Len() int { return len(a) }
|
||||
func (a addressesByHeartbeat) Less(i, j int) bool { return a[i].heartbeat.Before(a[j].heartbeat) }
|
||||
func (a addressesByHeartbeat) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||
|
||||
// accountSet is simply a set of addresses to check for existence, and a signer
|
||||
// capable of deriving addresses from transactions.
|
||||
type accountSet struct {
|
||||
|
|
@ -1931,17 +1822,17 @@ func (pool *LegacyPool) Clear() {
|
|||
// acquire the subpool lock until the transaction addition is completed.
|
||||
|
||||
for addr := range pool.pending {
|
||||
if _, ok := pool.queue[addr]; !ok {
|
||||
if _, ok := pool.queue.get(addr); !ok {
|
||||
pool.reserver.Release(addr)
|
||||
}
|
||||
}
|
||||
for addr := range pool.queue {
|
||||
for _, addr := range pool.queue.addresses() {
|
||||
pool.reserver.Release(addr)
|
||||
}
|
||||
pool.all.Clear()
|
||||
pool.priced.Reheap()
|
||||
pool.pending = make(map[common.Address]*list)
|
||||
pool.queue = make(map[common.Address]*list)
|
||||
pool.queue = newQueue(pool.config, pool.signer)
|
||||
pool.pendingNonces = newNoncer(pool.currentState)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -466,8 +466,8 @@ func TestQueue(t *testing.T) {
|
|||
if _, ok := pool.pending[from].txs.items[tx.Nonce()]; ok {
|
||||
t.Error("expected transaction to be in tx pool")
|
||||
}
|
||||
if len(pool.queue) > 0 {
|
||||
t.Error("expected transaction queue to be empty. is", len(pool.queue))
|
||||
if len(pool.queue.queued) > 0 {
|
||||
t.Error("expected transaction queue to be empty. is", len(pool.queue.queued))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -492,8 +492,8 @@ func TestQueue2(t *testing.T) {
|
|||
if len(pool.pending) != 1 {
|
||||
t.Error("expected pending length to be 1, got", len(pool.pending))
|
||||
}
|
||||
if pool.queue[from].Len() != 2 {
|
||||
t.Error("expected len(queue) == 2, got", pool.queue[from].Len())
|
||||
if list, _ := pool.queue.get(from); list.Len() != 2 {
|
||||
t.Error("expected len(queue) == 2, got", list.Len())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -639,8 +639,8 @@ func TestMissingNonce(t *testing.T) {
|
|||
if len(pool.pending) != 0 {
|
||||
t.Error("expected 0 pending transactions, got", len(pool.pending))
|
||||
}
|
||||
if pool.queue[addr].Len() != 1 {
|
||||
t.Error("expected 1 queued transaction, got", pool.queue[addr].Len())
|
||||
if list, _ := pool.queue.get(addr); list.Len() != 1 {
|
||||
t.Error("expected 1 queued transaction, got", list.Len())
|
||||
}
|
||||
if pool.all.Count() != 1 {
|
||||
t.Error("expected 1 total transactions, got", pool.all.Count())
|
||||
|
|
@ -712,8 +712,8 @@ func TestDropping(t *testing.T) {
|
|||
if pool.pending[account].Len() != 3 {
|
||||
t.Errorf("pending transaction mismatch: have %d, want %d", pool.pending[account].Len(), 3)
|
||||
}
|
||||
if pool.queue[account].Len() != 3 {
|
||||
t.Errorf("queued transaction mismatch: have %d, want %d", pool.queue[account].Len(), 3)
|
||||
if list, _ := pool.queue.get(account); list.Len() != 3 {
|
||||
t.Errorf("queued transaction mismatch: have %d, want %d", list.Len(), 3)
|
||||
}
|
||||
if pool.all.Count() != 6 {
|
||||
t.Errorf("total transaction mismatch: have %d, want %d", pool.all.Count(), 6)
|
||||
|
|
@ -722,8 +722,8 @@ func TestDropping(t *testing.T) {
|
|||
if pool.pending[account].Len() != 3 {
|
||||
t.Errorf("pending transaction mismatch: have %d, want %d", pool.pending[account].Len(), 3)
|
||||
}
|
||||
if pool.queue[account].Len() != 3 {
|
||||
t.Errorf("queued transaction mismatch: have %d, want %d", pool.queue[account].Len(), 3)
|
||||
if list, _ := pool.queue.get(account); list.Len() != 3 {
|
||||
t.Errorf("queued transaction mismatch: have %d, want %d", list.Len(), 3)
|
||||
}
|
||||
if pool.all.Count() != 6 {
|
||||
t.Errorf("total transaction mismatch: have %d, want %d", pool.all.Count(), 6)
|
||||
|
|
@ -741,13 +741,14 @@ func TestDropping(t *testing.T) {
|
|||
if _, ok := pool.pending[account].txs.items[tx2.Nonce()]; ok {
|
||||
t.Errorf("out-of-fund pending transaction present: %v", tx1)
|
||||
}
|
||||
if _, ok := pool.queue[account].txs.items[tx10.Nonce()]; !ok {
|
||||
list, _ := pool.queue.get(account)
|
||||
if _, ok := list.txs.items[tx10.Nonce()]; !ok {
|
||||
t.Errorf("funded queued transaction missing: %v", tx10)
|
||||
}
|
||||
if _, ok := pool.queue[account].txs.items[tx11.Nonce()]; !ok {
|
||||
if _, ok := list.txs.items[tx11.Nonce()]; !ok {
|
||||
t.Errorf("funded queued transaction missing: %v", tx10)
|
||||
}
|
||||
if _, ok := pool.queue[account].txs.items[tx12.Nonce()]; ok {
|
||||
if _, ok := list.txs.items[tx12.Nonce()]; ok {
|
||||
t.Errorf("out-of-fund queued transaction present: %v", tx11)
|
||||
}
|
||||
if pool.all.Count() != 4 {
|
||||
|
|
@ -763,10 +764,11 @@ func TestDropping(t *testing.T) {
|
|||
if _, ok := pool.pending[account].txs.items[tx1.Nonce()]; ok {
|
||||
t.Errorf("over-gased pending transaction present: %v", tx1)
|
||||
}
|
||||
if _, ok := pool.queue[account].txs.items[tx10.Nonce()]; !ok {
|
||||
list, _ = pool.queue.get(account)
|
||||
if _, ok := list.txs.items[tx10.Nonce()]; !ok {
|
||||
t.Errorf("funded queued transaction missing: %v", tx10)
|
||||
}
|
||||
if _, ok := pool.queue[account].txs.items[tx11.Nonce()]; ok {
|
||||
if _, ok := list.txs.items[tx11.Nonce()]; ok {
|
||||
t.Errorf("over-gased queued transaction present: %v", tx11)
|
||||
}
|
||||
if pool.all.Count() != 2 {
|
||||
|
|
@ -820,8 +822,8 @@ func TestPostponing(t *testing.T) {
|
|||
if pending := pool.pending[accs[0]].Len() + pool.pending[accs[1]].Len(); pending != len(txs) {
|
||||
t.Errorf("pending transaction mismatch: have %d, want %d", pending, len(txs))
|
||||
}
|
||||
if len(pool.queue) != 0 {
|
||||
t.Errorf("queued accounts mismatch: have %d, want %d", len(pool.queue), 0)
|
||||
if len(pool.queue.addresses()) != 0 {
|
||||
t.Errorf("queued accounts mismatch: have %d, want %d", len(pool.queue.addresses()), 0)
|
||||
}
|
||||
if pool.all.Count() != len(txs) {
|
||||
t.Errorf("total transaction mismatch: have %d, want %d", pool.all.Count(), len(txs))
|
||||
|
|
@ -830,8 +832,8 @@ func TestPostponing(t *testing.T) {
|
|||
if pending := pool.pending[accs[0]].Len() + pool.pending[accs[1]].Len(); pending != len(txs) {
|
||||
t.Errorf("pending transaction mismatch: have %d, want %d", pending, len(txs))
|
||||
}
|
||||
if len(pool.queue) != 0 {
|
||||
t.Errorf("queued accounts mismatch: have %d, want %d", len(pool.queue), 0)
|
||||
if len(pool.queue.addresses()) != 0 {
|
||||
t.Errorf("queued accounts mismatch: have %d, want %d", len(pool.queue.addresses()), 0)
|
||||
}
|
||||
if pool.all.Count() != len(txs) {
|
||||
t.Errorf("total transaction mismatch: have %d, want %d", pool.all.Count(), len(txs))
|
||||
|
|
@ -847,7 +849,8 @@ func TestPostponing(t *testing.T) {
|
|||
if _, ok := pool.pending[accs[0]].txs.items[txs[0].Nonce()]; !ok {
|
||||
t.Errorf("tx %d: valid and funded transaction missing from pending pool: %v", 0, txs[0])
|
||||
}
|
||||
if _, ok := pool.queue[accs[0]].txs.items[txs[0].Nonce()]; ok {
|
||||
list, _ := pool.queue.get(accs[0])
|
||||
if _, ok := list.txs.items[txs[0].Nonce()]; ok {
|
||||
t.Errorf("tx %d: valid and funded transaction present in future queue: %v", 0, txs[0])
|
||||
}
|
||||
for i, tx := range txs[1:100] {
|
||||
|
|
@ -855,14 +858,14 @@ func TestPostponing(t *testing.T) {
|
|||
if _, ok := pool.pending[accs[0]].txs.items[tx.Nonce()]; ok {
|
||||
t.Errorf("tx %d: valid but future transaction present in pending pool: %v", i+1, tx)
|
||||
}
|
||||
if _, ok := pool.queue[accs[0]].txs.items[tx.Nonce()]; !ok {
|
||||
if _, ok := list.txs.items[tx.Nonce()]; !ok {
|
||||
t.Errorf("tx %d: valid but future transaction missing from future queue: %v", i+1, tx)
|
||||
}
|
||||
} else {
|
||||
if _, ok := pool.pending[accs[0]].txs.items[tx.Nonce()]; ok {
|
||||
t.Errorf("tx %d: out-of-fund transaction present in pending pool: %v", i+1, tx)
|
||||
}
|
||||
if _, ok := pool.queue[accs[0]].txs.items[tx.Nonce()]; ok {
|
||||
if _, ok := list.txs.items[tx.Nonce()]; ok {
|
||||
t.Errorf("tx %d: out-of-fund transaction present in future queue: %v", i+1, tx)
|
||||
}
|
||||
}
|
||||
|
|
@ -872,13 +875,14 @@ func TestPostponing(t *testing.T) {
|
|||
if pool.pending[accs[1]] != nil {
|
||||
t.Errorf("invalidated account still has pending transactions")
|
||||
}
|
||||
list, _ = pool.queue.get(accs[1])
|
||||
for i, tx := range txs[100:] {
|
||||
if i%2 == 1 {
|
||||
if _, ok := pool.queue[accs[1]].txs.items[tx.Nonce()]; !ok {
|
||||
if _, ok := list.txs.items[tx.Nonce()]; !ok {
|
||||
t.Errorf("tx %d: valid but future transaction missing from future queue: %v", 100+i, tx)
|
||||
}
|
||||
} else {
|
||||
if _, ok := pool.queue[accs[1]].txs.items[tx.Nonce()]; ok {
|
||||
if _, ok := list.txs.items[tx.Nonce()]; ok {
|
||||
t.Errorf("tx %d: out-of-fund transaction present in future queue: %v", 100+i, tx)
|
||||
}
|
||||
}
|
||||
|
|
@ -963,13 +967,14 @@ func TestQueueAccountLimiting(t *testing.T) {
|
|||
if len(pool.pending) != 0 {
|
||||
t.Errorf("tx %d: pending pool size mismatch: have %d, want %d", i, len(pool.pending), 0)
|
||||
}
|
||||
list, _ := pool.queue.get(account)
|
||||
if i <= testTxPoolConfig.AccountQueue {
|
||||
if pool.queue[account].Len() != int(i) {
|
||||
t.Errorf("tx %d: queue size mismatch: have %d, want %d", i, pool.queue[account].Len(), i)
|
||||
if list.Len() != int(i) {
|
||||
t.Errorf("tx %d: queue size mismatch: have %d, want %d", i, list.Len(), i)
|
||||
}
|
||||
} else {
|
||||
if pool.queue[account].Len() != int(testTxPoolConfig.AccountQueue) {
|
||||
t.Errorf("tx %d: queue limit mismatch: have %d, want %d", i, pool.queue[account].Len(), testTxPoolConfig.AccountQueue)
|
||||
if list.Len() != int(testTxPoolConfig.AccountQueue) {
|
||||
t.Errorf("tx %d: queue limit mismatch: have %d, want %d", i, list.Len(), testTxPoolConfig.AccountQueue)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1020,7 +1025,7 @@ func TestQueueGlobalLimiting(t *testing.T) {
|
|||
pool.addRemotesSync(txs)
|
||||
|
||||
queued := 0
|
||||
for addr, list := range pool.queue {
|
||||
for addr, list := range pool.queue.queued {
|
||||
if list.Len() > int(config.AccountQueue) {
|
||||
t.Errorf("addr %x: queued accounts overflown allowance: %d > %d", addr, list.Len(), config.AccountQueue)
|
||||
}
|
||||
|
|
@ -1179,8 +1184,8 @@ func TestPendingLimiting(t *testing.T) {
|
|||
if pool.pending[account].Len() != int(i)+1 {
|
||||
t.Errorf("tx %d: pending pool size mismatch: have %d, want %d", i, pool.pending[account].Len(), i+1)
|
||||
}
|
||||
if len(pool.queue) != 0 {
|
||||
t.Errorf("tx %d: queue size mismatch: have %d, want %d", i, pool.queue[account].Len(), 0)
|
||||
if len(pool.queue.addresses()) != 0 {
|
||||
t.Errorf("tx %d: queue size mismatch: have %d, want %d", i, len(pool.queue.addresses()), 0)
|
||||
}
|
||||
}
|
||||
if pool.all.Count() != int(testTxPoolConfig.AccountQueue+5) {
|
||||
|
|
|
|||
275
core/txpool/legacypool/queue.go
Normal file
275
core/txpool/legacypool/queue.go
Normal file
|
|
@ -0,0 +1,275 @@
|
|||
// Copyright 2025 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package legacypool
|
||||
|
||||
import (
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/state"
|
||||
"github.com/ethereum/go-ethereum/core/txpool"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
)
|
||||
|
||||
// queue manages nonce-gapped transactions that have been validated but are
|
||||
// not yet processable.
|
||||
type queue struct {
|
||||
config Config
|
||||
signer types.Signer
|
||||
queued map[common.Address]*list // Queued but non-processable transactions
|
||||
beats map[common.Address]time.Time // Last heartbeat from each known account
|
||||
}
|
||||
|
||||
func newQueue(config Config, signer types.Signer) *queue {
|
||||
return &queue{
|
||||
signer: signer,
|
||||
config: config,
|
||||
queued: make(map[common.Address]*list),
|
||||
beats: make(map[common.Address]time.Time),
|
||||
}
|
||||
}
|
||||
|
||||
// evictList returns the hashes of transactions that are old enough to be evicted.
|
||||
func (q *queue) evictList() []common.Hash {
|
||||
var removed []common.Hash
|
||||
for addr, list := range q.queued {
|
||||
if time.Since(q.beats[addr]) > q.config.Lifetime {
|
||||
for _, tx := range list.Flatten() {
|
||||
removed = append(removed, tx.Hash())
|
||||
}
|
||||
}
|
||||
}
|
||||
queuedEvictionMeter.Mark(int64(len(removed)))
|
||||
return removed
|
||||
}
|
||||
|
||||
func (q *queue) stats() int {
|
||||
queued := 0
|
||||
for _, list := range q.queued {
|
||||
queued += list.Len()
|
||||
}
|
||||
return queued
|
||||
}
|
||||
|
||||
func (q *queue) content() map[common.Address][]*types.Transaction {
|
||||
queued := make(map[common.Address][]*types.Transaction, len(q.queued))
|
||||
for addr, list := range q.queued {
|
||||
queued[addr] = list.Flatten()
|
||||
}
|
||||
return queued
|
||||
}
|
||||
|
||||
func (q *queue) contentFrom(addr common.Address) []*types.Transaction {
|
||||
var queued []*types.Transaction
|
||||
if list, ok := q.get(addr); ok {
|
||||
queued = list.Flatten()
|
||||
}
|
||||
return queued
|
||||
}
|
||||
|
||||
func (q *queue) get(addr common.Address) (*list, bool) {
|
||||
l, ok := q.queued[addr]
|
||||
return l, ok
|
||||
}
|
||||
|
||||
func (q *queue) bump(addr common.Address) {
|
||||
q.beats[addr] = time.Now()
|
||||
}
|
||||
|
||||
func (q *queue) addresses() []common.Address {
|
||||
addrs := make([]common.Address, 0, len(q.queued))
|
||||
for addr := range q.queued {
|
||||
addrs = append(addrs, addr)
|
||||
}
|
||||
return addrs
|
||||
}
|
||||
|
||||
func (q *queue) remove(addr common.Address, tx *types.Transaction) {
|
||||
if future := q.queued[addr]; future != nil {
|
||||
if txOld := future.txs.Get(tx.Nonce()); txOld != nil && txOld.Hash() != tx.Hash() {
|
||||
// Edge case, a different transaction
|
||||
// with the same nonce is in the queued, just ignore
|
||||
return
|
||||
}
|
||||
if removed, _ := future.Remove(tx); removed {
|
||||
// Reduce the queued counter
|
||||
queuedGauge.Dec(1)
|
||||
}
|
||||
if future.Empty() {
|
||||
delete(q.queued, addr)
|
||||
delete(q.beats, addr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (q *queue) add(tx *types.Transaction) (*common.Hash, error) {
|
||||
// Try to insert the transaction into the future queue
|
||||
from, _ := types.Sender(q.signer, tx) // already validated
|
||||
if q.queued[from] == nil {
|
||||
q.queued[from] = newList(false)
|
||||
}
|
||||
inserted, old := q.queued[from].Add(tx, q.config.PriceBump)
|
||||
if !inserted {
|
||||
// An older transaction was better, discard this
|
||||
queuedDiscardMeter.Mark(1)
|
||||
return nil, txpool.ErrReplaceUnderpriced
|
||||
}
|
||||
// If we never record the heartbeat, do it right now.
|
||||
if _, exist := q.beats[from]; !exist {
|
||||
q.beats[from] = time.Now()
|
||||
}
|
||||
if old == nil {
|
||||
// Nothing was replaced, bump the queued counter
|
||||
queuedGauge.Inc(1)
|
||||
return nil, nil
|
||||
}
|
||||
h := old.Hash()
|
||||
// Transaction was replaced, bump the replacement counter
|
||||
queuedReplaceMeter.Mark(1)
|
||||
return &h, nil
|
||||
}
|
||||
|
||||
// promoteExecutables iterates over all accounts with queued transactions, selecting
|
||||
// for promotion any that are now executable. It also drops any transactions that are
|
||||
// deemed too old (nonce too low) or too costly (insufficient funds or over gas limit).
|
||||
//
|
||||
// Returns three lists:
|
||||
// - all transactions that were removed from the queue and selected for promotion;
|
||||
// - all other transactions that were removed from the queue and dropped;
|
||||
// - the list of addresses removed.
|
||||
func (q *queue) promoteExecutables(accounts []common.Address, gasLimit uint64, currentState *state.StateDB, nonces *noncer) ([]*types.Transaction, []common.Hash, []common.Address) {
|
||||
// Track the promotable transactions to broadcast them at once
|
||||
var (
|
||||
promotable []*types.Transaction
|
||||
dropped []common.Hash
|
||||
removedAddresses []common.Address
|
||||
)
|
||||
// Iterate over all accounts and promote any executable transactions
|
||||
for _, addr := range accounts {
|
||||
list := q.queued[addr]
|
||||
if list == nil {
|
||||
continue // Just in case someone calls with a non existing account
|
||||
}
|
||||
// Drop all transactions that are deemed too old (low nonce)
|
||||
forwards := list.Forward(currentState.GetNonce(addr))
|
||||
for _, tx := range forwards {
|
||||
dropped = append(dropped, tx.Hash())
|
||||
}
|
||||
log.Trace("Removing old queued transactions", "count", len(forwards))
|
||||
|
||||
// Drop all transactions that are too costly (low balance or out of gas)
|
||||
drops, _ := list.Filter(currentState.GetBalance(addr), gasLimit)
|
||||
for _, tx := range drops {
|
||||
dropped = append(dropped, tx.Hash())
|
||||
}
|
||||
log.Trace("Removing unpayable queued transactions", "count", len(drops))
|
||||
queuedNofundsMeter.Mark(int64(len(drops)))
|
||||
|
||||
// Gather all executable transactions and promote them
|
||||
readies := list.Ready(nonces.get(addr))
|
||||
promotable = append(promotable, readies...)
|
||||
log.Trace("Promoting queued transactions", "count", len(promotable))
|
||||
queuedGauge.Dec(int64(len(readies)))
|
||||
|
||||
// Drop all transactions over the allowed limit
|
||||
var caps = list.Cap(int(q.config.AccountQueue))
|
||||
for _, tx := range caps {
|
||||
hash := tx.Hash()
|
||||
dropped = append(dropped, hash)
|
||||
log.Trace("Removing cap-exceeding queued transaction", "hash", hash)
|
||||
}
|
||||
queuedRateLimitMeter.Mark(int64(len(caps)))
|
||||
|
||||
// Delete the entire queue entry if it became empty.
|
||||
if list.Empty() {
|
||||
delete(q.queued, addr)
|
||||
delete(q.beats, addr)
|
||||
removedAddresses = append(removedAddresses, addr)
|
||||
}
|
||||
}
|
||||
queuedGauge.Dec(int64(len(dropped)))
|
||||
return promotable, dropped, removedAddresses
|
||||
}
|
||||
|
||||
// truncate drops the oldest transactions from the queue until the total
|
||||
// number is below the configured limit. Returns the hashes of all dropped
|
||||
// transactions and the addresses of accounts that became empty due to
|
||||
// the truncation.
|
||||
func (q *queue) truncate() ([]common.Hash, []common.Address) {
|
||||
queued := uint64(0)
|
||||
for _, list := range q.queued {
|
||||
queued += uint64(list.Len())
|
||||
}
|
||||
if queued <= q.config.GlobalQueue {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Sort all accounts with queued transactions by heartbeat
|
||||
addresses := make(addressesByHeartbeat, 0, len(q.queued))
|
||||
for addr := range q.queued {
|
||||
addresses = append(addresses, addressByHeartbeat{addr, q.beats[addr]})
|
||||
}
|
||||
sort.Sort(sort.Reverse(addresses))
|
||||
|
||||
// Drop transactions until the total is below the limit
|
||||
var (
|
||||
removed = make([]common.Hash, 0)
|
||||
removedAddresses = make([]common.Address, 0)
|
||||
)
|
||||
for drop := queued - q.config.GlobalQueue; drop > 0 && len(addresses) > 0; {
|
||||
addr := addresses[len(addresses)-1]
|
||||
list := q.queued[addr.address]
|
||||
|
||||
addresses = addresses[:len(addresses)-1]
|
||||
|
||||
// Drop all transactions if they are less than the overflow
|
||||
if size := uint64(list.Len()); size <= drop {
|
||||
for _, tx := range list.Flatten() {
|
||||
q.remove(addr.address, tx)
|
||||
removed = append(removed, tx.Hash())
|
||||
}
|
||||
drop -= size
|
||||
queuedRateLimitMeter.Mark(int64(size))
|
||||
removedAddresses = append(removedAddresses, addr.address)
|
||||
continue
|
||||
}
|
||||
// Otherwise drop only last few transactions
|
||||
txs := list.Flatten()
|
||||
for i := len(txs) - 1; i >= 0 && drop > 0; i-- {
|
||||
q.remove(addr.address, txs[i])
|
||||
removed = append(removed, txs[i].Hash())
|
||||
drop--
|
||||
queuedRateLimitMeter.Mark(1)
|
||||
}
|
||||
}
|
||||
// No need to clear empty accounts, remove already does that
|
||||
return removed, removedAddresses
|
||||
}
|
||||
|
||||
// addressByHeartbeat is an account address tagged with its last activity timestamp.
|
||||
type addressByHeartbeat struct {
|
||||
address common.Address
|
||||
heartbeat time.Time
|
||||
}
|
||||
|
||||
type addressesByHeartbeat []addressByHeartbeat
|
||||
|
||||
func (a addressesByHeartbeat) Len() int { return len(a) }
|
||||
func (a addressesByHeartbeat) Less(i, j int) bool { return a[i].heartbeat.Before(a[j].heartbeat) }
|
||||
func (a addressesByHeartbeat) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||
|
|
@ -65,7 +65,6 @@ type BlockChain interface {
|
|||
type TxPool struct {
|
||||
subpools []SubPool // List of subpools for specialized transaction handling
|
||||
chain BlockChain
|
||||
signer types.Signer
|
||||
|
||||
stateLock sync.RWMutex // The lock for protecting state instance
|
||||
state *state.StateDB // Current state at the blockchain head
|
||||
|
|
@ -98,7 +97,6 @@ func New(gasTip uint64, chain BlockChain, subpools []SubPool) (*TxPool, error) {
|
|||
pool := &TxPool{
|
||||
subpools: subpools,
|
||||
chain: chain,
|
||||
signer: types.LatestSigner(chain.Config()),
|
||||
state: statedb,
|
||||
quit: make(chan chan error),
|
||||
term: make(chan struct{}),
|
||||
|
|
|
|||
|
|
@ -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{}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ import (
|
|||
"fmt"
|
||||
"math/big"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common/bitutil"
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
)
|
||||
|
|
@ -125,9 +126,7 @@ func MergeBloom(receipts Receipts) Bloom {
|
|||
for _, receipt := range receipts {
|
||||
if len(receipt.Logs) != 0 {
|
||||
bl := receipt.Bloom.Bytes()
|
||||
for i := range bin {
|
||||
bin[i] |= bl[i]
|
||||
}
|
||||
bitutil.ORBytes(bin[:], bin[:], bl)
|
||||
}
|
||||
}
|
||||
return bin
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -47,7 +47,6 @@ import (
|
|||
|
||||
// Register adds the engine API to the full node.
|
||||
func Register(stack *node.Node, backend *eth.Ethereum) error {
|
||||
log.Warn("Engine API enabled", "protocol", "eth")
|
||||
stack.RegisterAPIs([]rpc.API{
|
||||
{
|
||||
Namespace: "engine",
|
||||
|
|
@ -492,6 +491,12 @@ func (api *ConsensusAPI) getPayload(payloadID engine.PayloadID, full bool) (*eng
|
|||
// Client software MAY return an array of all null entries if syncing or otherwise
|
||||
// unable to serve blob pool data.
|
||||
func (api *ConsensusAPI) GetBlobsV1(hashes []common.Hash) ([]*engine.BlobAndProofV1, error) {
|
||||
// Reject the request if Osaka has been activated.
|
||||
// follow https://github.com/ethereum/execution-apis/blob/main/src/engine/osaka.md#cancun-api
|
||||
head := api.eth.BlockChain().CurrentHeader()
|
||||
if !api.checkFork(head.Time, forks.Cancun, forks.Prague) {
|
||||
return nil, unsupportedForkErr("engine_getBlobsV1 is only available at Cancun/Prague fork")
|
||||
}
|
||||
if len(hashes) > 128 {
|
||||
return nil, engine.TooLargeRequest.With(fmt.Errorf("requested blob count too large: %v", len(hashes)))
|
||||
}
|
||||
|
|
@ -532,9 +537,6 @@ func (api *ConsensusAPI) GetBlobsV1(hashes []common.Hash) ([]*engine.BlobAndProo
|
|||
// - if the request is [A_versioned_hash_for_blob_with_blob_proof], the response
|
||||
// MUST be null as well.
|
||||
//
|
||||
// Note, geth internally make the conversion from old version to new one, so the
|
||||
// data will be returned normally.
|
||||
//
|
||||
// Client software MUST support request sizes of at least 128 blob versioned
|
||||
// hashes. The client MUST return -38004: Too large request error if the number
|
||||
// of requested blobs is too large.
|
||||
|
|
@ -542,6 +544,10 @@ func (api *ConsensusAPI) GetBlobsV1(hashes []common.Hash) ([]*engine.BlobAndProo
|
|||
// Client software MUST return null if syncing or otherwise unable to serve
|
||||
// blob pool data.
|
||||
func (api *ConsensusAPI) GetBlobsV2(hashes []common.Hash) ([]*engine.BlobAndProofV2, error) {
|
||||
head := api.eth.BlockChain().CurrentHeader()
|
||||
if api.config().LatestFork(head.Time) < forks.Osaka {
|
||||
return nil, unsupportedForkErr("engine_getBlobsV2 is not available before Osaka fork")
|
||||
}
|
||||
if len(hashes) > 128 {
|
||||
return nil, engine.TooLargeRequest.With(fmt.Errorf("requested blob count too large: %v", len(hashes)))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1991,6 +1991,31 @@ func TestGetBlobsV1(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestGetBlobsV1AfterOsakaFork(t *testing.T) {
|
||||
genesis := &core.Genesis{
|
||||
Config: params.MergedTestChainConfig,
|
||||
Alloc: types.GenesisAlloc{testAddr: {Balance: testBalance}},
|
||||
Difficulty: common.Big0,
|
||||
Timestamp: 1, // Timestamp > 0 to ensure Osaka fork is active
|
||||
}
|
||||
n, ethServ := startEthService(t, genesis, nil)
|
||||
defer n.Close()
|
||||
|
||||
var engineErr *engine.EngineAPIError
|
||||
api := newConsensusAPIWithoutHeartbeat(ethServ)
|
||||
_, err := api.GetBlobsV1([]common.Hash{testrand.Hash()})
|
||||
if !errors.As(err, &engineErr) {
|
||||
t.Fatalf("Unexpected error: %T", err)
|
||||
} else {
|
||||
if engineErr.ErrorCode() != -38005 {
|
||||
t.Fatalf("Expected error code -38005, got %d", engineErr.ErrorCode())
|
||||
}
|
||||
if engineErr.Error() != "Unsupported fork" {
|
||||
t.Fatalf("Expected error message 'Unsupported fork', got '%s'", engineErr.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetBlobsV2(t *testing.T) {
|
||||
n, api := newGetBlobEnv(t, 1)
|
||||
defer n.Close()
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -140,6 +143,7 @@ func (api *FilterAPI) NewPendingTransactionFilter(fullTx *bool) rpc.ID {
|
|||
api.filtersMu.Unlock()
|
||||
|
||||
go func() {
|
||||
defer pendingTxSub.Unsubscribe()
|
||||
for {
|
||||
select {
|
||||
case pTx := <-pendingTxs:
|
||||
|
|
@ -214,6 +218,7 @@ func (api *FilterAPI) NewBlockFilter() rpc.ID {
|
|||
api.filtersMu.Unlock()
|
||||
|
||||
go func() {
|
||||
defer headerSub.Unsubscribe()
|
||||
for {
|
||||
select {
|
||||
case h := <-headers:
|
||||
|
|
@ -295,6 +300,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
|
||||
|
|
@ -322,6 +404,7 @@ func (api *FilterAPI) NewFilter(crit FilterCriteria) (rpc.ID, error) {
|
|||
api.filtersMu.Unlock()
|
||||
|
||||
go func() {
|
||||
defer logsSub.Unsubscribe()
|
||||
for {
|
||||
select {
|
||||
case l := <-logs:
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -181,8 +181,7 @@ func newHandler(config *handlerConfig) (*handler, error) {
|
|||
} else {
|
||||
head := h.chain.CurrentBlock()
|
||||
if head.Number.Uint64() > 0 && h.chain.HasState(head.Root) {
|
||||
// Print warning log if database is not empty to run snap sync.
|
||||
log.Warn("Switch sync mode from snap sync to full sync", "reason", "snap sync complete")
|
||||
log.Info("Switch sync mode from snap sync to full sync", "reason", "snap sync complete")
|
||||
} else {
|
||||
// If snap sync was requested and our database is empty, grant it
|
||||
h.snapSync.Store(true)
|
||||
|
|
|
|||
|
|
@ -494,12 +494,19 @@ func handleTransactions(backend Backend, msg Decoder, peer *Peer) error {
|
|||
if err := msg.Decode(&txs); err != nil {
|
||||
return err
|
||||
}
|
||||
// Duplicate transactions are not allowed
|
||||
seen := make(map[common.Hash]struct{})
|
||||
for i, tx := range txs {
|
||||
// Validate and mark the remote transaction
|
||||
if tx == nil {
|
||||
return fmt.Errorf("Transactions: transaction %d is nil", i)
|
||||
}
|
||||
peer.markTransaction(tx.Hash())
|
||||
hash := tx.Hash()
|
||||
if _, exists := seen[hash]; exists {
|
||||
return fmt.Errorf("Transactions: multiple copies of the same hash %v", hash)
|
||||
}
|
||||
seen[hash] = struct{}{}
|
||||
peer.markTransaction(hash)
|
||||
}
|
||||
return backend.Handle(peer, &txs)
|
||||
}
|
||||
|
|
@ -514,12 +521,19 @@ func handlePooledTransactions(backend Backend, msg Decoder, peer *Peer) error {
|
|||
if err := msg.Decode(&txs); err != nil {
|
||||
return err
|
||||
}
|
||||
// Duplicate transactions are not allowed
|
||||
seen := make(map[common.Hash]struct{})
|
||||
for i, tx := range txs.PooledTransactionsResponse {
|
||||
// Validate and mark the remote transaction
|
||||
if tx == nil {
|
||||
return fmt.Errorf("PooledTransactions: transaction %d is nil", i)
|
||||
}
|
||||
peer.markTransaction(tx.Hash())
|
||||
hash := tx.Hash()
|
||||
if _, exists := seen[hash]; exists {
|
||||
return fmt.Errorf("PooledTransactions: multiple copies of the same hash %v", hash)
|
||||
}
|
||||
seen[hash] = struct{}{}
|
||||
peer.markTransaction(hash)
|
||||
}
|
||||
requestTracker.Fulfil(peer.id, peer.version, PooledTransactionsMsg, txs.RequestId)
|
||||
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
86
eth/tracers/native/keccak256_preimage.go
Normal file
86
eth/tracers/native/keccak256_preimage.go
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
package native
|
||||
|
||||
// Copyright 2021 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/>.
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/ethereum/go-ethereum/core/tracing"
|
||||
"github.com/ethereum/go-ethereum/core/vm"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/eth/tracers"
|
||||
"github.com/ethereum/go-ethereum/eth/tracers/internal"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
)
|
||||
|
||||
func init() {
|
||||
tracers.DefaultDirectory.Register("keccak256PreimageTracer", newKeccak256PreimageTracer, false)
|
||||
}
|
||||
|
||||
// keccak256PreimageTracer is a native tracer that collects preimages of all KECCAK256 operations.
|
||||
// This tracer is particularly useful for analyzing smart contract execution patterns,
|
||||
// especially when debugging storage access in Solidity mappings and dynamic arrays.
|
||||
type keccak256PreimageTracer struct {
|
||||
computedHashes map[common.Hash]hexutil.Bytes
|
||||
}
|
||||
|
||||
// newKeccak256PreimageTracer returns a new keccak256PreimageTracer instance.
|
||||
func newKeccak256PreimageTracer(ctx *tracers.Context, cfg json.RawMessage, chainConfig *params.ChainConfig) (*tracers.Tracer, error) {
|
||||
t := &keccak256PreimageTracer{
|
||||
computedHashes: make(map[common.Hash]hexutil.Bytes),
|
||||
}
|
||||
return &tracers.Tracer{
|
||||
Hooks: &tracing.Hooks{
|
||||
OnOpcode: t.OnOpcode,
|
||||
},
|
||||
GetResult: t.GetResult,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (t *keccak256PreimageTracer) OnOpcode(pc uint64, op byte, gas, cost uint64, scope tracing.OpContext, rData []byte, depth int, err error) {
|
||||
if op == byte(vm.KECCAK256) {
|
||||
sd := scope.StackData()
|
||||
// it turns out that sometimes the stack is empty, evm will fail in this case, but we should not panic here
|
||||
if len(sd) < 2 {
|
||||
return
|
||||
}
|
||||
|
||||
dataOffset := internal.StackBack(sd, 0).Uint64()
|
||||
dataLength := internal.StackBack(sd, 1).Uint64()
|
||||
preimage, err := internal.GetMemoryCopyPadded(scope.MemoryData(), int64(dataOffset), int64(dataLength))
|
||||
if err != nil {
|
||||
log.Warn("keccak256PreimageTracer: failed to copy keccak preimage from memory", "err", err)
|
||||
return
|
||||
}
|
||||
|
||||
hash := crypto.Keccak256(preimage)
|
||||
|
||||
t.computedHashes[common.Hash(hash)] = hexutil.Bytes(preimage)
|
||||
}
|
||||
}
|
||||
|
||||
// GetResult returns the collected keccak256 preimages as a JSON object mapping hashes to preimages.
|
||||
func (t *keccak256PreimageTracer) GetResult() (json.RawMessage, error) {
|
||||
msg, err := json.Marshal(t.computedHashes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return msg, nil
|
||||
}
|
||||
442
eth/tracers/native/keccak256_preimage_test.go
Normal file
442
eth/tracers/native/keccak256_preimage_test.go
Normal file
|
|
@ -0,0 +1,442 @@
|
|||
// Copyright 2021 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 native_test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/ethereum/go-ethereum/core/tracing"
|
||||
"github.com/ethereum/go-ethereum/core/vm"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/eth/tracers"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"github.com/holiman/uint256"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// mockOpContext implements tracing.OpContext for testing
|
||||
type mockOpContext struct {
|
||||
memory []byte
|
||||
stack []uint256.Int
|
||||
}
|
||||
|
||||
// Ensure mockOpContext implements tracing.OpContext
|
||||
var _ tracing.OpContext = (*mockOpContext)(nil)
|
||||
|
||||
func (m *mockOpContext) MemoryData() []byte {
|
||||
return m.memory
|
||||
}
|
||||
|
||||
func (m *mockOpContext) StackData() []uint256.Int {
|
||||
return m.stack
|
||||
}
|
||||
|
||||
func (m *mockOpContext) Address() common.Address {
|
||||
return common.Address{}
|
||||
}
|
||||
|
||||
func (m *mockOpContext) Caller() common.Address {
|
||||
return common.Address{}
|
||||
}
|
||||
|
||||
func (m *mockOpContext) CallValue() *uint256.Int {
|
||||
return uint256.NewInt(0)
|
||||
}
|
||||
|
||||
func (m *mockOpContext) CallInput() []byte {
|
||||
return []byte{}
|
||||
}
|
||||
|
||||
func (m *mockOpContext) ContractCode() []byte {
|
||||
return []byte{}
|
||||
}
|
||||
|
||||
func TestKeccak256PreimageTracerCreation(t *testing.T) {
|
||||
tracer, err := tracers.DefaultDirectory.New("keccak256PreimageTracer", &tracers.Context{}, nil, params.MainnetChainConfig)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, tracer)
|
||||
require.NotNil(t, tracer.Hooks)
|
||||
require.NotNil(t, tracer.Hooks.OnOpcode)
|
||||
require.NotNil(t, tracer.GetResult)
|
||||
}
|
||||
|
||||
func TestKeccak256PreimageTracerInitialResult(t *testing.T) {
|
||||
tracer, err := tracers.DefaultDirectory.New("keccak256PreimageTracer", &tracers.Context{}, nil, params.MainnetChainConfig)
|
||||
require.NoError(t, err)
|
||||
|
||||
result, err := tracer.GetResult()
|
||||
require.NoError(t, err)
|
||||
|
||||
var hashes map[common.Hash]hexutil.Bytes
|
||||
err = json.Unmarshal(result, &hashes)
|
||||
require.NoError(t, err)
|
||||
require.Empty(t, hashes)
|
||||
}
|
||||
|
||||
func TestKeccak256PreimageTracerSingleKeccak(t *testing.T) {
|
||||
tracer, err := tracers.DefaultDirectory.New("keccak256PreimageTracer", &tracers.Context{}, nil, params.MainnetChainConfig)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Test data: "hello world"
|
||||
testData := []byte("hello world")
|
||||
memory := make([]byte, 32)
|
||||
copy(memory, testData)
|
||||
|
||||
// Create stack with offset=0, length=11
|
||||
stack := []uint256.Int{
|
||||
*uint256.NewInt(11), // length (stack[1])
|
||||
*uint256.NewInt(0), // offset (stack[0])
|
||||
}
|
||||
|
||||
mockScope := &mockOpContext{
|
||||
memory: memory,
|
||||
stack: stack,
|
||||
}
|
||||
|
||||
// Call OnOpcode with KECCAK256
|
||||
tracer.OnOpcode(0, byte(vm.KECCAK256), 0, 0, mockScope, nil, 0, nil)
|
||||
|
||||
// Get result
|
||||
result, err := tracer.GetResult()
|
||||
require.NoError(t, err)
|
||||
|
||||
var hashes map[common.Hash]hexutil.Bytes
|
||||
err = json.Unmarshal(result, &hashes)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Verify the hash and preimage
|
||||
expectedHash := crypto.Keccak256Hash(testData)
|
||||
require.Len(t, hashes, 1)
|
||||
require.Contains(t, hashes, expectedHash)
|
||||
require.Equal(t, hexutil.Bytes(testData), hashes[expectedHash])
|
||||
}
|
||||
|
||||
func TestKeccak256PreimageTracerMultipleKeccak(t *testing.T) {
|
||||
tracer, err := tracers.DefaultDirectory.New("keccak256PreimageTracer", &tracers.Context{}, nil, params.MainnetChainConfig)
|
||||
require.NoError(t, err)
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
data []byte
|
||||
}{
|
||||
{"empty", []byte{}},
|
||||
{"hello", []byte("hello")},
|
||||
{"world", []byte("world")},
|
||||
{"long_data", make([]byte, 100)},
|
||||
}
|
||||
|
||||
// Initialize long_data with some pattern
|
||||
for i := range testCases[3].data {
|
||||
testCases[3].data[i] = byte(i % 256)
|
||||
}
|
||||
|
||||
expectedHashes := make(map[common.Hash]hexutil.Bytes)
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
memory := make([]byte, max(len(tc.data), 1))
|
||||
copy(memory, tc.data)
|
||||
|
||||
stack := []uint256.Int{
|
||||
*uint256.NewInt(uint64(len(tc.data))), // length
|
||||
*uint256.NewInt(0), // offset
|
||||
}
|
||||
|
||||
mockScope := &mockOpContext{
|
||||
memory: memory,
|
||||
stack: stack,
|
||||
}
|
||||
|
||||
// Call OnOpcode with KECCAK256
|
||||
tracer.OnOpcode(0, byte(vm.KECCAK256), 0, 0, mockScope, nil, 0, nil)
|
||||
|
||||
expectedHash := crypto.Keccak256Hash(tc.data)
|
||||
expectedHashes[expectedHash] = hexutil.Bytes(tc.data)
|
||||
})
|
||||
}
|
||||
|
||||
// Get final result
|
||||
result, err := tracer.GetResult()
|
||||
require.NoError(t, err)
|
||||
|
||||
var hashes map[common.Hash]hexutil.Bytes
|
||||
err = json.Unmarshal(result, &hashes)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, expectedHashes, hashes)
|
||||
}
|
||||
|
||||
func TestKeccak256PreimageTracerNonKeccakOpcodes(t *testing.T) {
|
||||
tracer, err := tracers.DefaultDirectory.New("keccak256PreimageTracer", &tracers.Context{}, nil, params.MainnetChainConfig)
|
||||
require.NoError(t, err)
|
||||
|
||||
testData := []byte("should not be recorded")
|
||||
memory := make([]byte, 32)
|
||||
copy(memory, testData)
|
||||
|
||||
stack := []uint256.Int{
|
||||
*uint256.NewInt(uint64(len(testData))),
|
||||
*uint256.NewInt(0),
|
||||
}
|
||||
|
||||
mockScope := &mockOpContext{
|
||||
memory: memory,
|
||||
stack: stack,
|
||||
}
|
||||
|
||||
// Test various non-KECCAK256 opcodes
|
||||
nonKeccakOpcodes := []vm.OpCode{
|
||||
vm.ADD, vm.MUL, vm.SUB, vm.DIV, vm.SDIV, vm.MOD, vm.SMOD,
|
||||
vm.ADDMOD, vm.MULMOD, vm.EXP, vm.SIGNEXTEND, vm.SLOAD,
|
||||
vm.SSTORE, vm.MLOAD, vm.MSTORE, vm.CALL, vm.RETURN,
|
||||
}
|
||||
|
||||
for _, opcode := range nonKeccakOpcodes {
|
||||
tracer.OnOpcode(0, byte(opcode), 0, 0, mockScope, nil, 0, nil)
|
||||
}
|
||||
|
||||
// Get result - should be empty
|
||||
result, err := tracer.GetResult()
|
||||
require.NoError(t, err)
|
||||
|
||||
var hashes map[common.Hash]hexutil.Bytes
|
||||
err = json.Unmarshal(result, &hashes)
|
||||
require.NoError(t, err)
|
||||
require.Empty(t, hashes)
|
||||
}
|
||||
|
||||
func TestKeccak256PreimageTracerMemoryOffset(t *testing.T) {
|
||||
tracer, err := tracers.DefaultDirectory.New("keccak256PreimageTracer", &tracers.Context{}, nil, params.MainnetChainConfig)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Test data at different memory offset
|
||||
prefix := []byte("prefix_data_")
|
||||
testData := []byte("target_data")
|
||||
memory := make([]byte, len(prefix)+len(testData)+10)
|
||||
copy(memory, prefix)
|
||||
copy(memory[len(prefix):], testData)
|
||||
|
||||
// Stack: offset=len(prefix), length=len(testData)
|
||||
stack := []uint256.Int{
|
||||
*uint256.NewInt(uint64(len(testData))), // length
|
||||
*uint256.NewInt(uint64(len(prefix))), // offset
|
||||
}
|
||||
|
||||
mockScope := &mockOpContext{
|
||||
memory: memory,
|
||||
stack: stack,
|
||||
}
|
||||
|
||||
// Call OnOpcode with KECCAK256
|
||||
tracer.OnOpcode(0, byte(vm.KECCAK256), 0, 0, mockScope, nil, 0, nil)
|
||||
|
||||
// Get result
|
||||
result, err := tracer.GetResult()
|
||||
require.NoError(t, err)
|
||||
|
||||
var hashes map[common.Hash]hexutil.Bytes
|
||||
err = json.Unmarshal(result, &hashes)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Verify the hash matches the target data, not the prefix
|
||||
expectedHash := crypto.Keccak256Hash(testData)
|
||||
require.Len(t, hashes, 1)
|
||||
require.Contains(t, hashes, expectedHash)
|
||||
require.Equal(t, hexutil.Bytes(testData), hashes[expectedHash])
|
||||
}
|
||||
|
||||
func TestKeccak256PreimageTracerMemoryPadding(t *testing.T) {
|
||||
tracer, err := tracers.DefaultDirectory.New("keccak256PreimageTracer", &tracers.Context{}, nil, params.MainnetChainConfig)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Test data that extends beyond memory bounds (should be zero-padded)
|
||||
testData := []byte("short")
|
||||
memory := make([]byte, len(testData))
|
||||
copy(memory, testData)
|
||||
|
||||
// Request more data than available in memory
|
||||
requestedLength := len(testData) + 5
|
||||
stack := []uint256.Int{
|
||||
*uint256.NewInt(uint64(requestedLength)), // length > memory size
|
||||
*uint256.NewInt(0), // offset
|
||||
}
|
||||
|
||||
mockScope := &mockOpContext{
|
||||
memory: memory,
|
||||
stack: stack,
|
||||
}
|
||||
|
||||
// Call OnOpcode with KECCAK256
|
||||
tracer.OnOpcode(0, byte(vm.KECCAK256), 0, 0, mockScope, nil, 0, nil)
|
||||
|
||||
// Get result
|
||||
result, err := tracer.GetResult()
|
||||
require.NoError(t, err)
|
||||
|
||||
var hashes map[common.Hash]hexutil.Bytes
|
||||
err = json.Unmarshal(result, &hashes)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Verify the hash includes zero padding
|
||||
expectedData := make([]byte, requestedLength)
|
||||
copy(expectedData, testData)
|
||||
// Rest is zero-padded by default
|
||||
|
||||
expectedHash := crypto.Keccak256Hash(expectedData)
|
||||
require.Len(t, hashes, 1)
|
||||
require.Contains(t, hashes, expectedHash)
|
||||
require.Equal(t, hexutil.Bytes(expectedData), hashes[expectedHash])
|
||||
}
|
||||
|
||||
func TestKeccak256PreimageTracerDuplicateHashes(t *testing.T) {
|
||||
tracer, err := tracers.DefaultDirectory.New("keccak256PreimageTracer", &tracers.Context{}, nil, params.MainnetChainConfig)
|
||||
require.NoError(t, err)
|
||||
|
||||
testData := []byte("duplicate_test")
|
||||
memory := make([]byte, len(testData))
|
||||
copy(memory, testData)
|
||||
|
||||
stack := []uint256.Int{
|
||||
*uint256.NewInt(uint64(len(testData))),
|
||||
*uint256.NewInt(0),
|
||||
}
|
||||
|
||||
mockScope := &mockOpContext{
|
||||
memory: memory,
|
||||
stack: stack,
|
||||
}
|
||||
|
||||
// Call OnOpcode with KECCAK256 multiple times with same data
|
||||
for i := 0; i < 3; i++ {
|
||||
tracer.OnOpcode(0, byte(vm.KECCAK256), 0, 0, mockScope, nil, 0, nil)
|
||||
}
|
||||
|
||||
// Get result
|
||||
result, err := tracer.GetResult()
|
||||
require.NoError(t, err)
|
||||
|
||||
var hashes map[common.Hash]hexutil.Bytes
|
||||
err = json.Unmarshal(result, &hashes)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Should only have one entry (duplicates overwrite)
|
||||
expectedHash := crypto.Keccak256Hash(testData)
|
||||
require.Len(t, hashes, 1)
|
||||
require.Contains(t, hashes, expectedHash)
|
||||
require.Equal(t, hexutil.Bytes(testData), hashes[expectedHash])
|
||||
}
|
||||
|
||||
func TestKeccak256PreimageTracerWithExecutionError(t *testing.T) {
|
||||
tracer, err := tracers.DefaultDirectory.New("keccak256PreimageTracer", &tracers.Context{}, nil, params.MainnetChainConfig)
|
||||
require.NoError(t, err)
|
||||
|
||||
testData := []byte("error_test")
|
||||
memory := make([]byte, len(testData))
|
||||
copy(memory, testData)
|
||||
|
||||
stack := []uint256.Int{
|
||||
*uint256.NewInt(uint64(len(testData))),
|
||||
*uint256.NewInt(0),
|
||||
}
|
||||
|
||||
mockScope := &mockOpContext{
|
||||
memory: memory,
|
||||
stack: stack,
|
||||
}
|
||||
|
||||
// Call OnOpcode with KECCAK256 and an execution error
|
||||
tracer.OnOpcode(0, byte(vm.KECCAK256), 0, 0, mockScope, nil, 0, vm.ErrOutOfGas)
|
||||
|
||||
// Get result - should still record the hash even with execution error
|
||||
result, err := tracer.GetResult()
|
||||
require.NoError(t, err)
|
||||
|
||||
var hashes map[common.Hash]hexutil.Bytes
|
||||
err = json.Unmarshal(result, &hashes)
|
||||
require.NoError(t, err)
|
||||
|
||||
expectedHash := crypto.Keccak256Hash(testData)
|
||||
require.Len(t, hashes, 1)
|
||||
require.Contains(t, hashes, expectedHash)
|
||||
require.Equal(t, hexutil.Bytes(testData), hashes[expectedHash])
|
||||
}
|
||||
|
||||
func TestKeccak256PreimageTracerInsufficientStack(t *testing.T) {
|
||||
tracer, err := tracers.DefaultDirectory.New("keccak256PreimageTracer", &tracers.Context{}, nil, params.MainnetChainConfig)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Test with insufficient stack items (should cause panic, but we test it doesn't crash)
|
||||
testData := []byte("test")
|
||||
memory := make([]byte, len(testData))
|
||||
copy(memory, testData)
|
||||
|
||||
// Stack with only one item (need 2 for KECCAK256)
|
||||
stack := []uint256.Int{
|
||||
*uint256.NewInt(0), // only offset, missing length
|
||||
}
|
||||
|
||||
mockScope := &mockOpContext{
|
||||
memory: memory,
|
||||
stack: stack,
|
||||
}
|
||||
|
||||
// This should not panic due to insufficient stack
|
||||
tracer.OnOpcode(0, byte(vm.KECCAK256), 0, 0, mockScope, nil, 0, nil)
|
||||
}
|
||||
|
||||
func TestKeccak256PreimageTracerLargeData(t *testing.T) {
|
||||
tracer, err := tracers.DefaultDirectory.New("keccak256PreimageTracer", &tracers.Context{}, nil, params.MainnetChainConfig)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Test with large data
|
||||
largeData := make([]byte, 1024)
|
||||
for i := range largeData {
|
||||
largeData[i] = byte(i % 256)
|
||||
}
|
||||
|
||||
memory := make([]byte, len(largeData))
|
||||
copy(memory, largeData)
|
||||
|
||||
stack := []uint256.Int{
|
||||
*uint256.NewInt(uint64(len(largeData))),
|
||||
*uint256.NewInt(0),
|
||||
}
|
||||
|
||||
mockScope := &mockOpContext{
|
||||
memory: memory,
|
||||
stack: stack,
|
||||
}
|
||||
|
||||
// Call OnOpcode with KECCAK256
|
||||
tracer.OnOpcode(0, byte(vm.KECCAK256), 0, 0, mockScope, nil, 0, nil)
|
||||
|
||||
// Get result
|
||||
result, err := tracer.GetResult()
|
||||
require.NoError(t, err)
|
||||
|
||||
var hashes map[common.Hash]hexutil.Bytes
|
||||
err = json.Unmarshal(result, &hashes)
|
||||
require.NoError(t, err)
|
||||
|
||||
expectedHash := crypto.Keccak256Hash(largeData)
|
||||
require.Len(t, hashes, 1)
|
||||
require.Contains(t, hashes, expectedHash)
|
||||
require.Equal(t, hexutil.Bytes(largeData), hashes[expectedHash])
|
||||
}
|
||||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -121,6 +121,10 @@ type AncientReaderOp interface {
|
|||
// - if maxBytes is not specified, 'count' items will be returned if they are present
|
||||
AncientRange(kind string, start, count, maxBytes uint64) ([][]byte, error)
|
||||
|
||||
// AncientBytes retrieves the value segment of the element specified by the id
|
||||
// and value offsets.
|
||||
AncientBytes(kind string, id, offset, length uint64) ([]byte, error)
|
||||
|
||||
// Ancients returns the ancient item numbers in the ancient store.
|
||||
Ancients() (uint64, error)
|
||||
|
||||
|
|
|
|||
|
|
@ -140,6 +140,10 @@ func (db *Database) Close() error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (db *Database) AncientBytes(kind string, id, offset, length uint64) ([]byte, error) {
|
||||
panic("not supported")
|
||||
}
|
||||
|
||||
func New(client *rpc.Client) ethdb.Database {
|
||||
if client == nil {
|
||||
return nil
|
||||
|
|
|
|||
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
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -25,15 +25,6 @@ import (
|
|||
"strings"
|
||||
)
|
||||
|
||||
// FileExist checks if a file exists at path.
|
||||
func FileExist(path string) bool {
|
||||
_, err := os.Stat(path)
|
||||
if err != nil && os.IsNotExist(err) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// HashFolder iterates all files under the given directory, computing the hash
|
||||
// of each.
|
||||
func HashFolder(folder string, exlude []string) (map[string][32]byte, error) {
|
||||
|
|
|
|||
|
|
@ -449,21 +449,21 @@ func decodeHash(s string) (h common.Hash, inputLength int, err error) {
|
|||
if (len(s) & 1) > 0 {
|
||||
s = "0" + s
|
||||
}
|
||||
if len(s) > 64 {
|
||||
return common.Hash{}, len(s) / 2, errors.New("hex string too long, want at most 32 bytes")
|
||||
}
|
||||
b, err := hex.DecodeString(s)
|
||||
if err != nil {
|
||||
return common.Hash{}, 0, errors.New("hex string invalid")
|
||||
}
|
||||
if len(b) > 32 {
|
||||
return common.Hash{}, len(b), errors.New("hex string too long, want at most 32 bytes")
|
||||
}
|
||||
return common.BytesToHash(b), len(b), nil
|
||||
}
|
||||
|
||||
// GetHeaderByNumber returns the requested canonical block header.
|
||||
// - When blockNr is -1 the chain pending header is returned.
|
||||
// - When blockNr is -2 the chain latest header is returned.
|
||||
// - When blockNr is -3 the chain finalized header is returned.
|
||||
// - When blockNr is -4 the chain safe header is returned.
|
||||
// - When number is -1 the chain pending header is returned.
|
||||
// - When number is -2 the chain latest header is returned.
|
||||
// - When number is -3 the chain finalized header is returned.
|
||||
// - When number is -4 the chain safe header is returned.
|
||||
func (api *BlockChainAPI) GetHeaderByNumber(ctx context.Context, number rpc.BlockNumber) (map[string]interface{}, error) {
|
||||
header, err := api.b.HeaderByNumber(ctx, number)
|
||||
if header != nil && err == nil {
|
||||
|
|
@ -489,10 +489,10 @@ func (api *BlockChainAPI) GetHeaderByHash(ctx context.Context, hash common.Hash)
|
|||
}
|
||||
|
||||
// GetBlockByNumber returns the requested canonical block.
|
||||
// - When blockNr is -1 the chain pending block is returned.
|
||||
// - When blockNr is -2 the chain latest block is returned.
|
||||
// - When blockNr is -3 the chain finalized block is returned.
|
||||
// - When blockNr is -4 the chain safe block is returned.
|
||||
// - When number is -1 the chain pending block is returned.
|
||||
// - When number is -2 the chain latest block is returned.
|
||||
// - When number is -3 the chain finalized block is returned.
|
||||
// - When number is -4 the chain safe block is returned.
|
||||
// - When fullTx is true all transactions in the block are returned, otherwise
|
||||
// only the transaction hash is returned.
|
||||
func (api *BlockChainAPI) GetBlockByNumber(ctx context.Context, number rpc.BlockNumber, fullTx bool) (map[string]interface{}, error) {
|
||||
|
|
@ -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{}{
|
||||
|
|
@ -1603,16 +1619,9 @@ func (api *TransactionAPI) SendTransaction(ctx context.Context, args Transaction
|
|||
// processing (signing + broadcast).
|
||||
func (api *TransactionAPI) FillTransaction(ctx context.Context, args TransactionArgs) (*SignTransactionResult, error) {
|
||||
// Set some sanity defaults and terminate on failure
|
||||
sidecarVersion := types.BlobSidecarVersion0
|
||||
if len(args.Blobs) > 0 {
|
||||
h := api.b.CurrentHeader()
|
||||
if api.b.ChainConfig().IsOsaka(h.Number, h.Time) {
|
||||
sidecarVersion = types.BlobSidecarVersion1
|
||||
}
|
||||
}
|
||||
config := sidecarConfig{
|
||||
blobSidecarAllowed: true,
|
||||
blobSidecarVersion: sidecarVersion,
|
||||
blobSidecarVersion: api.currentBlobSidecarVersion(),
|
||||
}
|
||||
if err := args.setDefaults(ctx, api.b, config); err != nil {
|
||||
return nil, err
|
||||
|
|
@ -1626,6 +1635,14 @@ func (api *TransactionAPI) FillTransaction(ctx context.Context, args Transaction
|
|||
return &SignTransactionResult{data, tx}, nil
|
||||
}
|
||||
|
||||
func (api *TransactionAPI) currentBlobSidecarVersion() byte {
|
||||
h := api.b.CurrentHeader()
|
||||
if api.b.ChainConfig().IsOsaka(h.Number, h.Time) {
|
||||
return types.BlobSidecarVersion1
|
||||
}
|
||||
return types.BlobSidecarVersion0
|
||||
}
|
||||
|
||||
// SendRawTransaction will add the signed transaction to the transaction pool.
|
||||
// The sender is responsible for signing the transaction and using the correct nonce.
|
||||
func (api *TransactionAPI) SendRawTransaction(ctx context.Context, input hexutil.Bytes) (common.Hash, error) {
|
||||
|
|
@ -1633,6 +1650,19 @@ func (api *TransactionAPI) SendRawTransaction(ctx context.Context, input hexutil
|
|||
if err := tx.UnmarshalBinary(input); err != nil {
|
||||
return common.Hash{}, err
|
||||
}
|
||||
|
||||
// Convert legacy blob transaction proofs.
|
||||
// TODO: remove in go-ethereum v1.17.x
|
||||
if sc := tx.BlobTxSidecar(); sc != nil {
|
||||
exp := api.currentBlobSidecarVersion()
|
||||
if sc.Version == types.BlobSidecarVersion0 && exp == types.BlobSidecarVersion1 {
|
||||
if err := sc.ToV1(); err != nil {
|
||||
return common.Hash{}, fmt.Errorf("blob sidecar conversion failed: %v", err)
|
||||
}
|
||||
tx = tx.WithBlobTxSidecar(sc)
|
||||
}
|
||||
}
|
||||
|
||||
return SubmitTransaction(ctx, api.b, tx)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
}},
|
||||
|
|
@ -3746,8 +3750,8 @@ func TestCreateAccessListWithStateOverrides(t *testing.T) {
|
|||
if err != nil {
|
||||
t.Fatalf("Failed to create access list: %v", err)
|
||||
}
|
||||
if err != nil || result == nil {
|
||||
t.Fatalf("Failed to create access list: %v", err)
|
||||
if result == nil {
|
||||
t.Fatalf("Failed to create access list: result is nil")
|
||||
}
|
||||
require.NotNil(t, result.Accesslist)
|
||||
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -500,7 +500,7 @@ func (w *gzipResponseWriter) init() {
|
|||
hdr := w.resp.Header()
|
||||
length := hdr.Get("content-length")
|
||||
if len(length) > 0 {
|
||||
if n, err := strconv.ParseUint(length, 10, 64); err != nil {
|
||||
if n, err := strconv.ParseUint(length, 10, 64); err == nil {
|
||||
w.hasLength = true
|
||||
w.contentLength = n
|
||||
}
|
||||
|
|
|
|||
|
|
@ -153,6 +153,7 @@ type lookupIterator struct {
|
|||
cancel func()
|
||||
lookup *lookup
|
||||
tabRefreshing <-chan struct{}
|
||||
lastLookup time.Time
|
||||
}
|
||||
|
||||
type lookupFunc func(ctx context.Context) *lookup
|
||||
|
|
@ -185,6 +186,9 @@ func (it *lookupIterator) Next() bool {
|
|||
return false
|
||||
}
|
||||
if it.lookup == nil {
|
||||
// Ensure enough time has passed between lookup creations.
|
||||
it.slowdown()
|
||||
|
||||
it.lookup = it.nextLookup(it.ctx)
|
||||
if it.lookup.empty() {
|
||||
// If the lookup is empty right after creation, it means the local table
|
||||
|
|
@ -235,6 +239,25 @@ func (it *lookupIterator) lookupFailed(tab *Table, timeout time.Duration) {
|
|||
tab.waitForNodes(tout, 1)
|
||||
}
|
||||
|
||||
// slowdown applies a delay between creating lookups. This exists to prevent hot-spinning
|
||||
// in some test environments where lookups don't yield any results.
|
||||
func (it *lookupIterator) slowdown() {
|
||||
const minInterval = 1 * time.Second
|
||||
|
||||
now := time.Now()
|
||||
diff := now.Sub(it.lastLookup)
|
||||
it.lastLookup = now
|
||||
if diff > minInterval {
|
||||
return
|
||||
}
|
||||
wait := time.NewTimer(diff)
|
||||
defer wait.Stop()
|
||||
select {
|
||||
case <-wait.C:
|
||||
case <-it.ctx.Done():
|
||||
}
|
||||
}
|
||||
|
||||
// Close ends the iterator.
|
||||
func (it *lookupIterator) Close() {
|
||||
it.cancel()
|
||||
|
|
|
|||
|
|
@ -54,9 +54,8 @@ const (
|
|||
bucketIPLimit, bucketSubnet = 2, 24 // at most 2 addresses from the same /24
|
||||
tableIPLimit, tableSubnet = 10, 24
|
||||
|
||||
seedMinTableTime = 5 * time.Minute
|
||||
seedCount = 30
|
||||
seedMaxAge = 5 * 24 * time.Hour
|
||||
seedCount = 30
|
||||
seedMaxAge = 5 * 24 * time.Hour
|
||||
)
|
||||
|
||||
// Table is the 'node table', a Kademlia-like index of neighbor nodes. The table keeps
|
||||
|
|
|
|||
|
|
@ -575,6 +575,13 @@ func startLocalhostV4(t *testing.T, cfg Config) *UDPv4 {
|
|||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Wait for bootstrap to complete.
|
||||
select {
|
||||
case <-udp.tab.initDone:
|
||||
case <-time.After(5 * time.Second):
|
||||
t.Fatalf("timed out waiting for table initialization")
|
||||
}
|
||||
return udp
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ package enode
|
|||
import (
|
||||
"crypto/ecdsa"
|
||||
"encoding/base64"
|
||||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
|
@ -358,9 +359,10 @@ func ParseID(in string) (ID, error) {
|
|||
// Returns -1 if a is closer to target, 1 if b is closer to target
|
||||
// and 0 if they are equal.
|
||||
func DistCmp(target, a, b ID) int {
|
||||
for i := range target {
|
||||
da := a[i] ^ target[i]
|
||||
db := b[i] ^ target[i]
|
||||
for i := 0; i < len(target); i += 8 {
|
||||
tn := binary.BigEndian.Uint64(target[i : i+8])
|
||||
da := tn ^ binary.BigEndian.Uint64(a[i:i+8])
|
||||
db := tn ^ binary.BigEndian.Uint64(b[i:i+8])
|
||||
if da > db {
|
||||
return 1
|
||||
} else if da < db {
|
||||
|
|
@ -373,12 +375,14 @@ func DistCmp(target, a, b ID) int {
|
|||
// LogDist returns the logarithmic distance between a and b, log2(a ^ b).
|
||||
func LogDist(a, b ID) int {
|
||||
lz := 0
|
||||
for i := range a {
|
||||
x := a[i] ^ b[i]
|
||||
for i := 0; i < len(a); i += 8 {
|
||||
ai := binary.BigEndian.Uint64(a[i : i+8])
|
||||
bi := binary.BigEndian.Uint64(b[i : i+8])
|
||||
x := ai ^ bi
|
||||
if x == 0 {
|
||||
lz += 8
|
||||
lz += 64
|
||||
} else {
|
||||
lz += bits.LeadingZeros8(x)
|
||||
lz += bits.LeadingZeros64(x)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -368,6 +368,16 @@ func TestID_distcmpEqual(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func BenchmarkDistCmp(b *testing.B) {
|
||||
base := ID{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}
|
||||
aID := ID{15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0}
|
||||
bID := ID{15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 1}
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = DistCmp(base, aID, bID)
|
||||
}
|
||||
}
|
||||
|
||||
func TestID_logdist(t *testing.T) {
|
||||
logdistBig := func(a, b ID) int {
|
||||
abig, bbig := new(big.Int).SetBytes(a[:]), new(big.Int).SetBytes(b[:])
|
||||
|
|
@ -378,6 +388,28 @@ func TestID_logdist(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func makeIDs() (ID, ID) {
|
||||
var a, b ID
|
||||
size := len(a)
|
||||
// last byte differs
|
||||
for i := 0; i < size-1; i++ {
|
||||
a[i] = 0xAA
|
||||
b[i] = 0xAA
|
||||
}
|
||||
a[size-1] = 0xAA
|
||||
b[size-1] = 0xAB
|
||||
return a, b
|
||||
}
|
||||
|
||||
// Benchmark LogDist
|
||||
func BenchmarkLogDist(b *testing.B) {
|
||||
aID, bID := makeIDs() // 256-bit ID
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = LogDist(aID, bID)
|
||||
}
|
||||
}
|
||||
|
||||
// The random tests is likely to miss the case where a and b are equal,
|
||||
// this test checks it explicitly.
|
||||
func TestID_logdistEqual(t *testing.T) {
|
||||
|
|
|
|||
229
params/config.go
229
params/config.go
|
|
@ -377,25 +377,25 @@ var (
|
|||
Max: 9,
|
||||
UpdateFraction: 5007716,
|
||||
}
|
||||
// DefaultBPO1BlobConfig is the default blob configuration for the Osaka fork.
|
||||
// DefaultBPO1BlobConfig is the default blob configuration for the BPO1 fork.
|
||||
DefaultBPO1BlobConfig = &BlobConfig{
|
||||
Target: 10,
|
||||
Max: 15,
|
||||
UpdateFraction: 8346193,
|
||||
}
|
||||
// DefaultBPO1BlobConfig is the default blob configuration for the Osaka fork.
|
||||
// DefaultBPO2BlobConfig is the default blob configuration for the BPO2 fork.
|
||||
DefaultBPO2BlobConfig = &BlobConfig{
|
||||
Target: 14,
|
||||
Max: 21,
|
||||
UpdateFraction: 11684671,
|
||||
}
|
||||
// DefaultBPO1BlobConfig is the default blob configuration for the Osaka fork.
|
||||
// DefaultBPO3BlobConfig is the default blob configuration for the BPO3 fork.
|
||||
DefaultBPO3BlobConfig = &BlobConfig{
|
||||
Target: 21,
|
||||
Max: 32,
|
||||
UpdateFraction: 20609697,
|
||||
}
|
||||
// DefaultBPO1BlobConfig is the default blob configuration for the Osaka fork.
|
||||
// DefaultBPO4BlobConfig is the default blob configuration for the BPO4 fork.
|
||||
DefaultBPO4BlobConfig = &BlobConfig{
|
||||
Target: 14,
|
||||
Max: 21,
|
||||
|
|
@ -448,16 +448,17 @@ type ChainConfig struct {
|
|||
|
||||
// Fork scheduling was switched from blocks to timestamps here
|
||||
|
||||
ShanghaiTime *uint64 `json:"shanghaiTime,omitempty"` // Shanghai switch time (nil = no fork, 0 = already on shanghai)
|
||||
CancunTime *uint64 `json:"cancunTime,omitempty"` // Cancun switch time (nil = no fork, 0 = already on cancun)
|
||||
PragueTime *uint64 `json:"pragueTime,omitempty"` // Prague switch time (nil = no fork, 0 = already on prague)
|
||||
OsakaTime *uint64 `json:"osakaTime,omitempty"` // Osaka switch time (nil = no fork, 0 = already on osaka)
|
||||
VerkleTime *uint64 `json:"verkleTime,omitempty"` // Verkle switch time (nil = no fork, 0 = already on verkle)
|
||||
BPO1Time *uint64 `json:"bpo1Time,omitempty"` // BPO1 switch time (nil = no fork, 0 = already on bpo1)
|
||||
BPO2Time *uint64 `json:"bpo2Time,omitempty"` // BPO2 switch time (nil = no fork, 0 = already on bpo2)
|
||||
BPO3Time *uint64 `json:"bpo3Time,omitempty"` // BPO3 switch time (nil = no fork, 0 = already on bpo3)
|
||||
BPO4Time *uint64 `json:"bpo4Time,omitempty"` // BPO4 switch time (nil = no fork, 0 = already on bpo4)
|
||||
BPO5Time *uint64 `json:"bpo5Time,omitempty"` // BPO5 switch time (nil = no fork, 0 = already on bpo5)
|
||||
ShanghaiTime *uint64 `json:"shanghaiTime,omitempty"` // Shanghai switch time (nil = no fork, 0 = already on shanghai)
|
||||
CancunTime *uint64 `json:"cancunTime,omitempty"` // Cancun switch time (nil = no fork, 0 = already on cancun)
|
||||
PragueTime *uint64 `json:"pragueTime,omitempty"` // Prague switch time (nil = no fork, 0 = already on prague)
|
||||
OsakaTime *uint64 `json:"osakaTime,omitempty"` // Osaka switch time (nil = no fork, 0 = already on osaka)
|
||||
BPO1Time *uint64 `json:"bpo1Time,omitempty"` // BPO1 switch time (nil = no fork, 0 = already on bpo1)
|
||||
BPO2Time *uint64 `json:"bpo2Time,omitempty"` // BPO2 switch time (nil = no fork, 0 = already on bpo2)
|
||||
BPO3Time *uint64 `json:"bpo3Time,omitempty"` // BPO3 switch time (nil = no fork, 0 = already on bpo3)
|
||||
BPO4Time *uint64 `json:"bpo4Time,omitempty"` // BPO4 switch time (nil = no fork, 0 = already on bpo4)
|
||||
BPO5Time *uint64 `json:"bpo5Time,omitempty"` // BPO5 switch time (nil = no fork, 0 = already on bpo5)
|
||||
AmsterdamTime *uint64 `json:"amsterdamTime,omitempty"` // Amsterdam switch time (nil = no fork, 0 = already on amsterdam)
|
||||
VerkleTime *uint64 `json:"verkleTime,omitempty"` // Verkle switch time (nil = no fork, 0 = already on verkle)
|
||||
|
||||
// TerminalTotalDifficulty is the amount of total difficulty reached by
|
||||
// the network that triggers the consensus upgrade.
|
||||
|
|
@ -503,6 +504,96 @@ func (c CliqueConfig) String() string {
|
|||
return fmt.Sprintf("clique(period: %d, epoch: %d)", c.Period, c.Epoch)
|
||||
}
|
||||
|
||||
// String implements the fmt.Stringer interface, returning a string representation
|
||||
// of ChainConfig.
|
||||
func (c *ChainConfig) String() string {
|
||||
result := fmt.Sprintf("ChainConfig{ChainID: %v", c.ChainID)
|
||||
|
||||
// Add block-based forks
|
||||
if c.HomesteadBlock != nil {
|
||||
result += fmt.Sprintf(", HomesteadBlock: %v", c.HomesteadBlock)
|
||||
}
|
||||
if c.DAOForkBlock != nil {
|
||||
result += fmt.Sprintf(", DAOForkBlock: %v", c.DAOForkBlock)
|
||||
}
|
||||
if c.EIP150Block != nil {
|
||||
result += fmt.Sprintf(", EIP150Block: %v", c.EIP150Block)
|
||||
}
|
||||
if c.EIP155Block != nil {
|
||||
result += fmt.Sprintf(", EIP155Block: %v", c.EIP155Block)
|
||||
}
|
||||
if c.EIP158Block != nil {
|
||||
result += fmt.Sprintf(", EIP158Block: %v", c.EIP158Block)
|
||||
}
|
||||
if c.ByzantiumBlock != nil {
|
||||
result += fmt.Sprintf(", ByzantiumBlock: %v", c.ByzantiumBlock)
|
||||
}
|
||||
if c.ConstantinopleBlock != nil {
|
||||
result += fmt.Sprintf(", ConstantinopleBlock: %v", c.ConstantinopleBlock)
|
||||
}
|
||||
if c.PetersburgBlock != nil {
|
||||
result += fmt.Sprintf(", PetersburgBlock: %v", c.PetersburgBlock)
|
||||
}
|
||||
if c.IstanbulBlock != nil {
|
||||
result += fmt.Sprintf(", IstanbulBlock: %v", c.IstanbulBlock)
|
||||
}
|
||||
if c.MuirGlacierBlock != nil {
|
||||
result += fmt.Sprintf(", MuirGlacierBlock: %v", c.MuirGlacierBlock)
|
||||
}
|
||||
if c.BerlinBlock != nil {
|
||||
result += fmt.Sprintf(", BerlinBlock: %v", c.BerlinBlock)
|
||||
}
|
||||
if c.LondonBlock != nil {
|
||||
result += fmt.Sprintf(", LondonBlock: %v", c.LondonBlock)
|
||||
}
|
||||
if c.ArrowGlacierBlock != nil {
|
||||
result += fmt.Sprintf(", ArrowGlacierBlock: %v", c.ArrowGlacierBlock)
|
||||
}
|
||||
if c.GrayGlacierBlock != nil {
|
||||
result += fmt.Sprintf(", GrayGlacierBlock: %v", c.GrayGlacierBlock)
|
||||
}
|
||||
if c.MergeNetsplitBlock != nil {
|
||||
result += fmt.Sprintf(", MergeNetsplitBlock: %v", c.MergeNetsplitBlock)
|
||||
}
|
||||
|
||||
// Add timestamp-based forks
|
||||
if c.ShanghaiTime != nil {
|
||||
result += fmt.Sprintf(", ShanghaiTime: %v", *c.ShanghaiTime)
|
||||
}
|
||||
if c.CancunTime != nil {
|
||||
result += fmt.Sprintf(", CancunTime: %v", *c.CancunTime)
|
||||
}
|
||||
if c.PragueTime != nil {
|
||||
result += fmt.Sprintf(", PragueTime: %v", *c.PragueTime)
|
||||
}
|
||||
if c.OsakaTime != nil {
|
||||
result += fmt.Sprintf(", OsakaTime: %v", *c.OsakaTime)
|
||||
}
|
||||
if c.BPO1Time != nil {
|
||||
result += fmt.Sprintf(", BPO1Time: %v", *c.BPO1Time)
|
||||
}
|
||||
if c.BPO2Time != nil {
|
||||
result += fmt.Sprintf(", BPO2Time: %v", *c.BPO2Time)
|
||||
}
|
||||
if c.BPO3Time != nil {
|
||||
result += fmt.Sprintf(", BPO3Time: %v", *c.BPO3Time)
|
||||
}
|
||||
if c.BPO4Time != nil {
|
||||
result += fmt.Sprintf(", BPO4Time: %v", *c.BPO4Time)
|
||||
}
|
||||
if c.BPO5Time != nil {
|
||||
result += fmt.Sprintf(", BPO5Time: %v", *c.BPO5Time)
|
||||
}
|
||||
if c.AmsterdamTime != nil {
|
||||
result += fmt.Sprintf(", AmsterdamTime: %v", *c.AmsterdamTime)
|
||||
}
|
||||
if c.VerkleTime != nil {
|
||||
result += fmt.Sprintf(", VerkleTime: %v", *c.VerkleTime)
|
||||
}
|
||||
result += "}"
|
||||
return result
|
||||
}
|
||||
|
||||
// Description returns a human-readable description of ChainConfig.
|
||||
func (c *ChainConfig) Description() string {
|
||||
var banner string
|
||||
|
|
@ -527,34 +618,32 @@ func (c *ChainConfig) Description() string {
|
|||
// makes sense for mainnet should be optional at printing to avoid bloating
|
||||
// the output for testnets and private networks.
|
||||
banner += "Pre-Merge hard forks (block based):\n"
|
||||
banner += fmt.Sprintf(" - Homestead: #%-8v (https://ethereum.github.io/execution-specs/src/ethereum/forks/homestead/__init__.py.html)\n", c.HomesteadBlock)
|
||||
banner += fmt.Sprintf(" - Homestead: #%-8v\n", c.HomesteadBlock)
|
||||
if c.DAOForkBlock != nil {
|
||||
banner += fmt.Sprintf(" - DAO Fork: #%-8v (https://ethereum.github.io/execution-specs/src/ethereum/forks/dao_fork/__init__.py.html)\n", c.DAOForkBlock)
|
||||
banner += fmt.Sprintf(" - DAO Fork: #%-8v\n", c.DAOForkBlock)
|
||||
}
|
||||
banner += fmt.Sprintf(" - Tangerine Whistle (EIP 150): #%-8v (https://ethereum.github.io/execution-specs/src/ethereum/forks/tangerine_whistle/__init__.py.html)\n", c.EIP150Block)
|
||||
banner += fmt.Sprintf(" - Spurious Dragon/1 (EIP 155): #%-8v (https://ethereum.github.io/execution-specs/src/ethereum/forks/spurious_dragon/__init__.py.html)\n", c.EIP155Block)
|
||||
banner += fmt.Sprintf(" - Spurious Dragon/2 (EIP 158): #%-8v (https://ethereum.github.io/execution-specs/src/ethereum/forks/spurious_dragon/__init__.py.html)\n", c.EIP155Block)
|
||||
banner += fmt.Sprintf(" - Byzantium: #%-8v (https://ethereum.github.io/execution-specs/src/ethereum/forks/byzantium/__init__.py.html)\n", c.ByzantiumBlock)
|
||||
banner += fmt.Sprintf(" - Constantinople: #%-8v (https://ethereum.github.io/execution-specs/src/ethereum/forks/constantinople/__init__.py.html)\n", c.ConstantinopleBlock)
|
||||
banner += fmt.Sprintf(" - Petersburg: #%-8v (https://ethereum.github.io/execution-specs/src/ethereum/forks/constantinople/__init__.py.html)\n", c.PetersburgBlock)
|
||||
banner += fmt.Sprintf(" - Istanbul: #%-8v (https://ethereum.github.io/execution-specs/src/ethereum/forks/istanbul/__init__.py.html)\n", c.IstanbulBlock)
|
||||
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.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)
|
||||
banner += fmt.Sprintf(" - Istanbul: #%-8v\n", c.IstanbulBlock)
|
||||
if c.MuirGlacierBlock != nil {
|
||||
banner += fmt.Sprintf(" - Muir Glacier: #%-8v (https://ethereum.github.io/execution-specs/src/ethereum/forks/muir_glacier/__init__.py.html)\n", c.MuirGlacierBlock)
|
||||
banner += fmt.Sprintf(" - Muir Glacier: #%-8v\n", c.MuirGlacierBlock)
|
||||
}
|
||||
banner += fmt.Sprintf(" - Berlin: #%-8v (https://ethereum.github.io/execution-specs/src/ethereum/forks/berlin/__init__.py.html)\n", c.BerlinBlock)
|
||||
banner += fmt.Sprintf(" - London: #%-8v (https://ethereum.github.io/execution-specs/src/ethereum/forks/london/__init__.py.html)\n", c.LondonBlock)
|
||||
banner += fmt.Sprintf(" - Berlin: #%-8v\n", c.BerlinBlock)
|
||||
banner += fmt.Sprintf(" - London: #%-8v\n", c.LondonBlock)
|
||||
if c.ArrowGlacierBlock != nil {
|
||||
banner += fmt.Sprintf(" - Arrow Glacier: #%-8v (https://ethereum.github.io/execution-specs/src/ethereum/forks/arrow_glacier/__init__.py.html)\n", c.ArrowGlacierBlock)
|
||||
banner += fmt.Sprintf(" - Arrow Glacier: #%-8v\n", c.ArrowGlacierBlock)
|
||||
}
|
||||
if c.GrayGlacierBlock != nil {
|
||||
banner += fmt.Sprintf(" - Gray Glacier: #%-8v (https://ethereum.github.io/execution-specs/src/ethereum/forks/gray_glacier/__init__.py.html)\n", c.GrayGlacierBlock)
|
||||
banner += fmt.Sprintf(" - Gray Glacier: #%-8v\n", c.GrayGlacierBlock)
|
||||
}
|
||||
banner += "\n"
|
||||
|
||||
// Add a special section for the merge as it's non-obvious
|
||||
banner += "Merge configured:\n"
|
||||
banner += " - Hard-fork specification: https://ethereum.github.io/execution-specs/src/ethereum/forks/paris/__init__.py.html\n"
|
||||
banner += " - Network known to be merged\n"
|
||||
banner += fmt.Sprintf(" - Total terminal difficulty: %v\n", c.TerminalTotalDifficulty)
|
||||
if c.MergeNetsplitBlock != nil {
|
||||
banner += fmt.Sprintf(" - Merge netsplit block: #%-8v\n", c.MergeNetsplitBlock)
|
||||
|
|
@ -564,35 +653,39 @@ func (c *ChainConfig) Description() string {
|
|||
// Create a list of forks post-merge
|
||||
banner += "Post-Merge hard forks (timestamp based):\n"
|
||||
if c.ShanghaiTime != nil {
|
||||
banner += fmt.Sprintf(" - Shanghai: @%-10v (https://ethereum.github.io/execution-specs/src/ethereum/forks/shanghai/__init__.py.html)\n", *c.ShanghaiTime)
|
||||
banner += fmt.Sprintf(" - Shanghai: @%-10v\n", *c.ShanghaiTime)
|
||||
}
|
||||
if c.CancunTime != nil {
|
||||
banner += fmt.Sprintf(" - Cancun: @%-10v (https://ethereum.github.io/execution-specs/src/ethereum/forks/cancun/__init__.py.html)\n", *c.CancunTime)
|
||||
banner += fmt.Sprintf(" - Cancun: @%-10v blob: (%s)\n", *c.CancunTime, c.BlobScheduleConfig.Cancun)
|
||||
}
|
||||
if c.PragueTime != nil {
|
||||
banner += fmt.Sprintf(" - Prague: @%-10v (https://ethereum.github.io/execution-specs/src/ethereum/forks/prague/__init__.py.html)\n", *c.PragueTime)
|
||||
banner += fmt.Sprintf(" - Prague: @%-10v blob: (%s)\n", *c.PragueTime, c.BlobScheduleConfig.Prague)
|
||||
}
|
||||
if c.OsakaTime != nil {
|
||||
banner += fmt.Sprintf(" - Osaka: @%-10v (https://ethereum.github.io/execution-specs/src/ethereum/forks/osaka/__init__.py.html)\n", *c.OsakaTime)
|
||||
}
|
||||
if c.VerkleTime != nil {
|
||||
banner += fmt.Sprintf(" - Verkle: @%-10v\n", *c.VerkleTime)
|
||||
banner += fmt.Sprintf(" - Osaka: @%-10v blob: (%s)\n", *c.OsakaTime, c.BlobScheduleConfig.Osaka)
|
||||
}
|
||||
if c.BPO1Time != nil {
|
||||
banner += fmt.Sprintf(" - BPO1: @%-10v\n", *c.BPO1Time)
|
||||
banner += fmt.Sprintf(" - BPO1: @%-10v blob: (%s)\n", *c.BPO1Time, c.BlobScheduleConfig.BPO1)
|
||||
}
|
||||
if c.BPO2Time != nil {
|
||||
banner += fmt.Sprintf(" - BPO2: @%-10v\n", *c.BPO2Time)
|
||||
banner += fmt.Sprintf(" - BPO2: @%-10v blob: (%s)\n", *c.BPO2Time, c.BlobScheduleConfig.BPO2)
|
||||
}
|
||||
if c.BPO3Time != nil {
|
||||
banner += fmt.Sprintf(" - BPO3: @%-10v\n", *c.BPO3Time)
|
||||
banner += fmt.Sprintf(" - BPO3: @%-10v blob: (%s)\n", *c.BPO3Time, c.BlobScheduleConfig.BPO3)
|
||||
}
|
||||
if c.BPO4Time != nil {
|
||||
banner += fmt.Sprintf(" - BPO4: @%-10v\n", *c.BPO4Time)
|
||||
banner += fmt.Sprintf(" - BPO4: @%-10v blob: (%s)\n", *c.BPO4Time, c.BlobScheduleConfig.BPO4)
|
||||
}
|
||||
if c.BPO5Time != nil {
|
||||
banner += fmt.Sprintf(" - BPO5: @%-10v\n", *c.BPO5Time)
|
||||
banner += fmt.Sprintf(" - BPO5: @%-10v blob: (%s)\n", *c.BPO5Time, c.BlobScheduleConfig.BPO5)
|
||||
}
|
||||
if c.AmsterdamTime != nil {
|
||||
banner += fmt.Sprintf(" - Amsterdam: @%-10v blob: (%s)\n", *c.AmsterdamTime, c.BlobScheduleConfig.Amsterdam)
|
||||
}
|
||||
if c.VerkleTime != nil {
|
||||
banner += fmt.Sprintf(" - Verkle: @%-10v blob: (%s)\n", *c.VerkleTime, c.BlobScheduleConfig.Verkle)
|
||||
}
|
||||
banner += fmt.Sprintf("\nAll fork specifications can be found at https://ethereum.github.io/execution-specs/src/ethereum/forks/\n")
|
||||
return banner
|
||||
}
|
||||
|
||||
|
|
@ -603,17 +696,26 @@ type BlobConfig struct {
|
|||
UpdateFraction uint64 `json:"baseFeeUpdateFraction"`
|
||||
}
|
||||
|
||||
// String implement fmt.Stringer, returning string format blob config.
|
||||
func (bc *BlobConfig) String() string {
|
||||
if bc == nil {
|
||||
return "nil"
|
||||
}
|
||||
return fmt.Sprintf("target: %d, max: %d, fraction: %d", bc.Target, bc.Max, bc.UpdateFraction)
|
||||
}
|
||||
|
||||
// BlobScheduleConfig determines target and max number of blobs allow per fork.
|
||||
type BlobScheduleConfig struct {
|
||||
Cancun *BlobConfig `json:"cancun,omitempty"`
|
||||
Prague *BlobConfig `json:"prague,omitempty"`
|
||||
Osaka *BlobConfig `json:"osaka,omitempty"`
|
||||
Verkle *BlobConfig `json:"verkle,omitempty"`
|
||||
BPO1 *BlobConfig `json:"bpo1,omitempty"`
|
||||
BPO2 *BlobConfig `json:"bpo2,omitempty"`
|
||||
BPO3 *BlobConfig `json:"bpo3,omitempty"`
|
||||
BPO4 *BlobConfig `json:"bpo4,omitempty"`
|
||||
BPO5 *BlobConfig `json:"bpo5,omitempty"`
|
||||
Cancun *BlobConfig `json:"cancun,omitempty"`
|
||||
Prague *BlobConfig `json:"prague,omitempty"`
|
||||
Osaka *BlobConfig `json:"osaka,omitempty"`
|
||||
Verkle *BlobConfig `json:"verkle,omitempty"`
|
||||
BPO1 *BlobConfig `json:"bpo1,omitempty"`
|
||||
BPO2 *BlobConfig `json:"bpo2,omitempty"`
|
||||
BPO3 *BlobConfig `json:"bpo3,omitempty"`
|
||||
BPO4 *BlobConfig `json:"bpo4,omitempty"`
|
||||
BPO5 *BlobConfig `json:"bpo5,omitempty"`
|
||||
Amsterdam *BlobConfig `json:"amsterdam,omitempty"`
|
||||
}
|
||||
|
||||
// IsHomestead returns whether num is either equal to the homestead block or greater.
|
||||
|
|
@ -726,11 +828,6 @@ func (c *ChainConfig) IsOsaka(num *big.Int, time uint64) bool {
|
|||
return c.IsLondon(num) && isTimestampForked(c.OsakaTime, time)
|
||||
}
|
||||
|
||||
// IsVerkle returns whether time is either equal to the Verkle fork time or greater.
|
||||
func (c *ChainConfig) IsVerkle(num *big.Int, time uint64) bool {
|
||||
return c.IsLondon(num) && isTimestampForked(c.VerkleTime, time)
|
||||
}
|
||||
|
||||
// IsBPO1 returns whether time is either equal to the BPO1 fork time or greater.
|
||||
func (c *ChainConfig) IsBPO1(num *big.Int, time uint64) bool {
|
||||
return c.IsLondon(num) && isTimestampForked(c.BPO1Time, time)
|
||||
|
|
@ -756,6 +853,16 @@ func (c *ChainConfig) IsBPO5(num *big.Int, time uint64) bool {
|
|||
return c.IsLondon(num) && isTimestampForked(c.BPO5Time, time)
|
||||
}
|
||||
|
||||
// IsAmsterdam returns whether time is either equal to the Amsterdam fork time or greater.
|
||||
func (c *ChainConfig) IsAmsterdam(num *big.Int, time uint64) bool {
|
||||
return c.IsLondon(num) && isTimestampForked(c.AmsterdamTime, time)
|
||||
}
|
||||
|
||||
// IsVerkle returns whether time is either equal to the Verkle fork time or greater.
|
||||
func (c *ChainConfig) IsVerkle(num *big.Int, time uint64) bool {
|
||||
return c.IsLondon(num) && isTimestampForked(c.VerkleTime, time)
|
||||
}
|
||||
|
||||
// IsVerkleGenesis checks whether the verkle fork is activated at the genesis block.
|
||||
//
|
||||
// Verkle mode is considered enabled if the verkle fork time is configured,
|
||||
|
|
@ -836,6 +943,7 @@ func (c *ChainConfig) CheckConfigForkOrder() error {
|
|||
{name: "bpo3", timestamp: c.BPO3Time, optional: true},
|
||||
{name: "bpo4", timestamp: c.BPO4Time, optional: true},
|
||||
{name: "bpo5", timestamp: c.BPO5Time, optional: true},
|
||||
{name: "amsterdam", timestamp: c.AmsterdamTime, optional: true},
|
||||
} {
|
||||
if lastFork.name != "" {
|
||||
switch {
|
||||
|
|
@ -890,6 +998,7 @@ func (c *ChainConfig) CheckConfigForkOrder() error {
|
|||
{name: "bpo3", timestamp: c.BPO3Time, config: bsc.BPO3},
|
||||
{name: "bpo4", timestamp: c.BPO4Time, config: bsc.BPO4},
|
||||
{name: "bpo5", timestamp: c.BPO5Time, config: bsc.BPO5},
|
||||
{name: "amsterdam", timestamp: c.AmsterdamTime, config: bsc.Amsterdam},
|
||||
} {
|
||||
if cur.config != nil {
|
||||
if err := cur.config.validate(); err != nil {
|
||||
|
|
@ -1005,6 +1114,9 @@ func (c *ChainConfig) checkCompatible(newcfg *ChainConfig, headNumber *big.Int,
|
|||
if isForkTimestampIncompatible(c.BPO5Time, newcfg.BPO5Time, headTimestamp) {
|
||||
return newTimestampCompatError("BPO5 fork timestamp", c.BPO5Time, newcfg.BPO5Time)
|
||||
}
|
||||
if isForkTimestampIncompatible(c.AmsterdamTime, newcfg.AmsterdamTime, headTimestamp) {
|
||||
return newTimestampCompatError("Amsterdam fork timestamp", c.AmsterdamTime, newcfg.AmsterdamTime)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
@ -1024,6 +1136,8 @@ func (c *ChainConfig) LatestFork(time uint64) forks.Fork {
|
|||
london := c.LondonBlock
|
||||
|
||||
switch {
|
||||
case c.IsAmsterdam(london, time):
|
||||
return forks.Amsterdam
|
||||
case c.IsBPO5(london, time):
|
||||
return forks.BPO5
|
||||
case c.IsBPO4(london, time):
|
||||
|
|
@ -1259,7 +1373,7 @@ type Rules struct {
|
|||
IsByzantium, IsConstantinople, IsPetersburg, IsIstanbul bool
|
||||
IsBerlin, IsLondon bool
|
||||
IsMerge, IsShanghai, IsCancun, IsPrague, IsOsaka bool
|
||||
IsVerkle bool
|
||||
IsAmsterdam, IsVerkle bool
|
||||
}
|
||||
|
||||
// Rules ensures c's ChainID is not nil.
|
||||
|
|
@ -1289,6 +1403,7 @@ func (c *ChainConfig) Rules(num *big.Int, isMerge bool, timestamp uint64) Rules
|
|||
IsCancun: isMerge && c.IsCancun(num, timestamp),
|
||||
IsPrague: isMerge && c.IsPrague(num, timestamp),
|
||||
IsOsaka: isMerge && c.IsOsaka(num, timestamp),
|
||||
IsAmsterdam: isMerge && c.IsAmsterdam(num, timestamp),
|
||||
IsVerkle: isVerkle,
|
||||
IsEIP4762: isVerkle,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -45,6 +45,7 @@ const (
|
|||
BPO3
|
||||
BPO4
|
||||
BPO5
|
||||
Amsterdam
|
||||
)
|
||||
|
||||
// String implements fmt.Stringer.
|
||||
|
|
@ -82,4 +83,5 @@ var forkToString = map[Fork]string{
|
|||
BPO3: "BPO3",
|
||||
BPO4: "BPO4",
|
||||
BPO5: "BPO5",
|
||||
Amsterdam: "Amsterdam",
|
||||
}
|
||||
|
|
|
|||
|
|
@ -273,7 +273,8 @@ func TestServerWebsocketReadLimit(t *testing.T) {
|
|||
}
|
||||
} else if !errors.Is(err, websocket.ErrReadLimit) &&
|
||||
!strings.Contains(strings.ToLower(err.Error()), "1009") &&
|
||||
!strings.Contains(strings.ToLower(err.Error()), "message too big") {
|
||||
!strings.Contains(strings.ToLower(err.Error()), "message too big") &&
|
||||
!strings.Contains(strings.ToLower(err.Error()), "connection reset by peer") {
|
||||
// Not the error we expect from exceeding the message size limit.
|
||||
t.Fatalf("unexpected error for read limit violation: %v", err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,7 +18,6 @@ package core_test
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"os"
|
||||
|
|
@ -97,12 +96,12 @@ func (ui *headlessUi) ApproveNewAccount(request *core.NewAccountRequest) (core.N
|
|||
}
|
||||
|
||||
func (ui *headlessUi) ShowError(message string) {
|
||||
//stdout is used by communication
|
||||
// stdout is used by communication
|
||||
fmt.Fprintln(os.Stderr, message)
|
||||
}
|
||||
|
||||
func (ui *headlessUi) ShowInfo(message string) {
|
||||
//stdout is used by communication
|
||||
// stdout is used by communication
|
||||
fmt.Fprintln(os.Stderr, message)
|
||||
}
|
||||
|
||||
|
|
@ -128,7 +127,7 @@ func setup(t *testing.T) (*core.SignerAPI, *headlessUi) {
|
|||
func createAccount(ui *headlessUi, api *core.SignerAPI, t *testing.T) {
|
||||
ui.approveCh <- "Y"
|
||||
ui.inputCh <- "a_long_password"
|
||||
_, err := api.New(context.Background())
|
||||
_, err := api.New(t.Context())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
|
@ -143,7 +142,7 @@ func failCreateAccountWithPassword(ui *headlessUi, api *core.SignerAPI, password
|
|||
ui.inputCh <- password
|
||||
ui.inputCh <- password
|
||||
|
||||
addr, err := api.New(context.Background())
|
||||
addr, err := api.New(t.Context())
|
||||
if err == nil {
|
||||
t.Fatal("Should have returned an error")
|
||||
}
|
||||
|
|
@ -154,7 +153,7 @@ func failCreateAccountWithPassword(ui *headlessUi, api *core.SignerAPI, password
|
|||
|
||||
func failCreateAccount(ui *headlessUi, api *core.SignerAPI, t *testing.T) {
|
||||
ui.approveCh <- "N"
|
||||
addr, err := api.New(context.Background())
|
||||
addr, err := api.New(t.Context())
|
||||
if err != core.ErrRequestDenied {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
|
@ -165,7 +164,7 @@ func failCreateAccount(ui *headlessUi, api *core.SignerAPI, t *testing.T) {
|
|||
|
||||
func list(ui *headlessUi, api *core.SignerAPI, t *testing.T) ([]common.Address, error) {
|
||||
ui.approveCh <- "A"
|
||||
return api.List(context.Background())
|
||||
return api.List(t.Context())
|
||||
}
|
||||
|
||||
func TestNewAcc(t *testing.T) {
|
||||
|
|
@ -199,7 +198,7 @@ func TestNewAcc(t *testing.T) {
|
|||
// Testing listing:
|
||||
// Listing one Account
|
||||
control.approveCh <- "1"
|
||||
list, err := api.List(context.Background())
|
||||
list, err := api.List(t.Context())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
|
@ -208,7 +207,7 @@ func TestNewAcc(t *testing.T) {
|
|||
}
|
||||
// Listing denied
|
||||
control.approveCh <- "Nope"
|
||||
list, err = api.List(context.Background())
|
||||
list, err = api.List(t.Context())
|
||||
if len(list) != 0 {
|
||||
t.Fatalf("List should be empty")
|
||||
}
|
||||
|
|
@ -246,7 +245,7 @@ func TestSignTx(t *testing.T) {
|
|||
api, control := setup(t)
|
||||
createAccount(control, api, t)
|
||||
control.approveCh <- "A"
|
||||
list, err = api.List(context.Background())
|
||||
list, err = api.List(t.Context())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
|
@ -260,15 +259,15 @@ func TestSignTx(t *testing.T) {
|
|||
|
||||
control.approveCh <- "Y"
|
||||
control.inputCh <- "wrongpassword"
|
||||
res, err = api.SignTransaction(context.Background(), tx, &methodSig)
|
||||
res, err = api.SignTransaction(t.Context(), tx, &methodSig)
|
||||
if res != nil {
|
||||
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(context.Background(), tx, &methodSig)
|
||||
res, err = api.SignTransaction(t.Context(), tx, &methodSig)
|
||||
if res != nil {
|
||||
t.Errorf("Expected nil-response, got %v", res)
|
||||
}
|
||||
|
|
@ -278,22 +277,21 @@ func TestSignTx(t *testing.T) {
|
|||
// Sign with correct password
|
||||
control.approveCh <- "Y"
|
||||
control.inputCh <- "a_long_password"
|
||||
res, err = api.SignTransaction(context.Background(), tx, &methodSig)
|
||||
|
||||
res, err = api.SignTransaction(t.Context(), tx, &methodSig)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
parsedTx := &types.Transaction{}
|
||||
rlp.DecodeBytes(res.Raw, parsedTx)
|
||||
|
||||
//The tx should NOT be modified by the UI
|
||||
// The tx should NOT be modified by the UI
|
||||
if parsedTx.Value().Cmp(tx.Value.ToInt()) != 0 {
|
||||
t.Errorf("Expected value to be unchanged, expected %v got %v", tx.Value, parsedTx.Value())
|
||||
}
|
||||
control.approveCh <- "Y"
|
||||
control.inputCh <- "a_long_password"
|
||||
|
||||
res2, err = api.SignTransaction(context.Background(), tx, &methodSig)
|
||||
res2, err = api.SignTransaction(t.Context(), tx, &methodSig)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
|
@ -301,20 +299,20 @@ func TestSignTx(t *testing.T) {
|
|||
t.Error("Expected tx to be unmodified by UI")
|
||||
}
|
||||
|
||||
//The tx is modified by the UI
|
||||
// The tx is modified by the UI
|
||||
control.approveCh <- "M"
|
||||
control.inputCh <- "a_long_password"
|
||||
|
||||
res2, err = api.SignTransaction(context.Background(), tx, &methodSig)
|
||||
res2, err = api.SignTransaction(t.Context(), tx, &methodSig)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
parsedTx2 := &types.Transaction{}
|
||||
rlp.DecodeBytes(res.Raw, parsedTx2)
|
||||
rlp.DecodeBytes(res2.Raw, parsedTx2)
|
||||
|
||||
//The tx should be modified by the UI
|
||||
if parsedTx2.Value().Cmp(tx.Value.ToInt()) != 0 {
|
||||
t.Errorf("Expected value to be unchanged, got %v", parsedTx.Value())
|
||||
// The tx should be modified by the UI
|
||||
if parsedTx2.Value().Cmp(tx.Value.ToInt()) == 0 {
|
||||
t.Errorf("Expected value to be changed, got %v", parsedTx2.Value())
|
||||
}
|
||||
if bytes.Equal(res.Raw, res2.Raw) {
|
||||
t.Error("Expected tx to be modified by UI")
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -326,7 +326,7 @@ func TestReplication(t *testing.T) {
|
|||
updateString(trie2, val.k, val.v)
|
||||
}
|
||||
if trie2.Hash() != hash {
|
||||
t.Errorf("root failure. expected %x got %x", hash, hash)
|
||||
t.Errorf("root failure. expected %x got %x", hash, trie2.Hash())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -66,7 +66,7 @@ func TestVerkleTreeReadWrite(t *testing.T) {
|
|||
}
|
||||
for key, val := range storages[addr] {
|
||||
if err := tr.UpdateStorage(addr, key.Bytes(), val); err != nil {
|
||||
t.Fatalf("Failed to update account, %v", err)
|
||||
t.Fatalf("Failed to update storage, %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -107,7 +107,7 @@ func TestVerkleRollBack(t *testing.T) {
|
|||
}
|
||||
for key, val := range storages[addr] {
|
||||
if err := tr.UpdateStorage(addr, key.Bytes(), val); err != nil {
|
||||
t.Fatalf("Failed to update account, %v", err)
|
||||
t.Fatalf("Failed to update storage, %v", err)
|
||||
}
|
||||
}
|
||||
hash := crypto.Keccak256Hash(code)
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue