Merge remote-tracking branch 'origin/master' into add_gen5

This commit is contained in:
mmsqe 2026-01-04 22:18:48 +08:00
commit 6977a9867b
No known key found for this signature in database
GPG key ID: 1D6409A9D4025709
147 changed files with 2634 additions and 4095 deletions

View file

@ -8,6 +8,45 @@ jobs:
validate-pr:
runs-on: ubuntu-latest
steps:
- name: Check for Spam PR
uses: actions/github-script@v7
with:
script: |
const prTitle = context.payload.pull_request.title;
const spamRegex = /^(feat|chore|fix)(\(.*\))?\s*:/i;
if (spamRegex.test(prTitle)) {
// Leave a comment explaining why
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.payload.pull_request.number,
body: `## PR Closed as Spam
This PR was automatically closed because the title format \`feat:\`, \`fix:\`, or \`chore:\` is commonly associated with spam contributions.
If this is a legitimate contribution, please:
1. Review our contribution guidelines
2. Use the correct PR title format: \`directory, ...: description\`
3. Open a new PR with the proper title format
Thank you for your understanding.`
});
// Close the PR
await github.rest.pulls.update({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: context.payload.pull_request.number,
state: 'closed'
});
core.setFailed('PR closed as spam due to suspicious title format');
return;
}
console.log('✅ PR passed spam check');
- name: Checkout repository
uses: actions/checkout@v4

View file

@ -21,7 +21,6 @@ Audit reports are published in the `docs` folder: https://github.com/ethereum/go
To find out how to disclose a vulnerability in Ethereum visit [https://bounty.ethereum.org](https://bounty.ethereum.org) or email bounty@ethereum.org. Please read the [disclosure page](https://github.com/ethereum/go-ethereum/security/advisories?state=published) for more information about publicly disclosed security vulnerabilities.
Use the built-in `geth version-check` feature to check whether the software is affected by any known vulnerability. This command will fetch the latest [`vulnerabilities.json`](https://geth.ethereum.org/docs/vulnerabilities/vulnerabilities.json) file which contains known security vulnerabilities concerning `geth`, and cross-check the data against its own version number.
The following key may be used to communicate sensitive information to developers.

View file

@ -17,24 +17,23 @@ var _ = (*executableDataMarshaling)(nil)
// MarshalJSON marshals as JSON.
func (e ExecutableData) MarshalJSON() ([]byte, error) {
type ExecutableData struct {
ParentHash common.Hash `json:"parentHash" gencodec:"required"`
FeeRecipient common.Address `json:"feeRecipient" gencodec:"required"`
StateRoot common.Hash `json:"stateRoot" gencodec:"required"`
ReceiptsRoot common.Hash `json:"receiptsRoot" gencodec:"required"`
LogsBloom hexutil.Bytes `json:"logsBloom" gencodec:"required"`
Random common.Hash `json:"prevRandao" gencodec:"required"`
Number hexutil.Uint64 `json:"blockNumber" gencodec:"required"`
GasLimit hexutil.Uint64 `json:"gasLimit" gencodec:"required"`
GasUsed hexutil.Uint64 `json:"gasUsed" gencodec:"required"`
Timestamp hexutil.Uint64 `json:"timestamp" gencodec:"required"`
ExtraData hexutil.Bytes `json:"extraData" gencodec:"required"`
BaseFeePerGas *hexutil.Big `json:"baseFeePerGas" gencodec:"required"`
BlockHash common.Hash `json:"blockHash" gencodec:"required"`
Transactions []hexutil.Bytes `json:"transactions" gencodec:"required"`
Withdrawals []*types.Withdrawal `json:"withdrawals"`
BlobGasUsed *hexutil.Uint64 `json:"blobGasUsed"`
ExcessBlobGas *hexutil.Uint64 `json:"excessBlobGas"`
ExecutionWitness *types.ExecutionWitness `json:"executionWitness,omitempty"`
ParentHash common.Hash `json:"parentHash" gencodec:"required"`
FeeRecipient common.Address `json:"feeRecipient" gencodec:"required"`
StateRoot common.Hash `json:"stateRoot" gencodec:"required"`
ReceiptsRoot common.Hash `json:"receiptsRoot" gencodec:"required"`
LogsBloom hexutil.Bytes `json:"logsBloom" gencodec:"required"`
Random common.Hash `json:"prevRandao" gencodec:"required"`
Number hexutil.Uint64 `json:"blockNumber" gencodec:"required"`
GasLimit hexutil.Uint64 `json:"gasLimit" gencodec:"required"`
GasUsed hexutil.Uint64 `json:"gasUsed" gencodec:"required"`
Timestamp hexutil.Uint64 `json:"timestamp" gencodec:"required"`
ExtraData hexutil.Bytes `json:"extraData" gencodec:"required"`
BaseFeePerGas *hexutil.Big `json:"baseFeePerGas" gencodec:"required"`
BlockHash common.Hash `json:"blockHash" gencodec:"required"`
Transactions []hexutil.Bytes `json:"transactions" gencodec:"required"`
Withdrawals []*types.Withdrawal `json:"withdrawals"`
BlobGasUsed *hexutil.Uint64 `json:"blobGasUsed"`
ExcessBlobGas *hexutil.Uint64 `json:"excessBlobGas"`
}
var enc ExecutableData
enc.ParentHash = e.ParentHash
@ -59,31 +58,29 @@ func (e ExecutableData) MarshalJSON() ([]byte, error) {
enc.Withdrawals = e.Withdrawals
enc.BlobGasUsed = (*hexutil.Uint64)(e.BlobGasUsed)
enc.ExcessBlobGas = (*hexutil.Uint64)(e.ExcessBlobGas)
enc.ExecutionWitness = e.ExecutionWitness
return json.Marshal(&enc)
}
// UnmarshalJSON unmarshals from JSON.
func (e *ExecutableData) UnmarshalJSON(input []byte) error {
type ExecutableData struct {
ParentHash *common.Hash `json:"parentHash" gencodec:"required"`
FeeRecipient *common.Address `json:"feeRecipient" gencodec:"required"`
StateRoot *common.Hash `json:"stateRoot" gencodec:"required"`
ReceiptsRoot *common.Hash `json:"receiptsRoot" gencodec:"required"`
LogsBloom *hexutil.Bytes `json:"logsBloom" gencodec:"required"`
Random *common.Hash `json:"prevRandao" gencodec:"required"`
Number *hexutil.Uint64 `json:"blockNumber" gencodec:"required"`
GasLimit *hexutil.Uint64 `json:"gasLimit" gencodec:"required"`
GasUsed *hexutil.Uint64 `json:"gasUsed" gencodec:"required"`
Timestamp *hexutil.Uint64 `json:"timestamp" gencodec:"required"`
ExtraData *hexutil.Bytes `json:"extraData" gencodec:"required"`
BaseFeePerGas *hexutil.Big `json:"baseFeePerGas" gencodec:"required"`
BlockHash *common.Hash `json:"blockHash" gencodec:"required"`
Transactions []hexutil.Bytes `json:"transactions" gencodec:"required"`
Withdrawals []*types.Withdrawal `json:"withdrawals"`
BlobGasUsed *hexutil.Uint64 `json:"blobGasUsed"`
ExcessBlobGas *hexutil.Uint64 `json:"excessBlobGas"`
ExecutionWitness *types.ExecutionWitness `json:"executionWitness,omitempty"`
ParentHash *common.Hash `json:"parentHash" gencodec:"required"`
FeeRecipient *common.Address `json:"feeRecipient" gencodec:"required"`
StateRoot *common.Hash `json:"stateRoot" gencodec:"required"`
ReceiptsRoot *common.Hash `json:"receiptsRoot" gencodec:"required"`
LogsBloom *hexutil.Bytes `json:"logsBloom" gencodec:"required"`
Random *common.Hash `json:"prevRandao" gencodec:"required"`
Number *hexutil.Uint64 `json:"blockNumber" gencodec:"required"`
GasLimit *hexutil.Uint64 `json:"gasLimit" gencodec:"required"`
GasUsed *hexutil.Uint64 `json:"gasUsed" gencodec:"required"`
Timestamp *hexutil.Uint64 `json:"timestamp" gencodec:"required"`
ExtraData *hexutil.Bytes `json:"extraData" gencodec:"required"`
BaseFeePerGas *hexutil.Big `json:"baseFeePerGas" gencodec:"required"`
BlockHash *common.Hash `json:"blockHash" gencodec:"required"`
Transactions []hexutil.Bytes `json:"transactions" gencodec:"required"`
Withdrawals []*types.Withdrawal `json:"withdrawals"`
BlobGasUsed *hexutil.Uint64 `json:"blobGasUsed"`
ExcessBlobGas *hexutil.Uint64 `json:"excessBlobGas"`
}
var dec ExecutableData
if err := json.Unmarshal(input, &dec); err != nil {
@ -157,8 +154,5 @@ func (e *ExecutableData) UnmarshalJSON(input []byte) error {
if dec.ExcessBlobGas != nil {
e.ExcessBlobGas = (*uint64)(dec.ExcessBlobGas)
}
if dec.ExecutionWitness != nil {
e.ExecutionWitness = dec.ExecutionWitness
}
return nil
}

View file

@ -73,24 +73,23 @@ type payloadAttributesMarshaling struct {
// ExecutableData is the data necessary to execute an EL payload.
type ExecutableData struct {
ParentHash common.Hash `json:"parentHash" gencodec:"required"`
FeeRecipient common.Address `json:"feeRecipient" gencodec:"required"`
StateRoot common.Hash `json:"stateRoot" gencodec:"required"`
ReceiptsRoot common.Hash `json:"receiptsRoot" gencodec:"required"`
LogsBloom []byte `json:"logsBloom" gencodec:"required"`
Random common.Hash `json:"prevRandao" gencodec:"required"`
Number uint64 `json:"blockNumber" gencodec:"required"`
GasLimit uint64 `json:"gasLimit" gencodec:"required"`
GasUsed uint64 `json:"gasUsed" gencodec:"required"`
Timestamp uint64 `json:"timestamp" gencodec:"required"`
ExtraData []byte `json:"extraData" gencodec:"required"`
BaseFeePerGas *big.Int `json:"baseFeePerGas" gencodec:"required"`
BlockHash common.Hash `json:"blockHash" gencodec:"required"`
Transactions [][]byte `json:"transactions" gencodec:"required"`
Withdrawals []*types.Withdrawal `json:"withdrawals"`
BlobGasUsed *uint64 `json:"blobGasUsed"`
ExcessBlobGas *uint64 `json:"excessBlobGas"`
ExecutionWitness *types.ExecutionWitness `json:"executionWitness,omitempty"`
ParentHash common.Hash `json:"parentHash" gencodec:"required"`
FeeRecipient common.Address `json:"feeRecipient" gencodec:"required"`
StateRoot common.Hash `json:"stateRoot" gencodec:"required"`
ReceiptsRoot common.Hash `json:"receiptsRoot" gencodec:"required"`
LogsBloom []byte `json:"logsBloom" gencodec:"required"`
Random common.Hash `json:"prevRandao" gencodec:"required"`
Number uint64 `json:"blockNumber" gencodec:"required"`
GasLimit uint64 `json:"gasLimit" gencodec:"required"`
GasUsed uint64 `json:"gasUsed" gencodec:"required"`
Timestamp uint64 `json:"timestamp" gencodec:"required"`
ExtraData []byte `json:"extraData" gencodec:"required"`
BaseFeePerGas *big.Int `json:"baseFeePerGas" gencodec:"required"`
BlockHash common.Hash `json:"blockHash" gencodec:"required"`
Transactions [][]byte `json:"transactions" gencodec:"required"`
Withdrawals []*types.Withdrawal `json:"withdrawals"`
BlobGasUsed *uint64 `json:"blobGasUsed"`
ExcessBlobGas *uint64 `json:"excessBlobGas"`
}
// JSON type overrides for executableData.
@ -316,8 +315,7 @@ func ExecutableDataToBlockNoHash(data ExecutableData, versionedHashes []common.H
RequestsHash: requestsHash,
}
return types.NewBlockWithHeader(header).
WithBody(types.Body{Transactions: txs, Uncles: nil, Withdrawals: data.Withdrawals}).
WithWitness(data.ExecutionWitness),
WithBody(types.Body{Transactions: txs, Uncles: nil, Withdrawals: data.Withdrawals}),
nil
}
@ -325,24 +323,23 @@ func ExecutableDataToBlockNoHash(data ExecutableData, versionedHashes []common.H
// fields from the given block. It assumes the given block is post-merge block.
func BlockToExecutableData(block *types.Block, fees *big.Int, sidecars []*types.BlobTxSidecar, requests [][]byte) *ExecutionPayloadEnvelope {
data := &ExecutableData{
BlockHash: block.Hash(),
ParentHash: block.ParentHash(),
FeeRecipient: block.Coinbase(),
StateRoot: block.Root(),
Number: block.NumberU64(),
GasLimit: block.GasLimit(),
GasUsed: block.GasUsed(),
BaseFeePerGas: block.BaseFee(),
Timestamp: block.Time(),
ReceiptsRoot: block.ReceiptHash(),
LogsBloom: block.Bloom().Bytes(),
Transactions: encodeTransactions(block.Transactions()),
Random: block.MixDigest(),
ExtraData: block.Extra(),
Withdrawals: block.Withdrawals(),
BlobGasUsed: block.BlobGasUsed(),
ExcessBlobGas: block.ExcessBlobGas(),
ExecutionWitness: block.ExecutionWitness(),
BlockHash: block.Hash(),
ParentHash: block.ParentHash(),
FeeRecipient: block.Coinbase(),
StateRoot: block.Root(),
Number: block.NumberU64(),
GasLimit: block.GasLimit(),
GasUsed: block.GasUsed(),
BaseFeePerGas: block.BaseFee(),
Timestamp: block.Time(),
ReceiptsRoot: block.ReceiptHash(),
LogsBloom: block.Bloom().Bytes(),
Transactions: encodeTransactions(block.Transactions()),
Random: block.MixDigest(),
ExtraData: block.Extra(),
Withdrawals: block.Withdrawals(),
BlobGasUsed: block.BlobGasUsed(),
ExcessBlobGas: block.ExcessBlobGas(),
}
// Add blobs.

View file

@ -105,6 +105,7 @@ func (s *HeadSync) Process(requester request.Requester, events []request.Event)
delete(s.serverHeads, event.Server)
delete(s.unvalidatedOptimistic, event.Server)
delete(s.unvalidatedFinality, event.Server)
delete(s.reqFinalityEpoch, event.Server)
}
}
}

View file

@ -52,7 +52,7 @@ func BlockFromJSON(forkName string, data []byte) (*BeaconBlock, error) {
obj = new(capella.BeaconBlock)
case "deneb":
obj = new(deneb.BeaconBlock)
case "electra":
case "electra", "fulu":
obj = new(electra.BeaconBlock)
default:
return nil, fmt.Errorf("unsupported fork: %s", forkName)

View file

@ -45,7 +45,7 @@ func ExecutionHeaderFromJSON(forkName string, data []byte) (*ExecutionHeader, er
switch forkName {
case "capella":
obj = new(capella.ExecutionPayloadHeader)
case "deneb", "electra": // note: the payload type was not changed in electra
case "deneb", "electra", "fulu": // note: the payload type was not changed in electra/fulu
obj = new(deneb.ExecutionPayloadHeader)
default:
return nil, fmt.Errorf("unsupported fork: %s", forkName)

View file

@ -343,7 +343,7 @@ func buildFlags(env build.Environment, staticLinking bool, buildTags []string) (
}
ld = append(ld, "-extldflags", "'"+strings.Join(extld, " ")+"'")
}
// TODO(gballet): revisit after the input api has been defined
// TODO(gballet): revisit after the input api has been defined
if runtime.GOARCH == "wasm" {
ld = append(ld, "-gcflags=all=-d=softfloat")
}
@ -462,9 +462,14 @@ func doCheckGenerate() {
)
pathList := []string{filepath.Join(protocPath, "bin"), protocGenGoPath, os.Getenv("PATH")}
excludes := []string{"tests/testdata", "build/cache", ".git"}
for i := range excludes {
excludes[i] = filepath.FromSlash(excludes[i])
}
for _, mod := range goModules {
// Compute the origin hashes of all the files
hashes, err := build.HashFolder(mod, []string{"tests/testdata", "build/cache", ".git"})
hashes, err := build.HashFolder(mod, excludes)
if err != nil {
log.Fatal("Error computing hashes", "err", err)
}
@ -474,7 +479,7 @@ func doCheckGenerate() {
c.Dir = mod
build.MustRun(c)
// Check if generate file hashes have changed
generated, err := build.HashFolder(mod, []string{"tests/testdata", "build/cache", ".git"})
generated, err := build.HashFolder(mod, excludes)
if err != nil {
log.Fatalf("Error re-computing hashes: %v", err)
}

View file

@ -17,16 +17,18 @@
package main
import (
"bufio"
"encoding/json"
"errors"
"fmt"
"maps"
"os"
"regexp"
"slices"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/tests"
"github.com/urfave/cli/v2"
)
@ -34,33 +36,52 @@ import (
var blockTestCommand = &cli.Command{
Action: blockTestCmd,
Name: "blocktest",
Usage: "Executes the given blockchain tests",
Usage: "Executes the given blockchain tests. Filenames can be fed via standard input (batch mode) or as an argument (one-off execution).",
ArgsUsage: "<path>",
Flags: slices.Concat([]cli.Flag{
DumpFlag,
HumanReadableFlag,
RunFlag,
WitnessCrossCheckFlag,
FuzzFlag,
}, traceFlags),
}
func blockTestCmd(ctx *cli.Context) error {
path := ctx.Args().First()
if len(path) == 0 {
return errors.New("path argument required")
// If path is provided, run the tests at that path.
if len(path) != 0 {
var (
collected = collectFiles(path)
results []testResult
)
for _, fname := range collected {
r, err := runBlockTest(ctx, fname)
if err != nil {
return err
}
results = append(results, r...)
}
report(ctx, results)
return nil
}
var (
collected = collectFiles(path)
results []testResult
)
for _, fname := range collected {
r, err := runBlockTest(ctx, fname)
// Otherwise, read filenames from stdin and execute back-to-back.
scanner := bufio.NewScanner(os.Stdin)
for scanner.Scan() {
fname := scanner.Text()
if len(fname) == 0 {
return nil
}
results, err := runBlockTest(ctx, fname)
if err != nil {
return err
}
results = append(results, r...)
// During fuzzing, we report the result after every block
if !ctx.IsSet(FuzzFlag.Name) {
report(ctx, results)
}
}
report(ctx, results)
return nil
}
@ -79,6 +100,11 @@ func runBlockTest(ctx *cli.Context, fname string) ([]testResult, error) {
}
tracer := tracerFromFlags(ctx)
// Suppress INFO logs during fuzzing
if ctx.IsSet(FuzzFlag.Name) {
log.SetDefault(log.NewLogger(log.DiscardHandler()))
}
// Pull out keys to sort and ensure tests are run in order.
keys := slices.Sorted(maps.Keys(tests))
@ -88,16 +114,35 @@ func runBlockTest(ctx *cli.Context, fname string) ([]testResult, error) {
if !re.MatchString(name) {
continue
}
test := tests[name]
result := &testResult{Name: name, Pass: true}
if err := tests[name].Run(false, rawdb.PathScheme, ctx.Bool(WitnessCrossCheckFlag.Name), tracer, func(res error, chain *core.BlockChain) {
var finalRoot *common.Hash
if err := test.Run(false, rawdb.PathScheme, ctx.Bool(WitnessCrossCheckFlag.Name), tracer, func(res error, chain *core.BlockChain) {
if ctx.Bool(DumpFlag.Name) {
if s, _ := chain.State(); s != nil {
result.State = dump(s)
}
}
// Capture final state root for end marker
if chain != nil {
root := chain.CurrentBlock().Root
finalRoot = &root
}
}); err != nil {
result.Pass, result.Error = false, err.Error()
}
// Always assign fork (regardless of pass/fail or tracer)
result.Fork = test.Network()
// Assign root if test succeeded
if result.Pass && finalRoot != nil {
result.Root = finalRoot
}
// When fuzzing, write results after every block
if ctx.IsSet(FuzzFlag.Name) {
report(ctx, []testResult{*result})
}
results = append(results, *result)
}
return results, nil

View file

@ -55,6 +55,11 @@ var (
Usage: "benchmark the execution",
Category: flags.VMCategory,
}
FuzzFlag = &cli.BoolFlag{
Name: "fuzz",
Usage: "adapts output format for fuzzing",
Category: flags.VMCategory,
}
WitnessCrossCheckFlag = &cli.BoolFlag{
Name: "cross-check",
Aliases: []string{"xc"},

View file

@ -96,6 +96,7 @@ if one is set. Otherwise it prints the genesis from the datadir.`,
utils.CacheNoPrefetchFlag,
utils.CachePreimagesFlag,
utils.NoCompactionFlag,
utils.LogSlowBlockFlag,
utils.MetricsEnabledFlag,
utils.MetricsEnabledExpensiveFlag,
utils.MetricsHTTPFlag,

View file

@ -39,8 +39,9 @@ const (
// child g gets a temporary data directory.
func runMinimalGeth(t *testing.T, args ...string) *testgeth {
// --holesky to make the 'writing genesis to disk' faster (no accounts)
// --networkid=1337 to avoid cache bump
// --syncmode=full to avoid allocating fast sync bloom
allArgs := []string{"--holesky", "--authrpc.port", "0", "--syncmode=full", "--port", "0",
allArgs := []string{"--holesky", "--networkid", "1337", "--authrpc.port", "0", "--syncmode=full", "--port", "0",
"--nat", "none", "--nodiscover", "--maxpeers", "0", "--cache", "64",
"--datadir.minfreedisk", "0"}
return runGeth(t, append(allArgs, args...)...)

View file

@ -157,6 +157,7 @@ var (
utils.BeaconGenesisTimeFlag,
utils.BeaconCheckpointFlag,
utils.BeaconCheckpointFileFlag,
utils.LogSlowBlockFlag,
}, utils.NetworkFlags, utils.DatabaseFlags)
rpcFlags = []cli.Flag{
@ -240,7 +241,6 @@ func init() {
javascriptCommand,
// See misccmd.go:
versionCommand,
versionCheckCommand,
licenseCommand,
// See config.go
dumpConfigCommand,
@ -250,8 +250,6 @@ func init() {
utils.ShowDeprecated,
// See snapshot.go
snapshotCommand,
// See verkle.go
verkleCommand,
}
if logTestCommand != nil {
app.Commands = append(app.Commands, logTestCommand)

View file

@ -27,16 +27,6 @@ import (
)
var (
VersionCheckUrlFlag = &cli.StringFlag{
Name: "check.url",
Usage: "URL to use when checking vulnerabilities",
Value: "https://geth.ethereum.org/docs/vulnerabilities/vulnerabilities.json",
}
VersionCheckVersionFlag = &cli.StringFlag{
Name: "check.version",
Usage: "Version to check",
Value: version.ClientName(clientIdentifier),
}
versionCommand = &cli.Command{
Action: printVersion,
Name: "version",
@ -44,20 +34,6 @@ var (
ArgsUsage: " ",
Description: `
The output of this command is supposed to be machine-readable.
`,
}
versionCheckCommand = &cli.Command{
Action: versionCheck,
Flags: []cli.Flag{
VersionCheckUrlFlag,
VersionCheckVersionFlag,
},
Name: "version-check",
Usage: "Checks (online) for known Geth security vulnerabilities",
ArgsUsage: "<versionstring (optional)>",
Description: `
The version-check command fetches vulnerability-information from https://geth.ethereum.org/docs/vulnerabilities/vulnerabilities.json,
and displays information about any security vulnerabilities that affect the currently executing version.
`,
}
licenseCommand = &cli.Command{

View file

@ -1,202 +0,0 @@
[
{
"name": "CorruptedDAG",
"uid": "GETH-2020-01",
"summary": "Mining nodes will generate erroneous PoW on epochs > `385`.",
"description": "A mining flaw could cause miners to erroneously calculate PoW, due to an index overflow, if DAG size is exceeding the maximum 32 bit unsigned value.\n\nThis occurred on the ETC chain on 2020-11-06. This is likely to trigger for ETH mainnet around block `11550000`/epoch `385`, slated to occur early January 2021.\n\nThis issue is relevant only for miners, non-mining nodes are unaffected, since non-mining nodes use a smaller verification cache instead of a full DAG.",
"links": [
"https://github.com/ethereum/go-ethereum/pull/21793",
"https://blog.ethereum.org/2020/11/12/geth-security-release",
"https://github.com/ethereum/go-ethereum/commit/567d41d9363706b4b13ce0903804e8acf214af49",
"https://github.com/ethereum/go-ethereum/security/advisories/GHSA-v592-xf75-856p"
],
"introduced": "v1.6.0",
"fixed": "v1.9.24",
"published": "2020-11-12",
"severity": "Medium",
"CVE": "CVE-2020-26240",
"check": "Geth\\/v1\\.(6|7|8)\\..*|Geth\\/v1\\.9\\.\\d-.*|Geth\\/v1\\.9\\.1.*|Geth\\/v1\\.9\\.2(0|1|2|3)-.*"
},
{
"name": "Denial of service due to Go CVE-2020-28362",
"uid": "GETH-2020-02",
"summary": "A denial-of-service issue can be used to crash Geth nodes during block processing, due to an underlying bug in Go (CVE-2020-28362) versions < `1.15.5`, or `<1.14.12`",
"description": "The DoS issue can be used to crash all Geth nodes during block processing, the effects of which would be that a major part of the Ethereum network went offline.\n\nOutside of Go-Ethereum, the issue is most likely relevant for all forks of Geth (such as TurboGeth or ETCs core-geth) which is built with versions of Go which contains the vulnerability.",
"links": [
"https://blog.ethereum.org/2020/11/12/geth-security-release",
"https://groups.google.com/g/golang-announce/c/NpBGTTmKzpM",
"https://github.com/golang/go/issues/42552",
"https://github.com/ethereum/go-ethereum/security/advisories/GHSA-m6gx-rhvj-fh52"
],
"introduced": "v0.0.0",
"fixed": "v1.9.24",
"published": "2020-11-12",
"severity": "Critical",
"CVE": "CVE-2020-28362",
"check": "Geth.*\\/go1\\.(11(.*)|12(.*)|13(.*)|14|14\\.(\\d|10|11|)|15|15\\.[0-4])$"
},
{
"name": "ShallowCopy",
"uid": "GETH-2020-03",
"summary": "A consensus flaw in Geth, related to `datacopy` precompile",
"description": "Geth erroneously performed a 'shallow' copy when the precompiled `datacopy` (at `0x00...04`) was invoked. An attacker could deploy a contract that uses the shallow copy to corrupt the contents of the `RETURNDATA`, thus causing a consensus failure.",
"links": [
"https://blog.ethereum.org/2020/11/12/geth-security-release",
"https://github.com/ethereum/go-ethereum/security/advisories/GHSA-69v6-xc2j-r2jf"
],
"introduced": "v1.9.7",
"fixed": "v1.9.17",
"published": "2020-11-12",
"severity": "Critical",
"CVE": "CVE-2020-26241",
"check": "Geth\\/v1\\.9\\.(7|8|9|10|11|12|13|14|15|16).*$"
},
{
"name": "Geth DoS via MULMOD",
"uid": "GETH-2020-04",
"summary": "A denial-of-service issue can be used to crash Geth nodes during block processing",
"description": "Affected versions suffer from a vulnerability which can be exploited through the `MULMOD` operation, by specifying a modulo of `0`: `mulmod(a,b,0)`, causing a `panic` in the underlying library. \nThe crash was in the `uint256` library, where a buffer [underflowed](https://github.com/holiman/uint256/blob/4ce82e695c10ddad57215bdbeafb68b8c5df2c30/uint256.go#L442).\n\n\tif `d == 0`, `dLen` remains `0`\n\nand https://github.com/holiman/uint256/blob/4ce82e695c10ddad57215bdbeafb68b8c5df2c30/uint256.go#L451 will try to access index `[-1]`.\n\nThe `uint256` library was first merged in this [commit](https://github.com/ethereum/go-ethereum/commit/cf6674539c589f80031f3371a71c6a80addbe454), on 2020-06-08. \nExploiting this vulnerabilty would cause all vulnerable nodes to drop off the network. \n\nThe issue was brought to our attention through a [bug report](https://github.com/ethereum/go-ethereum/issues/21367), showing a `panic` occurring on sync from genesis on the Ropsten network.\n \nIt was estimated that the least obvious way to fix this would be to merge the fix into `uint256`, make a new release of that library and then update the geth-dependency.\n",
"links": [
"https://blog.ethereum.org/2020/11/12/geth-security-release",
"https://github.com/ethereum/go-ethereum/security/advisories/GHSA-jm5c-rv3w-w83m",
"https://github.com/holiman/uint256/releases/tag/v1.1.1",
"https://github.com/holiman/uint256/pull/80",
"https://github.com/ethereum/go-ethereum/pull/21368"
],
"introduced": "v1.9.16",
"fixed": "v1.9.18",
"published": "2020-11-12",
"severity": "Critical",
"CVE": "CVE-2020-26242",
"check": "Geth\\/v1\\.9.(16|17).*$"
},
{
"name": "LES Server DoS via GetProofsV2",
"uid": "GETH-2020-05",
"summary": "A DoS vulnerability can make a LES server crash.",
"description": "A DoS vulnerability can make a LES server crash via malicious GetProofsV2 request from a connected LES client.\n\nThe vulnerability was patched in #21896.\n\nThis vulnerability only concern users explicitly running geth as a light server",
"links": [
"https://github.com/ethereum/go-ethereum/security/advisories/GHSA-r33q-22hv-j29q",
"https://github.com/ethereum/go-ethereum/pull/21896"
],
"introduced": "v1.8.0",
"fixed": "v1.9.25",
"published": "2020-12-10",
"severity": "Medium",
"CVE": "CVE-2020-26264",
"check": "(Geth\\/v1\\.8\\.*)|(Geth\\/v1\\.9\\.\\d-.*)|(Geth\\/v1\\.9\\.1\\d-.*)|(Geth\\/v1\\.9\\.(20|21|22|23|24)-.*)$"
},
{
"name": "SELFDESTRUCT-recreate consensus flaw",
"uid": "GETH-2020-06",
"introduced": "v1.9.4",
"fixed": "v1.9.20",
"summary": "A consensus-vulnerability in Geth could cause a chain split, where vulnerable versions refuse to accept the canonical chain.",
"description": "A flaw was repoted at 2020-08-11 by John Youngseok Yang (Software Platform Lab), where a particular sequence of transactions could cause a consensus failure.\n\n- Tx 1:\n - `sender` invokes `caller`.\n - `caller` invokes `0xaa`. `0xaa` has 3 wei, does a self-destruct-to-self\n - `caller` does a `1 wei` -call to `0xaa`, who thereby has 1 wei (the code in `0xaa` still executed, since the tx is still ongoing, but doesn't redo the selfdestruct, it takes a different path if callvalue is non-zero)\n\n-Tx 2:\n - `sender` does a 5-wei call to 0xaa. No exec (since no code). \n\nIn geth, the result would be that `0xaa` had `6 wei`, whereas OE reported (correctly) `5` wei. Furthermore, in geth, if the second tx was not executed, the `0xaa` would be destructed, resulting in `0 wei`. Thus obviously wrong. \n\nIt was determined that the root cause was this [commit](https://github.com/ethereum/go-ethereum/commit/223b950944f494a5b4e0957fd9f92c48b09037ad) from [this PR](https://github.com/ethereum/go-ethereum/pull/19953). The semantics of `createObject` was subtly changd, into returning a non-nil object (with `deleted=true`) where it previously did not if the account had been destructed. This return value caused the new object to inherit the old `balance`.\n",
"links": [
"https://github.com/ethereum/go-ethereum/security/advisories/GHSA-xw37-57qp-9mm4"
],
"published": "2020-12-10",
"severity": "High",
"CVE": "CVE-2020-26265",
"check": "(Geth\\/v1\\.9\\.(4|5|6|7|8|9)-.*)|(Geth\\/v1\\.9\\.1\\d-.*)$"
},
{
"name": "Not ready for London upgrade",
"uid": "GETH-2021-01",
"summary": "The client is not ready for the 'London' technical upgrade, and will deviate from the canonical chain when the London upgrade occurs (at block '12965000' around August 4, 2021.",
"description": "At (or around) August 4, Ethereum will undergo a technical upgrade called 'London'. Clients not upgraded will fail to progress on the canonical chain.",
"links": [
"https://github.com/ethereum/eth1.0-specs/blob/master/network-upgrades/mainnet-upgrades/london.md",
"https://notes.ethereum.org/@timbeiko/ropsten-postmortem"
],
"introduced": "v1.10.1",
"fixed": "v1.10.6",
"published": "2021-07-22",
"severity": "High",
"check": "(Geth\\/v1\\.10\\.(1|2|3|4|5)-.*)$"
},
{
"name": "RETURNDATA corruption via datacopy",
"uid": "GETH-2021-02",
"summary": "A consensus-flaw in the Geth EVM could cause a node to deviate from the canonical chain.",
"description": "A memory-corruption bug within the EVM can cause a consensus error, where vulnerable nodes obtain a different `stateRoot` when processing a maliciously crafted transaction. This, in turn, would lead to the chain being split: mainnet splitting in two forks.\n\nAll Geth versions supporting the London hard fork are vulnerable (the bug is older than London), so all users should update.\n\nThis bug was exploited on Mainnet at block 13107518.\n\nCredits for the discovery go to @guidovranken (working for Sentnl during an audit of the Telos EVM) and reported via bounty@ethereum.org.",
"links": [
"https://github.com/ethereum/go-ethereum/blob/master/docs/postmortems/2021-08-22-split-postmortem.md",
"https://github.com/ethereum/go-ethereum/security/advisories/GHSA-9856-9gg9-qcmq",
"https://github.com/ethereum/go-ethereum/releases/tag/v1.10.8"
],
"introduced": "v1.10.0",
"fixed": "v1.10.8",
"published": "2021-08-24",
"severity": "High",
"CVE": "CVE-2021-39137",
"check": "(Geth\\/v1\\.10\\.(0|1|2|3|4|5|6|7)-.*)$"
},
{
"name": "DoS via malicious `snap/1` request",
"uid": "GETH-2021-03",
"summary": "A vulnerable node is susceptible to crash when processing a maliciously crafted message from a peer, via the snap/1 protocol. The crash can be triggered by sending a malicious snap/1 GetTrieNodes package.",
"description": "The `snap/1` protocol handler contains two vulnerabilities related to the `GetTrieNodes` packet, which can be exploited to crash the node. Full details are available at the Github security [advisory](https://github.com/ethereum/go-ethereum/security/advisories/GHSA-59hh-656j-3p7v)",
"links": [
"https://github.com/ethereum/go-ethereum/security/advisories/GHSA-59hh-656j-3p7v",
"https://geth.ethereum.org/docs/vulnerabilities/vulnerabilities",
"https://github.com/ethereum/go-ethereum/pull/23657"
],
"introduced": "v1.10.0",
"fixed": "v1.10.9",
"published": "2021-10-24",
"severity": "Medium",
"CVE": "CVE-2021-41173",
"check": "(Geth\\/v1\\.10\\.(0|1|2|3|4|5|6|7|8)-.*)$"
},
{
"name": "DoS via malicious p2p message",
"uid": "GETH-2022-01",
"summary": "A vulnerable node can crash via p2p messages sent from an attacker node, if running with non-default log options.",
"description": "A vulnerable node, if configured to use high verbosity logging, can be made to crash when handling specially crafted p2p messages sent from an attacker node. Full details are available at the Github security [advisory](https://github.com/ethereum/go-ethereum/security/advisories/GHSA-wjxw-gh3m-7pm5)",
"links": [
"https://github.com/ethereum/go-ethereum/security/advisories/GHSA-wjxw-gh3m-7pm5",
"https://geth.ethereum.org/docs/vulnerabilities/vulnerabilities",
"https://github.com/ethereum/go-ethereum/pull/24507"
],
"introduced": "v1.10.0",
"fixed": "v1.10.17",
"published": "2022-05-11",
"severity": "Low",
"CVE": "CVE-2022-29177",
"check": "(Geth\\/v1\\.10\\.(0|1|2|3|4|5|6|7|8|9|10|11|12|13|14|15|16)-.*)$"
},
{
"name": "DoS via malicious p2p message",
"uid": "GETH-2023-01",
"summary": "A vulnerable node can be made to consume unbounded amounts of memory when handling specially crafted p2p messages sent from an attacker node.",
"description": "The p2p handler spawned a new goroutine to respond to ping requests. By flooding a node with ping requests, an unbounded number of goroutines can be created, leading to resource exhaustion and potentially crash due to OOM.",
"links": [
"https://github.com/ethereum/go-ethereum/security/advisories/GHSA-ppjg-v974-84cm",
"https://geth.ethereum.org/docs/vulnerabilities/vulnerabilities"
],
"introduced": "v1.10.0",
"fixed": "v1.12.1",
"published": "2023-09-06",
"severity": "High",
"CVE": "CVE-2023-40591",
"check": "(Geth\\/v1\\.(10|11)\\..*)|(Geth\\/v1\\.12\\.0-.*)$"
},
{
"name": "DoS via malicious p2p message",
"uid": "GETH-2024-01",
"summary": "A vulnerable node can be made to consume very large amounts of memory when handling specially crafted p2p messages sent from an attacker node.",
"description": "A vulnerable node can be made to consume very large amounts of memory when handling specially crafted p2p messages sent from an attacker node. Full details will be available at the Github security [advisory](https://github.com/ethereum/go-ethereum/security/advisories/GHSA-4xc9-8hmq-j652)",
"links": [
"https://github.com/ethereum/go-ethereum/security/advisories/GHSA-4xc9-8hmq-j652",
"https://geth.ethereum.org/docs/vulnerabilities/vulnerabilities"
],
"introduced": "v1.10.0",
"fixed": "v1.13.15",
"published": "2024-05-06",
"severity": "High",
"CVE": "CVE-2024-32972",
"check": "(Geth\\/v1\\.(10|11|12)\\..*)|(Geth\\/v1\\.13\\.\\d-.*)|(Geth\\/v1\\.13\\.1(0|1|2|3|4)-.*)$"
}
]

View file

@ -1,4 +0,0 @@
untrusted comment: signature from minisign secret key
RUQkliYstQBOKHklFEYCUjepz81dyUuDmIAxjAvXa+icjGuKcjtVfV06G7qfOMSpplS5EcntU12n+AnGNyuOM8zIctaIWcfG2w0=
trusted comment: timestamp:1752094689 file:data.json hashed
u2e4wo4HBTU6viQTSY/NVBHoWoPFJnnTvLZS0FYl3JdvSOYi6+qpbEsDhAIFqq/n8VmlS/fPqqf7vKCNiAgjAA==

View file

@ -1,4 +0,0 @@
untrusted comment: signature from minisign secret key
RWQkliYstQBOKNoyq2O98hPmeVJQ6ShQLM58+4n0gkY0y0trFMDAsHuN/l4IyHfh8dDQ1ry0+IuZVrf/i8M/P3YFzFfAymDYCQ0=
trusted comment: timestamp:1752094703 file:data.json
cNyq3ZGlqo785HtWODb9ejWqF0HhSeXuLGXzC7z1IhnDrBObWBJngYd3qBG1dQcYlHQ+bgB/On5mSyMFn4UoCQ==

View file

@ -1,4 +0,0 @@
untrusted comment: Here's a comment
RWQkliYstQBOKNoyq2O98hPmeVJQ6ShQLM58+4n0gkY0y0trFMDAsHuN/l4IyHfh8dDQ1ry0+IuZVrf/i8M/P3YFzFfAymDYCQ0=
trusted comment: Here's a trusted comment
dL7lO8sqFFCOXJO/u8SgoDk2nlXGWPRDbOTJkChMbmtUp9PB7sG831basXkZ/0CQ/l/vG7AbPyMNEVZyJn5NCg==

View file

@ -1,4 +0,0 @@
untrusted comment: One more (untrusted™) comment
RWQkliYstQBOKNoyq2O98hPmeVJQ6ShQLM58+4n0gkY0y0trFMDAsHuN/l4IyHfh8dDQ1ry0+IuZVrf/i8M/P3YFzFfAymDYCQ0=
trusted comment: Here's a trusted comment
dL7lO8sqFFCOXJO/u8SgoDk2nlXGWPRDbOTJkChMbmtUp9PB7sG831basXkZ/0CQ/l/vG7AbPyMNEVZyJn5NCg==

View file

@ -1,2 +0,0 @@
untrusted comment: minisign public key 284E00B52C269624
RWQkliYstQBOKOdtClfgC3IypIPX6TAmoEi7beZ4gyR3wsaezvqOMWsp

View file

@ -1,2 +0,0 @@
untrusted comment: minisign encrypted secret key
RWRTY0Iyz8kmPMKrqk6DCtlO9a33akKiaOQG1aLolqDxs52qvPoAAAACAAAAAAAAAEAAAAAArEiggdvyn6+WzTprirLtgiYQoU+ihz/HyGgjhuF+Pz2ddMduyCO+xjCHeq+vgVVW039fbsI8hW6LRGJZLBKV5/jdxCXAVVQE7qTQ6xpEdO0z8Z731/pV1hlspQXG2PNd16NMtwd9dWw=

View file

@ -1,2 +0,0 @@
untrusted comment: verify with signifykey.pub
RWSKLNhZb0KdARbMcGN40hbHzKQYZDgDOFhEUT1YpzMnqre/mbKJ8td/HVlG03Am1YCszATiI0DbnljjTy4iNHYwqBfzrFUqUg0=

View file

@ -1,2 +0,0 @@
untrusted comment: signify public key
RWSKLNhZb0KdATtRT7mZC/bybI3t3+Hv/O2i3ye04Dq9fnT9slpZ1a2/

View file

@ -1,2 +0,0 @@
untrusted comment: signify secret key
RWRCSwAAACpLQDLawSQCtI7eAVIvaiHzjTsTyJsfV5aKLNhZb0KdAWeICXJGa93/bHAcsY6jUh9I8RdEcDWEoGxmaXZC+IdVBPxDpkix9fBRGEUdKWHi3dOfqME0YRzErWI5AVg3cRw=

View file

@ -1,202 +0,0 @@
[
{
"name": "CorruptedDAG",
"uid": "GETH-2020-01",
"summary": "Mining nodes will generate erroneous PoW on epochs > `385`.",
"description": "A mining flaw could cause miners to erroneously calculate PoW, due to an index overflow, if DAG size is exceeding the maximum 32 bit unsigned value.\n\nThis occurred on the ETC chain on 2020-11-06. This is likely to trigger for ETH mainnet around block `11550000`/epoch `385`, slated to occur early January 2021.\n\nThis issue is relevant only for miners, non-mining nodes are unaffected, since non-mining nodes use a smaller verification cache instead of a full DAG.",
"links": [
"https://github.com/ethereum/go-ethereum/pull/21793",
"https://blog.ethereum.org/2020/11/12/geth-security-release",
"https://github.com/ethereum/go-ethereum/commit/567d41d9363706b4b13ce0903804e8acf214af49",
"https://github.com/ethereum/go-ethereum/security/advisories/GHSA-v592-xf75-856p"
],
"introduced": "v1.6.0",
"fixed": "v1.9.24",
"published": "2020-11-12",
"severity": "Medium",
"CVE": "CVE-2020-26240",
"check": "Geth\\/v1\\.(6|7|8)\\..*|Geth\\/v1\\.9\\.\\d-.*|Geth\\/v1\\.9\\.1.*|Geth\\/v1\\.9\\.2(0|1|2|3)-.*"
},
{
"name": "Denial of service due to Go CVE-2020-28362",
"uid": "GETH-2020-02",
"summary": "A denial-of-service issue can be used to crash Geth nodes during block processing, due to an underlying bug in Go (CVE-2020-28362) versions < `1.15.5`, or `<1.14.12`",
"description": "The DoS issue can be used to crash all Geth nodes during block processing, the effects of which would be that a major part of the Ethereum network went offline.\n\nOutside of Go-Ethereum, the issue is most likely relevant for all forks of Geth (such as TurboGeth or ETCs core-geth) which is built with versions of Go which contains the vulnerability.",
"links": [
"https://blog.ethereum.org/2020/11/12/geth-security-release",
"https://groups.google.com/g/golang-announce/c/NpBGTTmKzpM",
"https://github.com/golang/go/issues/42552",
"https://github.com/ethereum/go-ethereum/security/advisories/GHSA-m6gx-rhvj-fh52"
],
"introduced": "v0.0.0",
"fixed": "v1.9.24",
"published": "2020-11-12",
"severity": "Critical",
"CVE": "CVE-2020-28362",
"check": "Geth.*\\/go1\\.(11(.*)|12(.*)|13(.*)|14|14\\.(\\d|10|11|)|15|15\\.[0-4])$"
},
{
"name": "ShallowCopy",
"uid": "GETH-2020-03",
"summary": "A consensus flaw in Geth, related to `datacopy` precompile",
"description": "Geth erroneously performed a 'shallow' copy when the precompiled `datacopy` (at `0x00...04`) was invoked. An attacker could deploy a contract that uses the shallow copy to corrupt the contents of the `RETURNDATA`, thus causing a consensus failure.",
"links": [
"https://blog.ethereum.org/2020/11/12/geth-security-release",
"https://github.com/ethereum/go-ethereum/security/advisories/GHSA-69v6-xc2j-r2jf"
],
"introduced": "v1.9.7",
"fixed": "v1.9.17",
"published": "2020-11-12",
"severity": "Critical",
"CVE": "CVE-2020-26241",
"check": "Geth\\/v1\\.9\\.(7|8|9|10|11|12|13|14|15|16).*$"
},
{
"name": "Geth DoS via MULMOD",
"uid": "GETH-2020-04",
"summary": "A denial-of-service issue can be used to crash Geth nodes during block processing",
"description": "Affected versions suffer from a vulnerability which can be exploited through the `MULMOD` operation, by specifying a modulo of `0`: `mulmod(a,b,0)`, causing a `panic` in the underlying library. \nThe crash was in the `uint256` library, where a buffer [underflowed](https://github.com/holiman/uint256/blob/4ce82e695c10ddad57215bdbeafb68b8c5df2c30/uint256.go#L442).\n\n\tif `d == 0`, `dLen` remains `0`\n\nand https://github.com/holiman/uint256/blob/4ce82e695c10ddad57215bdbeafb68b8c5df2c30/uint256.go#L451 will try to access index `[-1]`.\n\nThe `uint256` library was first merged in this [commit](https://github.com/ethereum/go-ethereum/commit/cf6674539c589f80031f3371a71c6a80addbe454), on 2020-06-08. \nExploiting this vulnerabilty would cause all vulnerable nodes to drop off the network. \n\nThe issue was brought to our attention through a [bug report](https://github.com/ethereum/go-ethereum/issues/21367), showing a `panic` occurring on sync from genesis on the Ropsten network.\n \nIt was estimated that the least obvious way to fix this would be to merge the fix into `uint256`, make a new release of that library and then update the geth-dependency.\n",
"links": [
"https://blog.ethereum.org/2020/11/12/geth-security-release",
"https://github.com/ethereum/go-ethereum/security/advisories/GHSA-jm5c-rv3w-w83m",
"https://github.com/holiman/uint256/releases/tag/v1.1.1",
"https://github.com/holiman/uint256/pull/80",
"https://github.com/ethereum/go-ethereum/pull/21368"
],
"introduced": "v1.9.16",
"fixed": "v1.9.18",
"published": "2020-11-12",
"severity": "Critical",
"CVE": "CVE-2020-26242",
"check": "Geth\\/v1\\.9.(16|17).*$"
},
{
"name": "LES Server DoS via GetProofsV2",
"uid": "GETH-2020-05",
"summary": "A DoS vulnerability can make a LES server crash.",
"description": "A DoS vulnerability can make a LES server crash via malicious GetProofsV2 request from a connected LES client.\n\nThe vulnerability was patched in #21896.\n\nThis vulnerability only concern users explicitly running geth as a light server",
"links": [
"https://github.com/ethereum/go-ethereum/security/advisories/GHSA-r33q-22hv-j29q",
"https://github.com/ethereum/go-ethereum/pull/21896"
],
"introduced": "v1.8.0",
"fixed": "v1.9.25",
"published": "2020-12-10",
"severity": "Medium",
"CVE": "CVE-2020-26264",
"check": "(Geth\\/v1\\.8\\.*)|(Geth\\/v1\\.9\\.\\d-.*)|(Geth\\/v1\\.9\\.1\\d-.*)|(Geth\\/v1\\.9\\.(20|21|22|23|24)-.*)$"
},
{
"name": "SELFDESTRUCT-recreate consensus flaw",
"uid": "GETH-2020-06",
"introduced": "v1.9.4",
"fixed": "v1.9.20",
"summary": "A consensus-vulnerability in Geth could cause a chain split, where vulnerable versions refuse to accept the canonical chain.",
"description": "A flaw was repoted at 2020-08-11 by John Youngseok Yang (Software Platform Lab), where a particular sequence of transactions could cause a consensus failure.\n\n- Tx 1:\n - `sender` invokes `caller`.\n - `caller` invokes `0xaa`. `0xaa` has 3 wei, does a self-destruct-to-self\n - `caller` does a `1 wei` -call to `0xaa`, who thereby has 1 wei (the code in `0xaa` still executed, since the tx is still ongoing, but doesn't redo the selfdestruct, it takes a different path if callvalue is non-zero)\n\n-Tx 2:\n - `sender` does a 5-wei call to 0xaa. No exec (since no code). \n\nIn geth, the result would be that `0xaa` had `6 wei`, whereas OE reported (correctly) `5` wei. Furthermore, in geth, if the second tx was not executed, the `0xaa` would be destructed, resulting in `0 wei`. Thus obviously wrong. \n\nIt was determined that the root cause was this [commit](https://github.com/ethereum/go-ethereum/commit/223b950944f494a5b4e0957fd9f92c48b09037ad) from [this PR](https://github.com/ethereum/go-ethereum/pull/19953). The semantics of `createObject` was subtly changd, into returning a non-nil object (with `deleted=true`) where it previously did not if the account had been destructed. This return value caused the new object to inherit the old `balance`.\n",
"links": [
"https://github.com/ethereum/go-ethereum/security/advisories/GHSA-xw37-57qp-9mm4"
],
"published": "2020-12-10",
"severity": "High",
"CVE": "CVE-2020-26265",
"check": "(Geth\\/v1\\.9\\.(4|5|6|7|8|9)-.*)|(Geth\\/v1\\.9\\.1\\d-.*)$"
},
{
"name": "Not ready for London upgrade",
"uid": "GETH-2021-01",
"summary": "The client is not ready for the 'London' technical upgrade, and will deviate from the canonical chain when the London upgrade occurs (at block '12965000' around August 4, 2021.",
"description": "At (or around) August 4, Ethereum will undergo a technical upgrade called 'London'. Clients not upgraded will fail to progress on the canonical chain.",
"links": [
"https://github.com/ethereum/eth1.0-specs/blob/master/network-upgrades/mainnet-upgrades/london.md",
"https://notes.ethereum.org/@timbeiko/ropsten-postmortem"
],
"introduced": "v1.10.1",
"fixed": "v1.10.6",
"published": "2021-07-22",
"severity": "High",
"check": "(Geth\\/v1\\.10\\.(1|2|3|4|5)-.*)$"
},
{
"name": "RETURNDATA corruption via datacopy",
"uid": "GETH-2021-02",
"summary": "A consensus-flaw in the Geth EVM could cause a node to deviate from the canonical chain.",
"description": "A memory-corruption bug within the EVM can cause a consensus error, where vulnerable nodes obtain a different `stateRoot` when processing a maliciously crafted transaction. This, in turn, would lead to the chain being split: mainnet splitting in two forks.\n\nAll Geth versions supporting the London hard fork are vulnerable (the bug is older than London), so all users should update.\n\nThis bug was exploited on Mainnet at block 13107518.\n\nCredits for the discovery go to @guidovranken (working for Sentnl during an audit of the Telos EVM) and reported via bounty@ethereum.org.",
"links": [
"https://github.com/ethereum/go-ethereum/blob/master/docs/postmortems/2021-08-22-split-postmortem.md",
"https://github.com/ethereum/go-ethereum/security/advisories/GHSA-9856-9gg9-qcmq",
"https://github.com/ethereum/go-ethereum/releases/tag/v1.10.8"
],
"introduced": "v1.10.0",
"fixed": "v1.10.8",
"published": "2021-08-24",
"severity": "High",
"CVE": "CVE-2021-39137",
"check": "(Geth\\/v1\\.10\\.(0|1|2|3|4|5|6|7)-.*)$"
},
{
"name": "DoS via malicious `snap/1` request",
"uid": "GETH-2021-03",
"summary": "A vulnerable node is susceptible to crash when processing a maliciously crafted message from a peer, via the snap/1 protocol. The crash can be triggered by sending a malicious snap/1 GetTrieNodes package.",
"description": "The `snap/1` protocol handler contains two vulnerabilities related to the `GetTrieNodes` packet, which can be exploited to crash the node. Full details are available at the Github security [advisory](https://github.com/ethereum/go-ethereum/security/advisories/GHSA-59hh-656j-3p7v)",
"links": [
"https://github.com/ethereum/go-ethereum/security/advisories/GHSA-59hh-656j-3p7v",
"https://geth.ethereum.org/docs/vulnerabilities/vulnerabilities",
"https://github.com/ethereum/go-ethereum/pull/23657"
],
"introduced": "v1.10.0",
"fixed": "v1.10.9",
"published": "2021-10-24",
"severity": "Medium",
"CVE": "CVE-2021-41173",
"check": "(Geth\\/v1\\.10\\.(0|1|2|3|4|5|6|7|8)-.*)$"
},
{
"name": "DoS via malicious p2p message",
"uid": "GETH-2022-01",
"summary": "A vulnerable node can crash via p2p messages sent from an attacker node, if running with non-default log options.",
"description": "A vulnerable node, if configured to use high verbosity logging, can be made to crash when handling specially crafted p2p messages sent from an attacker node. Full details are available at the Github security [advisory](https://github.com/ethereum/go-ethereum/security/advisories/GHSA-wjxw-gh3m-7pm5)",
"links": [
"https://github.com/ethereum/go-ethereum/security/advisories/GHSA-wjxw-gh3m-7pm5",
"https://geth.ethereum.org/docs/vulnerabilities/vulnerabilities",
"https://github.com/ethereum/go-ethereum/pull/24507"
],
"introduced": "v1.10.0",
"fixed": "v1.10.17",
"published": "2022-05-11",
"severity": "Low",
"CVE": "CVE-2022-29177",
"check": "(Geth\\/v1\\.10\\.(0|1|2|3|4|5|6|7|8|9|10|11|12|13|14|15|16)-.*)$"
},
{
"name": "DoS via malicious p2p message",
"uid": "GETH-2023-01",
"summary": "A vulnerable node can be made to consume unbounded amounts of memory when handling specially crafted p2p messages sent from an attacker node.",
"description": "The p2p handler spawned a new goroutine to respond to ping requests. By flooding a node with ping requests, an unbounded number of goroutines can be created, leading to resource exhaustion and potentially crash due to OOM.",
"links": [
"https://github.com/ethereum/go-ethereum/security/advisories/GHSA-ppjg-v974-84cm",
"https://geth.ethereum.org/docs/vulnerabilities/vulnerabilities"
],
"introduced": "v1.10.0",
"fixed": "v1.12.1",
"published": "2023-09-06",
"severity": "High",
"CVE": "CVE-2023-40591",
"check": "(Geth\\/v1\\.(10|11)\\..*)|(Geth\\/v1\\.12\\.0-.*)$"
},
{
"name": "DoS via malicious p2p message",
"uid": "GETH-2024-01",
"summary": "A vulnerable node can be made to consume very large amounts of memory when handling specially crafted p2p messages sent from an attacker node.",
"description": "A vulnerable node can be made to consume very large amounts of memory when handling specially crafted p2p messages sent from an attacker node. Full details will be available at the Github security [advisory](https://github.com/ethereum/go-ethereum/security/advisories/GHSA-4xc9-8hmq-j652)",
"links": [
"https://github.com/ethereum/go-ethereum/security/advisories/GHSA-4xc9-8hmq-j652",
"https://geth.ethereum.org/docs/vulnerabilities/vulnerabilities"
],
"introduced": "v1.10.0",
"fixed": "v1.13.15",
"published": "2024-05-06",
"severity": "High",
"CVE": "CVE-2024-32972",
"check": "(Geth\\/v1\\.(10|11|12)\\..*)|(Geth\\/v1\\.13\\.\\d-.*)|(Geth\\/v1\\.13\\.1(0|1|2|3|4)-.*)$"
}
]

View file

@ -1,214 +0,0 @@
// Copyright 2022 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 (
"bytes"
"encoding/hex"
"errors"
"fmt"
"os"
"slices"
"github.com/ethereum/go-ethereum/cmd/utils"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-verkle"
"github.com/urfave/cli/v2"
)
var (
zero [32]byte
verkleCommand = &cli.Command{
Name: "verkle",
Usage: "A set of experimental verkle tree management commands",
Description: "",
Subcommands: []*cli.Command{
{
Name: "verify",
Usage: "verify the conversion of a MPT into a verkle tree",
ArgsUsage: "<root>",
Action: verifyVerkle,
Flags: slices.Concat(utils.NetworkFlags, utils.DatabaseFlags),
Description: `
geth verkle verify <state-root>
This command takes a root commitment and attempts to rebuild the tree.
`,
},
{
Name: "dump",
Usage: "Dump a verkle tree to a DOT file",
ArgsUsage: "<root> <key1> [<key 2> ...]",
Action: expandVerkle,
Flags: slices.Concat(utils.NetworkFlags, utils.DatabaseFlags),
Description: `
geth verkle dump <state-root> <key 1> [<key 2> ...]
This command will produce a dot file representing the tree, rooted at <root>.
in which key1, key2, ... are expanded.
`,
},
},
}
)
// recurse into each child to ensure they can be loaded from the db. The tree isn't rebuilt
// (only its nodes are loaded) so there is no need to flush them, the garbage collector should
// take care of that for us.
func checkChildren(root verkle.VerkleNode, resolver verkle.NodeResolverFn) error {
switch node := root.(type) {
case *verkle.InternalNode:
for i, child := range node.Children() {
childC := child.Commit().Bytes()
if bytes.Equal(childC[:], zero[:]) {
continue
}
childS, err := resolver(childC[:])
if err != nil {
return fmt.Errorf("could not find child %x in db: %w", childC, err)
}
// depth is set to 0, the tree isn't rebuilt so it's not a problem
childN, err := verkle.ParseNode(childS, 0)
if err != nil {
return fmt.Errorf("decode error child %x in db: %w", child.Commitment().Bytes(), err)
}
if err := checkChildren(childN, resolver); err != nil {
return fmt.Errorf("%x%w", i, err) // write the path to the erroring node
}
}
case *verkle.LeafNode:
// sanity check: ensure at least one value is non-zero
for i := 0; i < verkle.NodeWidth; i++ {
if len(node.Value(i)) != 0 {
return nil
}
}
return errors.New("both balance and nonce are 0")
case verkle.Empty:
// nothing to do
default:
return fmt.Errorf("unsupported type encountered %v", root)
}
return nil
}
func verifyVerkle(ctx *cli.Context) error {
stack, _ := makeConfigNode(ctx)
defer stack.Close()
chaindb := utils.MakeChainDatabase(ctx, stack, true)
defer chaindb.Close()
headBlock := rawdb.ReadHeadBlock(chaindb)
if headBlock == nil {
log.Error("Failed to load head block")
return errors.New("no head block")
}
if ctx.NArg() > 1 {
log.Error("Too many arguments given")
return errors.New("too many arguments")
}
var (
rootC common.Hash
err error
)
if ctx.NArg() == 1 {
rootC, err = parseRoot(ctx.Args().First())
if err != nil {
log.Error("Failed to resolve state root", "error", err)
return err
}
log.Info("Rebuilding the tree", "root", rootC)
} else {
rootC = headBlock.Root()
log.Info("Rebuilding the tree", "root", rootC, "number", headBlock.NumberU64())
}
serializedRoot, err := chaindb.Get(rootC[:])
if err != nil {
return err
}
root, err := verkle.ParseNode(serializedRoot, 0)
if err != nil {
return err
}
if err := checkChildren(root, chaindb.Get); err != nil {
log.Error("Could not rebuild the tree from the database", "err", err)
return err
}
log.Info("Tree was rebuilt from the database")
return nil
}
func expandVerkle(ctx *cli.Context) error {
stack, _ := makeConfigNode(ctx)
defer stack.Close()
chaindb := utils.MakeChainDatabase(ctx, stack, true)
defer chaindb.Close()
var (
rootC common.Hash
keylist [][]byte
err error
)
if ctx.NArg() >= 2 {
rootC, err = parseRoot(ctx.Args().First())
if err != nil {
log.Error("Failed to resolve state root", "error", err)
return err
}
keylist = make([][]byte, 0, ctx.Args().Len()-1)
args := ctx.Args().Slice()
for i := range args[1:] {
key, err := hex.DecodeString(args[i+1])
log.Info("decoded key", "arg", args[i+1], "key", key)
if err != nil {
return fmt.Errorf("error decoding key #%d: %w", i+1, err)
}
keylist = append(keylist, key)
}
log.Info("Rebuilding the tree", "root", rootC)
} else {
return fmt.Errorf("usage: %s root key1 [key 2...]", ctx.App.Name)
}
serializedRoot, err := chaindb.Get(rootC[:])
if err != nil {
return err
}
root, err := verkle.ParseNode(serializedRoot, 0)
if err != nil {
return err
}
for i, key := range keylist {
log.Info("Reading key", "index", i, "key", key)
root.Get(key, chaindb.Get)
}
if err := os.WriteFile("dump.dot", []byte(verkle.ToDot(root)), 0600); err != nil {
log.Error("Failed to dump file", "err", err)
} else {
log.Info("Tree was dumped to file", "file", "dump.dot")
}
return nil
}

View file

@ -1,170 +0,0 @@
// Copyright 2020 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 (
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"os"
"regexp"
"strings"
"github.com/ethereum/go-ethereum/log"
"github.com/jedisct1/go-minisign"
"github.com/urfave/cli/v2"
)
var gethPubKeys []string = []string{
//@holiman, minisign public key FB1D084D39BAEC24
"RWQk7Lo5TQgd+wxBNZM+Zoy+7UhhMHaWKzqoes9tvSbFLJYZhNTbrIjx",
//minisign public key 138B1CA303E51687
"RWSHFuUDoxyLEzjszuWZI1xStS66QTyXFFZG18uDfO26CuCsbckX1e9J",
//minisign public key FD9813B2D2098484
"RWSEhAnSshOY/b+GmaiDkObbCWefsAoavjoLcPjBo1xn71yuOH5I+Lts",
}
type vulnJson struct {
Name string
Uid string
Summary string
Description string
Links []string
Introduced string
Fixed string
Published string
Severity string
Check string
CVE string
}
func versionCheck(ctx *cli.Context) error {
url := ctx.String(VersionCheckUrlFlag.Name)
version := ctx.String(VersionCheckVersionFlag.Name)
log.Info("Checking vulnerabilities", "version", version, "url", url)
return checkCurrent(url, version)
}
func checkCurrent(url, current string) error {
var (
data []byte
sig []byte
err error
)
if data, err = fetch(url); err != nil {
return fmt.Errorf("could not retrieve data: %w", err)
}
if sig, err = fetch(fmt.Sprintf("%v.minisig", url)); err != nil {
return fmt.Errorf("could not retrieve signature: %w", err)
}
if err = verifySignature(gethPubKeys, data, sig); err != nil {
return err
}
var vulns []vulnJson
if err = json.Unmarshal(data, &vulns); err != nil {
return err
}
allOk := true
for _, vuln := range vulns {
r, err := regexp.Compile(vuln.Check)
if err != nil {
return err
}
if r.MatchString(current) {
allOk = false
fmt.Printf("## Vulnerable to %v (%v)\n\n", vuln.Uid, vuln.Name)
fmt.Printf("Severity: %v\n", vuln.Severity)
fmt.Printf("Summary : %v\n", vuln.Summary)
fmt.Printf("Fixed in: %v\n", vuln.Fixed)
if len(vuln.CVE) > 0 {
fmt.Printf("CVE: %v\n", vuln.CVE)
}
if len(vuln.Links) > 0 {
fmt.Printf("References:\n")
for _, ref := range vuln.Links {
fmt.Printf("\t- %v\n", ref)
}
}
fmt.Println()
}
}
if allOk {
fmt.Println("No vulnerabilities found")
}
return nil
}
// fetch makes an HTTP request to the given url and returns the response body
func fetch(url string) ([]byte, error) {
if filep := strings.TrimPrefix(url, "file://"); filep != url {
return os.ReadFile(filep)
}
res, err := http.Get(url)
if err != nil {
return nil, err
}
defer res.Body.Close()
body, err := io.ReadAll(res.Body)
if err != nil {
return nil, err
}
return body, nil
}
// verifySignature checks that the sigData is a valid signature of the given
// data, for pubkey GethPubkey
func verifySignature(pubkeys []string, data, sigdata []byte) error {
sig, err := minisign.DecodeSignature(string(sigdata))
if err != nil {
return err
}
// find the used key
var key *minisign.PublicKey
for _, pubkey := range pubkeys {
pub, err := minisign.NewPublicKey(pubkey)
if err != nil {
// our pubkeys should be parseable
return err
}
if pub.KeyId != sig.KeyId {
continue
}
key = &pub
break
}
if key == nil {
log.Info("Signing key not trusted", "keyid", keyID(sig.KeyId), "error", err)
return errors.New("signature could not be verified")
}
if ok, err := key.Verify(data, sig); !ok || err != nil {
log.Info("Verification failed error", "keyid", keyID(key.KeyId), "error", err)
return errors.New("signature could not be verified")
}
return nil
}
// keyID turns a binary minisign key ID into a hex string.
// Note: key IDs are printed in reverse byte order.
func keyID(id [8]byte) string {
var rev [8]byte
for i := range id {
rev[len(rev)-1-i] = id[i]
}
return fmt.Sprintf("%X", rev)
}

View file

@ -1,189 +0,0 @@
// Copyright 2020 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 (
"encoding/json"
"fmt"
"os"
"path/filepath"
"regexp"
"strconv"
"strings"
"testing"
"github.com/jedisct1/go-minisign"
)
func TestVerification(t *testing.T) {
t.Parallel()
// Signatures generated with `minisign`. Legacy format, not pre-hashed file.
t.Run("minisig-legacy", func(t *testing.T) {
t.Parallel()
// For this test, the pubkey is in testdata/vcheck/minisign.pub
// (the privkey is `minisign.sec`, if we want to expand this test. Password 'test' )
// 1. `minisign -S -l -s ./minisign.sec -m data.json -x ./minisig-sigs/vulnerabilities.json.minisig.1 -c "signature from minisign secret key"`
// 2. `minisign -S -l -s ./minisign.sec -m vulnerabilities.json -x ./minisig-sigs/vulnerabilities.json.minisig.2 -c "Here's a comment" -t "Here's a trusted comment"`
// 3. minisign -S -l -s ./minisign.sec -m vulnerabilities.json -x ./minisig-sigs/vulnerabilities.json.minisig.3 -c "One more (untrusted™) comment" -t "Here's a trusted comment"
pub := "RWQkliYstQBOKOdtClfgC3IypIPX6TAmoEi7beZ4gyR3wsaezvqOMWsp"
testVerification(t, pub, "./testdata/vcheck/minisig-sigs/")
})
t.Run("minisig-new", func(t *testing.T) {
t.Parallel()
// For this test, the pubkey is in testdata/vcheck/minisign.pub
// (the privkey is `minisign.sec`, if we want to expand this test. Password 'test' )
// `minisign -S -s ./minisign.sec -m data.json -x ./minisig-sigs-new/data.json.minisig`
pub := "RWQkliYstQBOKOdtClfgC3IypIPX6TAmoEi7beZ4gyR3wsaezvqOMWsp"
testVerification(t, pub, "./testdata/vcheck/minisig-sigs-new/")
})
// Signatures generated with `signify-openbsd`
t.Run("signify-openbsd", func(t *testing.T) {
t.Parallel()
t.Skip("This currently fails, minisign expects 4 lines of data, signify provides only 2")
// For this test, the pubkey is in testdata/vcheck/signifykey.pub
// (the privkey is `signifykey.sec`, if we want to expand this test. Password 'test' )
// `signify -S -s signifykey.sec -m data.json -x ./signify-sigs/data.json.sig`
pub := "RWSKLNhZb0KdATtRT7mZC/bybI3t3+Hv/O2i3ye04Dq9fnT9slpZ1a2/"
testVerification(t, pub, "./testdata/vcheck/signify-sigs/")
})
}
func testVerification(t *testing.T, pubkey, sigdir string) {
// Data to verify
data, err := os.ReadFile("./testdata/vcheck/data.json")
if err != nil {
t.Fatal(err)
}
// Signatures, with and without comments, both trusted and untrusted
files, err := os.ReadDir(sigdir)
if err != nil {
t.Fatal(err)
}
if len(files) == 0 {
t.Fatal("Missing tests")
}
for _, f := range files {
sig, err := os.ReadFile(filepath.Join(sigdir, f.Name()))
if err != nil {
t.Fatal(err)
}
err = verifySignature([]string{pubkey}, data, sig)
if err != nil {
t.Fatal(err)
}
}
}
func versionUint(v string) int {
mustInt := func(s string) int {
a, err := strconv.Atoi(s)
if err != nil {
panic(v)
}
return a
}
components := strings.Split(strings.TrimPrefix(v, "v"), ".")
a := mustInt(components[0])
b := mustInt(components[1])
c := mustInt(components[2])
return a*100*100 + b*100 + c
}
// TestMatching can be used to check that the regexps are correct
func TestMatching(t *testing.T) {
t.Parallel()
data, _ := os.ReadFile("./testdata/vcheck/vulnerabilities.json")
var vulns []vulnJson
if err := json.Unmarshal(data, &vulns); err != nil {
t.Fatal(err)
}
check := func(version string) {
vFull := fmt.Sprintf("Geth/%v-unstable-15339cf1-20201204/linux-amd64/go1.15.4", version)
for _, vuln := range vulns {
r, err := regexp.Compile(vuln.Check)
vulnIntro := versionUint(vuln.Introduced)
vulnFixed := versionUint(vuln.Fixed)
current := versionUint(version)
if err != nil {
t.Fatal(err)
}
if vuln.Name == "Denial of service due to Go CVE-2020-28362" {
// this one is not tied to geth-versions
continue
}
if vulnIntro <= current && vulnFixed > current {
// Should be vulnerable
if !r.MatchString(vFull) {
t.Errorf("Should be vulnerable, version %v, intro: %v, fixed: %v %v %v",
version, vuln.Introduced, vuln.Fixed, vuln.Name, vuln.Check)
}
} else {
if r.MatchString(vFull) {
t.Errorf("Should not be flagged vulnerable, version %v, intro: %v, fixed: %v %v %d %d %d",
version, vuln.Introduced, vuln.Fixed, vuln.Name, vulnIntro, current, vulnFixed)
}
}
}
}
for major := 1; major < 2; major++ {
for minor := 0; minor < 30; minor++ {
for patch := 0; patch < 30; patch++ {
vShort := fmt.Sprintf("v%d.%d.%d", major, minor, patch)
check(vShort)
}
}
}
}
func TestGethPubKeysParseable(t *testing.T) {
t.Parallel()
for _, pubkey := range gethPubKeys {
_, err := minisign.NewPublicKey(pubkey)
if err != nil {
t.Errorf("Should be parseable")
}
}
}
func TestKeyID(t *testing.T) {
t.Parallel()
type args struct {
id [8]byte
}
tests := []struct {
name string
args args
want string
}{
{"@holiman key", args{id: extractKeyId(gethPubKeys[0])}, "FB1D084D39BAEC24"},
{"second key", args{id: extractKeyId(gethPubKeys[1])}, "138B1CA303E51687"},
{"third key", args{id: extractKeyId(gethPubKeys[2])}, "FD9813B2D2098484"},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
if got := keyID(tt.args.id); got != tt.want {
t.Errorf("keyID() = %v, want %v", got, tt.want)
}
})
}
}
func extractKeyId(pubkey string) [8]byte {
p, _ := minisign.NewPublicKey(pubkey)
return p.KeyId
}

View file

@ -12,15 +12,13 @@ require (
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
github.com/consensys/gnark-crypto v0.18.1 // indirect
github.com/crate-crypto/go-eth-kzg v1.4.0 // indirect
github.com/crate-crypto/go-ipa v0.0.0-20240724233137-53bbb0ceb27a // indirect
github.com/deckarep/golang-set/v2 v2.6.0 // indirect
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 // indirect
github.com/emicklei/dot v1.6.2 // indirect
github.com/ethereum/c-kzg-4844/v2 v2.1.5 // indirect
github.com/ethereum/go-bigmodexpfix v0.0.0-20250911101455-f9e208c548ab // indirect
github.com/ethereum/go-verkle v0.2.2 // indirect
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

View file

@ -26,12 +26,10 @@ github.com/cockroachdb/redact v1.1.5 h1:u1PMllDkdFfPWaNGMyLD1+so+aq3uUItthCFqzwP
github.com/cockroachdb/redact v1.1.5/go.mod h1:BVNblN9mBWFyMyqK1k3AAiSxhvhfK2oOZZ2lK+dpvRg=
github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06 h1:zuQyyAKVxetITBuuhv3BI9cMrmStnpT18zmgmTxunpo=
github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06/go.mod h1:7nc4anLGjupUW/PeY5qiNYsdNXj7zopG+eqsS7To5IQ=
github.com/consensys/gnark-crypto v0.18.0 h1:vIye/FqI50VeAr0B3dx+YjeIvmc3LWz4yEfbWBpTUf0=
github.com/consensys/gnark-crypto v0.18.0/go.mod h1:L3mXGFTe1ZN+RSJ+CLjUt9x7PNdx8ubaYfDROyp2Z8c=
github.com/consensys/gnark-crypto v0.18.1 h1:RyLV6UhPRoYYzaFnPQA4qK3DyuDgkTgskDdoGqFt3fI=
github.com/consensys/gnark-crypto v0.18.1/go.mod h1:L3mXGFTe1ZN+RSJ+CLjUt9x7PNdx8ubaYfDROyp2Z8c=
github.com/crate-crypto/go-eth-kzg v1.4.0 h1:WzDGjHk4gFg6YzV0rJOAsTK4z3Qkz5jd4RE3DAvPFkg=
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.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=
@ -46,8 +44,6 @@ github.com/ethereum/c-kzg-4844/v2 v2.1.5 h1:aVtoLK5xwJ6c5RiqO8g8ptJ5KU+2Hdquf6G3
github.com/ethereum/c-kzg-4844/v2 v2.1.5/go.mod h1:u59hRTTah4Co6i9fDWtiCjTrblJv0UwsqZKCc0GfgUs=
github.com/ethereum/go-bigmodexpfix v0.0.0-20250911101455-f9e208c548ab h1:rvv6MJhy07IMfEKuARQ9TKojGqLVNxQajaXEp/BoqSk=
github.com/ethereum/go-bigmodexpfix v0.0.0-20250911101455-f9e208c548ab/go.mod h1:IuLm4IsPipXKF7CW5Lzf68PIbZ5yl7FFd74l/E0o9A8=
github.com/ethereum/go-verkle v0.2.2 h1:I2W0WjnrFUIzzVPwm8ykY+7pL2d4VhlsePn4j7cnFk8=
github.com/ethereum/go-verkle v0.2.2/go.mod h1:M3b90YRnzqKyyzBEWJGqj8Qff4IDeXnzFw0P9bFw3uk=
github.com/ferranbt/fastssz v0.1.4 h1:OCDB+dYDEQDvAgtAGnTSidK1Pe2tW3nFV40XyMkTeDY=
github.com/ferranbt/fastssz v0.1.4/go.mod h1:Ea3+oeoRGGLGm5shYAeDgu6PGUlcvQhE2fILyD9+tGg=
github.com/getsentry/sentry-go v0.27.0 h1:Pv98CIbtB3LkMWmXi4Joa5OOcwbmnX88sF5qbK3r3Ps=

View file

@ -137,7 +137,7 @@ var (
}
NetworkIdFlag = &cli.Uint64Flag{
Name: "networkid",
Usage: "Explicitly set network id (integer)(For testnets: use --sepolia, --holesky, --hoodi instead)",
Usage: "Explicitly set network ID (integer)(For testnets: use --sepolia, --holesky, --hoodi instead)",
Value: ethconfig.Defaults.NetworkId,
Category: flags.EthCategory,
}
@ -672,6 +672,12 @@ var (
Usage: "Disables db compaction after import",
Category: flags.LoggingCategory,
}
LogSlowBlockFlag = &cli.DurationFlag{
Name: "debug.logslowblock",
Usage: "Block execution time threshold beyond which detailed statistics will be logged (0 means disable)",
Value: ethconfig.Defaults.SlowBlockThreshold,
Category: flags.LoggingCategory,
}
// MISC settings
SyncTargetFlag = &cli.StringFlag{
@ -864,14 +870,14 @@ var (
Aliases: []string{"discv4"},
Usage: "Enables the V4 discovery mechanism",
Category: flags.NetworkingCategory,
Value: true,
Value: node.DefaultConfig.P2P.DiscoveryV4,
}
DiscoveryV5Flag = &cli.BoolFlag{
Name: "discovery.v5",
Aliases: []string{"discv5"},
Usage: "Enables the V5 discovery mechanism",
Category: flags.NetworkingCategory,
Value: true,
Value: node.DefaultConfig.P2P.DiscoveryV5,
}
NetrestrictFlag = &cli.StringFlag{
Name: "netrestrict",
@ -1368,13 +1374,17 @@ func SetP2PConfig(ctx *cli.Context, cfg *p2p.Config) {
cfg.MaxPendingPeers = ctx.Int(MaxPendingPeersFlag.Name)
}
if ctx.IsSet(NoDiscoverFlag.Name) {
cfg.NoDiscovery = true
cfg.NoDiscovery = ctx.Bool(NoDiscoverFlag.Name)
}
flags.CheckExclusive(ctx, DiscoveryV4Flag, NoDiscoverFlag)
flags.CheckExclusive(ctx, DiscoveryV5Flag, NoDiscoverFlag)
cfg.DiscoveryV4 = ctx.Bool(DiscoveryV4Flag.Name)
cfg.DiscoveryV5 = ctx.Bool(DiscoveryV5Flag.Name)
if ctx.IsSet(DiscoveryV4Flag.Name) {
cfg.DiscoveryV4 = ctx.Bool(DiscoveryV4Flag.Name)
}
if ctx.IsSet(DiscoveryV5Flag.Name) {
cfg.DiscoveryV5 = ctx.Bool(DiscoveryV5Flag.Name)
}
if netrestrict := ctx.String(NetrestrictFlag.Name); netrestrict != "" {
list, err := netutil.ParseNetlist(netrestrict)
@ -1420,7 +1430,7 @@ func SetNodeConfig(ctx *cli.Context, cfg *node.Config) {
cfg.KeyStoreDir = ctx.String(KeyStoreDirFlag.Name)
}
if ctx.IsSet(DeveloperFlag.Name) {
cfg.UseLightweightKDF = true
cfg.UseLightweightKDF = ctx.Bool(DeveloperFlag.Name)
}
if ctx.IsSet(LightKDFFlag.Name) {
cfg.UseLightweightKDF = ctx.Bool(LightKDFFlag.Name)
@ -1577,8 +1587,7 @@ func setMiner(ctx *cli.Context, cfg *miner.Config) {
cfg.Recommit = ctx.Duration(MinerNewPayloadTimeoutFlag.Name)
}
if ctx.IsSet(MinerMaxBlobsFlag.Name) {
maxBlobs := ctx.Int(MinerMaxBlobsFlag.Name)
cfg.MaxBlobsPerBlock = &maxBlobs
cfg.MaxBlobsPerBlock = ctx.Int(MinerMaxBlobsFlag.Name)
}
}
@ -1612,8 +1621,8 @@ func setRequiredBlocks(ctx *cli.Context, cfg *ethconfig.Config) {
// SetEthConfig applies eth-related command line flags to the config.
func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) {
// Avoid conflicting network flags, don't allow network id override on preset networks
flags.CheckExclusive(ctx, MainnetFlag, DeveloperFlag, SepoliaFlag, HoleskyFlag, HoodiFlag, NetworkIdFlag, OverrideGenesisFlag)
// Avoid conflicting network flags
flags.CheckExclusive(ctx, MainnetFlag, DeveloperFlag, SepoliaFlag, HoleskyFlag, HoodiFlag, OverrideGenesisFlag)
flags.CheckExclusive(ctx, DeveloperFlag, ExternalSignerFlag) // Can't use both ephemeral unlocked and external signer
// Set configurations from CLI flags
@ -1660,9 +1669,6 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) {
}
}
if ctx.IsSet(NetworkIdFlag.Name) {
cfg.NetworkId = ctx.Uint64(NetworkIdFlag.Name)
}
if ctx.IsSet(CacheFlag.Name) || ctx.IsSet(CacheDatabaseFlag.Name) {
cfg.DatabaseCache = ctx.Int(CacheFlag.Name) * ctx.Int(CacheDatabaseFlag.Name) / 100
}
@ -1683,8 +1689,9 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) {
if ctx.IsSet(CacheNoPrefetchFlag.Name) {
cfg.NoPrefetch = ctx.Bool(CacheNoPrefetchFlag.Name)
}
// Read the value from the flag no matter if it's set or not.
cfg.Preimages = ctx.Bool(CachePreimagesFlag.Name)
if ctx.IsSet(CachePreimagesFlag.Name) {
cfg.Preimages = ctx.Bool(CachePreimagesFlag.Name)
}
if cfg.NoPruning && !cfg.Preimages {
cfg.Preimages = true
log.Info("Enabling recording of key preimages since archive mode is used")
@ -1717,7 +1724,10 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) {
cfg.LogHistory = ctx.Uint64(LogHistoryFlag.Name)
}
if ctx.IsSet(LogNoHistoryFlag.Name) {
cfg.LogNoHistory = true
cfg.LogNoHistory = ctx.Bool(LogNoHistoryFlag.Name)
}
if ctx.IsSet(LogSlowBlockFlag.Name) {
cfg.SlowBlockThreshold = ctx.Duration(LogSlowBlockFlag.Name)
}
if ctx.IsSet(LogExportCheckpointsFlag.Name) {
cfg.LogExportCheckpoints = ctx.String(LogExportCheckpointsFlag.Name)
@ -1912,10 +1922,18 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) {
}
cfg.Genesis = genesis
default:
if cfg.NetworkId == 1 {
if ctx.Uint64(NetworkIdFlag.Name) == 1 {
SetDNSDiscoveryDefaults(cfg, params.MainnetGenesisHash)
}
}
if ctx.IsSet(NetworkIdFlag.Name) {
// Typically it's best to automatically set the network ID to the chainID,
// by not passing the --networkid flag at all. Emit a warning when set
// explicitly in case overriding the network ID is not the user's intention.
id := ctx.Uint64(NetworkIdFlag.Name)
log.Warn("Setting network ID with command-line flag", "id", id)
cfg.NetworkId = id
}
// Set any dangling config values
if ctx.String(CryptoKZGFlag.Name) != "gokzg" && ctx.String(CryptoKZGFlag.Name) != "ckzg" {
Fatalf("--%s flag must be 'gokzg' or 'ckzg'", CryptoKZGFlag.Name)
@ -2290,6 +2308,7 @@ func MakeChain(ctx *cli.Context, stack *node.Node, readonly bool) (*core.BlockCh
Preimages: ctx.Bool(CachePreimagesFlag.Name),
StateScheme: scheme,
StateHistory: ctx.Uint64(StateHistoryFlag.Name),
// Disable transaction indexing/unindexing.
TxLookupLimit: -1,
@ -2301,6 +2320,9 @@ func MakeChain(ctx *cli.Context, stack *node.Node, readonly bool) (*core.BlockCh
// Enable state size tracking if enabled
StateSizeTracking: ctx.Bool(StateSizeTrackingFlag.Name),
// Configure the slow block statistic logger
SlowBlockThreshold: ctx.Duration(LogSlowBlockFlag.Name),
}
if options.ArchiveMode && !options.Preimages {
options.Preimages = true

View file

@ -34,4 +34,5 @@ the following commands (in this directory) against a synced mainnet node:
> go run . filtergen --queries queries/filter_queries_mainnet.json http://host:8545
> go run . historygen --history-tests queries/history_mainnet.json http://host:8545
> go run . tracegen --trace-tests queries/trace_mainnet.json --trace-start 4000000 --trace-end 4000100 http://host:8545
> go run . proofgen --proof-tests queries/proof_mainnet.json --proof-states 3000 http://host:8545
```

View file

@ -48,6 +48,7 @@ func init() {
historyGenerateCommand,
filterGenerateCommand,
traceGenerateCommand,
proofGenerateCommand,
filterPerfCommand,
filterFuzzCommand,
}

105
cmd/workload/prooftest.go Normal file
View file

@ -0,0 +1,105 @@
// 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"
"encoding/json"
"fmt"
"math/big"
"os"
"strings"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/internal/utesting"
"github.com/urfave/cli/v2"
)
// proofTest is the content of a state-proof test.
type proofTest struct {
BlockNumbers []uint64 `json:"blockNumbers"`
Addresses [][]common.Address `json:"addresses"`
StorageKeys [][][]string `json:"storageKeys"`
Results [][]common.Hash `json:"results"`
}
type proofTestSuite struct {
cfg testConfig
tests proofTest
invalidDir string
}
func newProofTestSuite(cfg testConfig, ctx *cli.Context) *proofTestSuite {
s := &proofTestSuite{
cfg: cfg,
invalidDir: ctx.String(proofTestInvalidOutputFlag.Name),
}
if err := s.loadTests(); err != nil {
exit(err)
}
return s
}
func (s *proofTestSuite) loadTests() error {
file, err := s.cfg.fsys.Open(s.cfg.proofTestFile)
if err != nil {
// If not found in embedded FS, try to load it from disk
if !os.IsNotExist(err) {
return err
}
file, err = os.OpenFile(s.cfg.proofTestFile, os.O_RDONLY, 0666)
if err != nil {
return fmt.Errorf("can't open proofTestFile: %v", err)
}
}
defer file.Close()
if err := json.NewDecoder(file).Decode(&s.tests); err != nil {
return fmt.Errorf("invalid JSON in %s: %v", s.cfg.proofTestFile, err)
}
if len(s.tests.BlockNumbers) == 0 {
return fmt.Errorf("proofTestFile %s has no test data", s.cfg.proofTestFile)
}
return nil
}
func (s *proofTestSuite) allTests() []workloadTest {
return []workloadTest{
newArchiveWorkloadTest("Proof/GetProof", s.getProof),
}
}
func (s *proofTestSuite) getProof(t *utesting.T) {
ctx := context.Background()
for i, blockNumber := range s.tests.BlockNumbers {
for j := 0; j < len(s.tests.Addresses[i]); j++ {
res, err := s.cfg.client.Geth.GetProof(ctx, s.tests.Addresses[i][j], s.tests.StorageKeys[i][j], big.NewInt(int64(blockNumber)))
if err != nil {
t.Errorf("State proving fails, blockNumber: %d, address: %x, keys: %v, err: %v\n", blockNumber, s.tests.Addresses[i][j], strings.Join(s.tests.StorageKeys[i][j], " "), err)
continue
}
blob, err := json.Marshal(res)
if err != nil {
t.Fatalf("State proving fails: error %v", err)
continue
}
if crypto.Keccak256Hash(blob) != s.tests.Results[i][j] {
t.Errorf("State proof mismatch, %d, number: %d, address: %x, keys: %v: invalid result", i, blockNumber, s.tests.Addresses[i][j], strings.Join(s.tests.StorageKeys[i][j], " "))
}
}
}
}

View file

@ -0,0 +1,355 @@
// 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"
"encoding/json"
"fmt"
"math/big"
"math/rand"
"os"
"path/filepath"
"time"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/eth/tracers"
"github.com/ethereum/go-ethereum/eth/tracers/native"
"github.com/ethereum/go-ethereum/internal/flags"
"github.com/ethereum/go-ethereum/internal/testrand"
"github.com/ethereum/go-ethereum/log"
"github.com/urfave/cli/v2"
)
var (
proofGenerateCommand = &cli.Command{
Name: "proofgen",
Usage: "Generates tests for state proof verification",
ArgsUsage: "<RPC endpoint URL>",
Action: generateProofTests,
Flags: []cli.Flag{
proofTestFileFlag,
proofTestResultOutputFlag,
proofTestStatesFlag,
proofTestStartBlockFlag,
proofTestEndBlockFlag,
},
}
proofTestFileFlag = &cli.StringFlag{
Name: "proof-tests",
Usage: "JSON file containing proof test queries",
Value: "proof_tests.json",
Category: flags.TestingCategory,
}
proofTestResultOutputFlag = &cli.StringFlag{
Name: "proof-output",
Usage: "Folder containing detailed trace output files",
Value: "",
Category: flags.TestingCategory,
}
proofTestStatesFlag = &cli.Int64Flag{
Name: "proof-states",
Usage: "Number of states to generate proof against",
Value: 10000,
Category: flags.TestingCategory,
}
proofTestInvalidOutputFlag = &cli.StringFlag{
Name: "proof-invalid",
Usage: "Folder containing the mismatched state proof output files",
Value: "",
Category: flags.TestingCategory,
}
proofTestStartBlockFlag = &cli.Uint64Flag{
Name: "proof-start",
Usage: "The number of starting block for proof verification (included)",
Category: flags.TestingCategory,
}
proofTestEndBlockFlag = &cli.Uint64Flag{
Name: "proof-end",
Usage: "The number of ending block for proof verification (excluded)",
Category: flags.TestingCategory,
}
)
type proofGenerator func(cli *client, startBlock uint64, endBlock uint64, number int) ([]uint64, [][]common.Address, [][][]string, error)
func genAccountProof(cli *client, startBlock uint64, endBlock uint64, number int) ([]uint64, [][]common.Address, [][][]string, error) {
var (
blockNumbers []uint64
accountAddresses [][]common.Address
storageKeys [][][]string
nAccounts int
ctx = context.Background()
start = time.Now()
)
chainID, err := cli.Eth.ChainID(ctx)
if err != nil {
return nil, nil, nil, err
}
signer := types.LatestSignerForChainID(chainID)
for {
if nAccounts >= number {
break
}
blockNumber := uint64(rand.Intn(int(endBlock-startBlock))) + startBlock
block, err := cli.Eth.BlockByNumber(context.Background(), big.NewInt(int64(blockNumber)))
if err != nil {
continue
}
var (
addresses []common.Address
keys [][]string
gather = func(address common.Address) {
addresses = append(addresses, address)
keys = append(keys, nil)
nAccounts++
}
)
for _, tx := range block.Transactions() {
if nAccounts >= number {
break
}
sender, err := signer.Sender(tx)
if err != nil {
log.Error("Failed to resolve the sender address", "hash", tx.Hash(), "err", err)
continue
}
gather(sender)
if tx.To() != nil {
gather(*tx.To())
}
}
blockNumbers = append(blockNumbers, blockNumber)
accountAddresses = append(accountAddresses, addresses)
storageKeys = append(storageKeys, keys)
}
log.Info("Generated tests for account proof", "blocks", len(blockNumbers), "accounts", nAccounts, "elapsed", common.PrettyDuration(time.Since(start)))
return blockNumbers, accountAddresses, storageKeys, nil
}
func genNonExistentAccountProof(cli *client, startBlock uint64, endBlock uint64, number int) ([]uint64, [][]common.Address, [][][]string, error) {
var (
blockNumbers []uint64
accountAddresses [][]common.Address
storageKeys [][][]string
total int
)
for i := 0; i < number/5; i++ {
var (
addresses []common.Address
keys [][]string
blockNumber = uint64(rand.Intn(int(endBlock-startBlock))) + startBlock
)
for j := 0; j < 5; j++ {
addresses = append(addresses, testrand.Address())
keys = append(keys, nil)
}
total += len(addresses)
blockNumbers = append(blockNumbers, blockNumber)
accountAddresses = append(accountAddresses, addresses)
storageKeys = append(storageKeys, keys)
}
log.Info("Generated tests for non-existing account proof", "blocks", len(blockNumbers), "accounts", total)
return blockNumbers, accountAddresses, storageKeys, nil
}
func genStorageProof(cli *client, startBlock uint64, endBlock uint64, number int) ([]uint64, [][]common.Address, [][][]string, error) {
var (
blockNumbers []uint64
accountAddresses [][]common.Address
storageKeys [][][]string
nAccounts int
nStorages int
start = time.Now()
)
for {
if nAccounts+nStorages >= number {
break
}
blockNumber := uint64(rand.Intn(int(endBlock-startBlock))) + startBlock
block, err := cli.Eth.BlockByNumber(context.Background(), big.NewInt(int64(blockNumber)))
if err != nil {
continue
}
var (
addresses []common.Address
slots [][]string
tracer = "prestateTracer"
configBlob, _ = json.Marshal(native.PrestateTracerConfig{
DiffMode: false,
DisableCode: true,
DisableStorage: false,
})
)
for _, tx := range block.Transactions() {
if nAccounts+nStorages >= number {
break
}
if tx.To() == nil {
continue
}
ret, err := cli.Geth.TraceTransaction(context.Background(), tx.Hash(), &tracers.TraceConfig{
Tracer: &tracer,
TracerConfig: configBlob,
})
if err != nil {
log.Error("Failed to trace the transaction", "blockNumber", blockNumber, "hash", tx.Hash(), "err", err)
continue
}
blob, err := json.Marshal(ret)
if err != nil {
log.Error("Failed to marshal data", "err", err)
continue
}
var accounts map[common.Address]*types.Account
if err := json.Unmarshal(blob, &accounts); err != nil {
log.Error("Failed to decode trace result", "blockNumber", blockNumber, "hash", tx.Hash(), "err", err)
continue
}
for addr, account := range accounts {
if len(account.Storage) == 0 {
continue
}
addresses = append(addresses, addr)
nAccounts += 1
var keys []string
for k := range account.Storage {
keys = append(keys, k.Hex())
}
nStorages += len(keys)
var emptyKeys []string
for i := 0; i < 3; i++ {
emptyKeys = append(emptyKeys, testrand.Hash().Hex())
}
nStorages += len(emptyKeys)
slots = append(slots, append(keys, emptyKeys...))
}
}
blockNumbers = append(blockNumbers, blockNumber)
accountAddresses = append(accountAddresses, addresses)
storageKeys = append(storageKeys, slots)
}
log.Info("Generated tests for storage proof", "blocks", len(blockNumbers), "accounts", nAccounts, "storages", nStorages, "elapsed", common.PrettyDuration(time.Since(start)))
return blockNumbers, accountAddresses, storageKeys, nil
}
func genProofRequests(cli *client, startBlock, endBlock uint64, states int) (*proofTest, error) {
var (
blockNumbers []uint64
accountAddresses [][]common.Address
storageKeys [][][]string
)
ratio := []float64{0.2, 0.1, 0.7}
for i, fn := range []proofGenerator{genAccountProof, genNonExistentAccountProof, genStorageProof} {
numbers, addresses, keys, err := fn(cli, startBlock, endBlock, int(float64(states)*ratio[i]))
if err != nil {
return nil, err
}
blockNumbers = append(blockNumbers, numbers...)
accountAddresses = append(accountAddresses, addresses...)
storageKeys = append(storageKeys, keys...)
}
return &proofTest{
BlockNumbers: blockNumbers,
Addresses: accountAddresses,
StorageKeys: storageKeys,
}, nil
}
func generateProofTests(clictx *cli.Context) error {
var (
client = makeClient(clictx)
ctx = context.Background()
states = clictx.Int(proofTestStatesFlag.Name)
outputFile = clictx.String(proofTestFileFlag.Name)
outputDir = clictx.String(proofTestResultOutputFlag.Name)
startBlock = clictx.Uint64(proofTestStartBlockFlag.Name)
endBlock = clictx.Uint64(proofTestEndBlockFlag.Name)
)
head, err := client.Eth.BlockNumber(ctx)
if err != nil {
exit(err)
}
if startBlock > head || endBlock > head {
return fmt.Errorf("chain is out of proof range, head %d, start: %d, limit: %d", head, startBlock, endBlock)
}
if endBlock == 0 {
endBlock = head
}
log.Info("Generating proof states", "startBlock", startBlock, "endBlock", endBlock, "states", states)
test, err := genProofRequests(client, startBlock, endBlock, states)
if err != nil {
exit(err)
}
for i, blockNumber := range test.BlockNumbers {
var hashes []common.Hash
for j := 0; j < len(test.Addresses[i]); j++ {
res, err := client.Geth.GetProof(ctx, test.Addresses[i][j], test.StorageKeys[i][j], big.NewInt(int64(blockNumber)))
if err != nil {
log.Error("Failed to prove the state", "number", blockNumber, "address", test.Addresses[i][j], "slots", len(test.StorageKeys[i][j]), "err", err)
continue
}
blob, err := json.Marshal(res)
if err != nil {
return err
}
hashes = append(hashes, crypto.Keccak256Hash(blob))
writeStateProof(outputDir, blockNumber, test.Addresses[i][j], res)
}
test.Results = append(test.Results, hashes)
}
writeJSON(outputFile, test)
return nil
}
func writeStateProof(dir string, blockNumber uint64, address common.Address, result any) {
if dir == "" {
return
}
// Ensure the directory exists
if err := os.MkdirAll(dir, os.ModePerm); err != nil {
exit(fmt.Errorf("failed to create directories: %w", err))
}
fname := fmt.Sprintf("%d-%x", blockNumber, address)
name := filepath.Join(dir, fname)
file, err := os.Create(name)
if err != nil {
exit(fmt.Errorf("error creating %s: %v", name, err))
return
}
defer file.Close()
data, _ := json.MarshalIndent(result, "", " ")
_, err = file.Write(data)
if err != nil {
exit(fmt.Errorf("error writing %s: %v", name, err))
return
}
}

View file

@ -50,7 +50,9 @@ var (
filterQueryFileFlag,
historyTestFileFlag,
traceTestFileFlag,
proofTestFileFlag,
traceTestInvalidOutputFlag,
proofTestInvalidOutputFlag,
},
}
testPatternFlag = &cli.StringFlag{
@ -95,6 +97,7 @@ type testConfig struct {
historyTestFile string
historyPruneBlock *uint64
traceTestFile string
proofTestFile string
}
var errPrunedHistory = errors.New("attempt to access pruned history")
@ -145,6 +148,12 @@ func testConfigFromCLI(ctx *cli.Context) (cfg testConfig) {
} else {
cfg.traceTestFile = "queries/trace_mainnet.json"
}
if ctx.IsSet(proofTestFileFlag.Name) {
cfg.proofTestFile = ctx.String(proofTestFileFlag.Name)
} else {
cfg.proofTestFile = "queries/proof_mainnet.json"
}
cfg.historyPruneBlock = new(uint64)
*cfg.historyPruneBlock = history.PrunePoints[params.MainnetGenesisHash].BlockNumber
case ctx.Bool(testSepoliaFlag.Name):
@ -164,6 +173,12 @@ func testConfigFromCLI(ctx *cli.Context) (cfg testConfig) {
} else {
cfg.traceTestFile = "queries/trace_sepolia.json"
}
if ctx.IsSet(proofTestFileFlag.Name) {
cfg.proofTestFile = ctx.String(proofTestFileFlag.Name)
} else {
cfg.proofTestFile = "queries/proof_sepolia.json"
}
cfg.historyPruneBlock = new(uint64)
*cfg.historyPruneBlock = history.PrunePoints[params.SepoliaGenesisHash].BlockNumber
default:
@ -171,6 +186,7 @@ func testConfigFromCLI(ctx *cli.Context) (cfg testConfig) {
cfg.filterQueryFile = ctx.String(filterQueryFileFlag.Name)
cfg.historyTestFile = ctx.String(historyTestFileFlag.Name)
cfg.traceTestFile = ctx.String(traceTestFileFlag.Name)
cfg.proofTestFile = ctx.String(proofTestFileFlag.Name)
}
return cfg
}
@ -222,11 +238,13 @@ func runTestCmd(ctx *cli.Context) error {
filterSuite := newFilterTestSuite(cfg)
historySuite := newHistoryTestSuite(cfg)
traceSuite := newTraceTestSuite(cfg, ctx)
proofSuite := newProofTestSuite(cfg, ctx)
// Filter test cases.
tests := filterSuite.allTests()
tests = append(tests, historySuite.allTests()...)
tests = append(tests, traceSuite.allTests()...)
tests = append(tests, proofSuite.allTests()...)
utests := filterTests(tests, ctx.String(testPatternFlag.Name), func(t workloadTest) bool {
if t.Slow && !ctx.Bool(testSlowFlag.Name) {

View file

@ -8,6 +8,7 @@
package bitutil
import (
"crypto/subtle"
"runtime"
"unsafe"
)
@ -17,46 +18,16 @@ const supportsUnaligned = runtime.GOARCH == "386" || runtime.GOARCH == "amd64" |
// XORBytes xors the bytes in a and b. The destination is assumed to have enough
// space. Returns the number of bytes xor'd.
//
// If dst does not have length at least n,
// XORBytes panics without writing anything to dst.
//
// dst and x or y may overlap exactly or not at all,
// otherwise XORBytes may panic.
//
// Deprecated: use crypto/subtle.XORBytes
func XORBytes(dst, a, b []byte) int {
if supportsUnaligned {
return fastXORBytes(dst, a, b)
}
return safeXORBytes(dst, a, b)
}
// fastXORBytes xors in bulk. It only works on architectures that support
// unaligned read/writes.
func fastXORBytes(dst, a, b []byte) int {
n := len(a)
if len(b) < n {
n = len(b)
}
w := n / wordSize
if w > 0 {
dw := *(*[]uintptr)(unsafe.Pointer(&dst))
aw := *(*[]uintptr)(unsafe.Pointer(&a))
bw := *(*[]uintptr)(unsafe.Pointer(&b))
for i := 0; i < w; i++ {
dw[i] = aw[i] ^ bw[i]
}
}
for i := n - n%wordSize; i < n; i++ {
dst[i] = a[i] ^ b[i]
}
return n
}
// safeXORBytes xors one by one. It works on all architectures, independent if
// it supports unaligned read/writes or not.
func safeXORBytes(dst, a, b []byte) int {
n := len(a)
if len(b) < n {
n = len(b)
}
for i := 0; i < n; i++ {
dst[i] = a[i] ^ b[i]
}
return n
return subtle.XORBytes(dst, a, b)
}
// ANDBytes ands the bytes in a and b. The destination is assumed to have enough

View file

@ -29,7 +29,7 @@ func TestXOR(t *testing.T) {
d2 := make([]byte, 1023+alignD)[alignD:]
XORBytes(d1, p, q)
safeXORBytes(d2, p, q)
naiveXOR(d2, p, q)
if !bytes.Equal(d1, d2) {
t.Error("not equal", d1, d2)
}
@ -38,6 +38,18 @@ func TestXOR(t *testing.T) {
}
}
// naiveXOR xors bytes one by one.
func naiveXOR(dst, a, b []byte) int {
n := len(a)
if len(b) < n {
n = len(b)
}
for i := 0; i < n; i++ {
dst[i] = a[i] ^ b[i]
}
return n
}
// Tests that bitwise AND works for various alignments.
func TestAND(t *testing.T) {
for alignP := 0; alignP < 2; alignP++ {
@ -134,7 +146,7 @@ func benchmarkBaseXOR(b *testing.B, size int) {
p, q := make([]byte, size), make([]byte, size)
for i := 0; i < b.N; i++ {
safeXORBytes(p, p, q)
naiveXOR(p, p, q)
}
}

View file

@ -365,46 +365,7 @@ func (beacon *Beacon) FinalizeAndAssemble(chain consensus.ChainHeaderReader, hea
header.Root = state.IntermediateRoot(true)
// Assemble the final block.
block := types.NewBlock(header, body, receipts, trie.NewStackTrie(nil))
// Create the block witness and attach to block.
// This step needs to happen as late as possible to catch all access events.
if chain.Config().IsVerkle(header.Number, header.Time) {
keys := state.AccessEvents().Keys()
// Open the pre-tree to prove the pre-state against
parent := chain.GetHeaderByNumber(header.Number.Uint64() - 1)
if parent == nil {
return nil, fmt.Errorf("nil parent header for block %d", header.Number)
}
preTrie, err := state.Database().OpenTrie(parent.Root)
if err != nil {
return nil, fmt.Errorf("error opening pre-state tree root: %w", err)
}
postTrie := state.GetTrie()
if postTrie == nil {
return nil, errors.New("post-state tree is not available")
}
vktPreTrie, okpre := preTrie.(*trie.VerkleTrie)
vktPostTrie, okpost := postTrie.(*trie.VerkleTrie)
// The witness is only attached iff both parent and current block are
// using verkle tree.
if okpre && okpost {
if len(keys) > 0 {
verkleProof, stateDiff, err := vktPreTrie.Proof(vktPostTrie, keys)
if err != nil {
return nil, fmt.Errorf("error generating verkle proof for block %d: %w", header.Number, err)
}
block = block.WithWitness(&types.ExecutionWitness{
StateDiff: stateDiff,
VerkleProof: verkleProof,
})
}
}
}
return block, nil
return types.NewBlock(header, body, receipts, trie.NewStackTrie(nil)), nil
}
// Seal generates a new sealing request for the given input block and pushes

View file

@ -305,11 +305,11 @@ func (c *Clique) verifyHeader(chain consensus.ChainHeaderReader, header *types.H
// Verify the non-existence of cancun-specific header fields
switch {
case header.ExcessBlobGas != nil:
return fmt.Errorf("invalid excessBlobGas: have %d, expected nil", header.ExcessBlobGas)
return fmt.Errorf("invalid excessBlobGas: have %d, expected nil", *header.ExcessBlobGas)
case header.BlobGasUsed != nil:
return fmt.Errorf("invalid blobGasUsed: have %d, expected nil", header.BlobGasUsed)
return fmt.Errorf("invalid blobGasUsed: have %d, expected nil", *header.BlobGasUsed)
case header.ParentBeaconRoot != nil:
return fmt.Errorf("invalid parentBeaconRoot, have %#x, expected nil", header.ParentBeaconRoot)
return fmt.Errorf("invalid parentBeaconRoot, have %#x, expected nil", *header.ParentBeaconRoot)
}
// All basic checks passed, verify cascading fields
return c.verifyCascadingFields(chain, header, parents)

View file

@ -278,11 +278,11 @@ func (ethash *Ethash) verifyHeader(chain consensus.ChainHeaderReader, header, pa
// Verify the non-existence of cancun-specific header fields
switch {
case header.ExcessBlobGas != nil:
return fmt.Errorf("invalid excessBlobGas: have %d, expected nil", header.ExcessBlobGas)
return fmt.Errorf("invalid excessBlobGas: have %d, expected nil", *header.ExcessBlobGas)
case header.BlobGasUsed != nil:
return fmt.Errorf("invalid blobGasUsed: have %d, expected nil", header.BlobGasUsed)
return fmt.Errorf("invalid blobGasUsed: have %d, expected nil", *header.BlobGasUsed)
case header.ParentBeaconRoot != nil:
return fmt.Errorf("invalid parentBeaconRoot, have %#x, expected nil", header.ParentBeaconRoot)
return fmt.Errorf("invalid parentBeaconRoot, have %#x, expected nil", *header.ParentBeaconRoot)
}
// Add some fake checks for tests
if ethash.fakeDelay != nil {

View file

@ -142,7 +142,7 @@ func (p *terminalPrompter) PromptPassword(prompt string) (passwd string, err err
// PromptConfirm displays the given prompt to the user and requests a boolean
// choice to be made, returning that choice.
func (p *terminalPrompter) PromptConfirm(prompt string) (bool, error) {
input, err := p.Prompt(prompt + " [y/n] ")
input, err := p.PromptInput(prompt + " [y/n] ")
if len(input) > 0 && strings.EqualFold(input[:1], "y") {
return true, nil
}

View file

@ -75,6 +75,7 @@ var (
storageReadTimer = metrics.NewRegisteredResettingTimer("chain/storage/reads", nil)
storageUpdateTimer = metrics.NewRegisteredResettingTimer("chain/storage/updates", nil)
storageCommitTimer = metrics.NewRegisteredResettingTimer("chain/storage/commits", nil)
codeReadTimer = metrics.NewRegisteredResettingTimer("chain/code/reads", nil)
accountCacheHitMeter = metrics.NewRegisteredMeter("chain/account/reads/cache/process/hit", nil)
accountCacheMissMeter = metrics.NewRegisteredMeter("chain/account/reads/cache/process/miss", nil)
@ -88,6 +89,7 @@ var (
accountReadSingleTimer = metrics.NewRegisteredResettingTimer("chain/account/single/reads", nil)
storageReadSingleTimer = metrics.NewRegisteredResettingTimer("chain/storage/single/reads", nil)
codeReadSingleTimer = metrics.NewRegisteredResettingTimer("chain/code/single/reads", nil)
snapshotCommitTimer = metrics.NewRegisteredResettingTimer("chain/snapshot/commits", nil)
triedbCommitTimer = metrics.NewRegisteredResettingTimer("chain/triedb/commits", nil)
@ -198,6 +200,10 @@ type BlockChainConfig struct {
// StateSizeTracking indicates whether the state size tracking is enabled.
StateSizeTracking bool
// SlowBlockThreshold is the block execution time threshold beyond which
// detailed statistics will be logged.
SlowBlockThreshold time.Duration
}
// DefaultConfig returns the default config.
@ -337,7 +343,8 @@ type BlockChain struct {
logger *tracing.Hooks
stateSizer *state.SizeTracker // State size tracking
lastForkReadyAlert time.Time // Last time there was a fork readiness print out
lastForkReadyAlert time.Time // Last time there was a fork readiness print out
slowBlockThreshold time.Duration // Block execution time threshold beyond which detailed statistics will be logged
}
// NewBlockChain returns a fully initialised block chain using information
@ -372,19 +379,20 @@ func NewBlockChain(db ethdb.Database, genesis *Genesis, engine consensus.Engine,
log.Info("")
bc := &BlockChain{
chainConfig: chainConfig,
cfg: cfg,
db: db,
triedb: triedb,
triegc: prque.New[int64, common.Hash](nil),
chainmu: syncx.NewClosableMutex(),
bodyCache: lru.NewCache[common.Hash, *types.Body](bodyCacheLimit),
bodyRLPCache: lru.NewCache[common.Hash, rlp.RawValue](bodyCacheLimit),
receiptsCache: lru.NewCache[common.Hash, []*types.Receipt](receiptsCacheLimit),
blockCache: lru.NewCache[common.Hash, *types.Block](blockCacheLimit),
txLookupCache: lru.NewCache[common.Hash, txLookup](txLookupCacheLimit),
engine: engine,
logger: cfg.VmConfig.Tracer,
chainConfig: chainConfig,
cfg: cfg,
db: db,
triedb: triedb,
triegc: prque.New[int64, common.Hash](nil),
chainmu: syncx.NewClosableMutex(),
bodyCache: lru.NewCache[common.Hash, *types.Body](bodyCacheLimit),
bodyRLPCache: lru.NewCache[common.Hash, rlp.RawValue](bodyCacheLimit),
receiptsCache: lru.NewCache[common.Hash, []*types.Receipt](receiptsCacheLimit),
blockCache: lru.NewCache[common.Hash, *types.Block](blockCacheLimit),
txLookupCache: lru.NewCache[common.Hash, txLookup](txLookupCacheLimit),
engine: engine,
logger: cfg.VmConfig.Tracer,
slowBlockThreshold: cfg.SlowBlockThreshold,
}
bc.hc, err = NewHeaderChain(db, chainConfig, engine, bc.insertStopped)
if err != nil {
@ -945,7 +953,8 @@ func (bc *BlockChain) rewindPathHead(head *types.Header, root common.Hash) (*typ
// Recover if the target state if it's not available yet.
if !bc.HasState(head.Root) {
if err := bc.triedb.Recover(head.Root); err != nil {
log.Crit("Failed to rollback state", "err", err)
log.Error("Failed to rollback state, resetting to genesis", "err", err)
return bc.genesisBlock.Header(), rootNumber
}
}
log.Info("Rewound to block with state", "number", head.Number, "hash", head.Hash())
@ -1096,25 +1105,60 @@ func (bc *BlockChain) setHeadBeyondRoot(head uint64, time uint64, root common.Ha
bc.txLookupCache.Purge()
// Clear safe block, finalized block if needed
if safe := bc.CurrentSafeBlock(); safe != nil && head < safe.Number.Uint64() {
headBlock := bc.CurrentBlock()
if safe := bc.CurrentSafeBlock(); safe != nil && headBlock.Number.Uint64() < safe.Number.Uint64() {
log.Warn("SetHead invalidated safe block")
bc.SetSafe(nil)
}
if finalized := bc.CurrentFinalBlock(); finalized != nil && head < finalized.Number.Uint64() {
if finalized := bc.CurrentFinalBlock(); finalized != nil && headBlock.Number.Uint64() < finalized.Number.Uint64() {
log.Error("SetHead invalidated finalized block")
bc.SetFinalized(nil)
}
return rootNumber, bc.loadLastState()
}
// SnapSyncCommitHead sets the current head block to the one defined by the hash
// irrelevant what the chain contents were prior.
func (bc *BlockChain) SnapSyncCommitHead(hash common.Hash) error {
// SnapSyncStart disables the underlying databases (such as the trie DB and the
// optional state snapshot) to prevent potential concurrent mutations between
// snap sync and other chain operations.
func (bc *BlockChain) SnapSyncStart() error {
if !bc.chainmu.TryLock() {
return errChainStopped
}
defer bc.chainmu.Unlock()
// Snap sync will directly modify the persistent state, making the entire
// trie database unusable until the state is fully synced. To prevent any
// subsequent state reads, explicitly disable the trie database and state
// syncer is responsible to address and correct any state missing.
if bc.TrieDB().Scheme() == rawdb.PathScheme {
if err := bc.TrieDB().Disable(); err != nil {
return err
}
}
// Snap sync uses the snapshot namespace to store potentially flaky data until
// sync completely heals and finishes. Pause snapshot maintenance in the mean-
// time to prevent access.
if snapshots := bc.Snapshots(); snapshots != nil { // Only nil in tests
snapshots.Disable()
}
return nil
}
// SnapSyncComplete sets the current head block to the block identified by the
// given hash, regardless of the chain contents prior to snap sync. It is
// invoked once snap sync completes and assumes that SnapSyncStart was called
// previously.
func (bc *BlockChain) SnapSyncComplete(hash common.Hash) error {
// Make sure that both the block as well at its state trie exists
block := bc.GetBlockByHash(hash)
if block == nil {
return fmt.Errorf("non existent block [%x..]", hash[:4])
}
if !bc.chainmu.TryLock() {
return errChainStopped
}
defer bc.chainmu.Unlock()
// Reset the trie database with the fresh snap synced state.
root := block.Root()
if bc.triedb.Scheme() == rawdb.PathScheme {
@ -1125,19 +1169,16 @@ func (bc *BlockChain) SnapSyncCommitHead(hash common.Hash) error {
if !bc.HasState(root) {
return fmt.Errorf("non existent state [%x..]", root[:4])
}
// If all checks out, manually set the head block.
if !bc.chainmu.TryLock() {
return errChainStopped
}
bc.currentBlock.Store(block.Header())
headBlockGauge.Update(int64(block.NumberU64()))
bc.chainmu.Unlock()
// Destroy any existing state snapshot and regenerate it in the background,
// also resuming the normal maintenance of any previously paused snapshot.
if bc.snaps != nil {
bc.snaps.Rebuild(root)
}
// If all checks out, manually set the head block.
bc.currentBlock.Store(block.Header())
headBlockGauge.Update(int64(block.NumberU64()))
log.Info("Committed new head block", "number", block.Number(), "hash", hash)
return nil
}
@ -1596,22 +1637,33 @@ func (bc *BlockChain) writeBlockWithState(block *types.Block, receipts []*types.
//
// Note all the components of block(hash->number map, header, body, receipts)
// should be written atomically. BlockBatch is used for containing all components.
blockBatch := bc.db.NewBatch()
rawdb.WriteBlock(blockBatch, block)
rawdb.WriteReceipts(blockBatch, block.Hash(), block.NumberU64(), receipts)
rawdb.WritePreimages(blockBatch, statedb.Preimages())
if err := blockBatch.Write(); err != nil {
var (
batch = bc.db.NewBatch()
start = time.Now()
)
rawdb.WriteBlock(batch, block)
rawdb.WriteReceipts(batch, block.Hash(), block.NumberU64(), receipts)
rawdb.WritePreimages(batch, statedb.Preimages())
if err := batch.Write(); err != nil {
log.Crit("Failed to write block into disk", "err", err)
}
// Commit all cached state changes into underlying memory database.
root, stateUpdate, err := statedb.CommitWithUpdate(block.NumberU64(), bc.chainConfig.IsEIP158(block.Number()), bc.chainConfig.IsCancun(block.Number(), block.Time()))
log.Debug("Committed block data", "size", common.StorageSize(batch.ValueSize()), "elapsed", common.PrettyDuration(time.Since(start)))
var (
err error
root common.Hash
isEIP158 = bc.chainConfig.IsEIP158(block.Number())
isCancun = bc.chainConfig.IsCancun(block.Number(), block.Time())
)
if bc.stateSizer == nil {
root, err = statedb.Commit(block.NumberU64(), isEIP158, isCancun)
} else {
root, err = statedb.CommitAndTrack(block.NumberU64(), isEIP158, isCancun, bc.stateSizer)
}
if err != nil {
return err
}
// Emit the state update to the state sizestats if it's active
if bc.stateSizer != nil {
bc.stateSizer.Notify(stateUpdate)
}
// If node is running in path mode, skip explicit gc operation
// which is unnecessary in this mode.
if bc.triedb.Scheme() == rawdb.PathScheme {
@ -1847,7 +1899,7 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool, makeWitness
// still need re-execution to generate snapshots that are missing
case err != nil && !errors.Is(err, ErrKnownBlock):
stats.ignored += len(it.chain)
bc.reportBlock(block, nil, err)
bc.reportBadBlock(block, nil, err)
return nil, it.index, err
}
// Track the singleton witness from this chain insertion (if any)
@ -1915,6 +1967,14 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool, makeWitness
if err != nil {
return nil, it.index, err
}
res.stats.reportMetrics()
// Log slow block only if a single block is inserted (usually after the
// initial sync) to not overwhelm the users.
if len(chain) == 1 {
res.stats.logSlow(block, bc.slowBlockThreshold)
}
// Report the import stats before returning the various results
stats.processed++
stats.usedGas += res.usedGas
@ -1975,15 +2035,20 @@ type blockProcessingResult struct {
procTime time.Duration
status WriteStatus
witness *stateless.Witness
stats *ExecuteStats
}
func (bpr *blockProcessingResult) Witness() *stateless.Witness {
return bpr.witness
}
func (bpr *blockProcessingResult) Stats() *ExecuteStats {
return bpr.stats
}
// ProcessBlock executes and validates the given block. If there was no error
// it writes the block and associated state to database.
func (bc *BlockChain) ProcessBlock(parentRoot common.Hash, block *types.Block, setHead bool, makeWitness bool) (_ *blockProcessingResult, blockEndErr error) {
func (bc *BlockChain) ProcessBlock(parentRoot common.Hash, block *types.Block, setHead bool, makeWitness bool) (result *blockProcessingResult, blockEndErr error) {
var (
err error
startTime = time.Now()
@ -2017,16 +2082,22 @@ func (bc *BlockChain) ProcessBlock(parentRoot common.Hash, block *types.Block, s
}
// Upload the statistics of reader at the end
defer func() {
stats := prefetch.GetStats()
accountCacheHitPrefetchMeter.Mark(stats.AccountHit)
accountCacheMissPrefetchMeter.Mark(stats.AccountMiss)
storageCacheHitPrefetchMeter.Mark(stats.StorageHit)
storageCacheMissPrefetchMeter.Mark(stats.StorageMiss)
stats = process.GetStats()
accountCacheHitMeter.Mark(stats.AccountHit)
accountCacheMissMeter.Mark(stats.AccountMiss)
storageCacheHitMeter.Mark(stats.StorageHit)
storageCacheMissMeter.Mark(stats.StorageMiss)
pStat := prefetch.GetStats()
accountCacheHitPrefetchMeter.Mark(pStat.AccountCacheHit)
accountCacheMissPrefetchMeter.Mark(pStat.AccountCacheMiss)
storageCacheHitPrefetchMeter.Mark(pStat.StorageCacheHit)
storageCacheMissPrefetchMeter.Mark(pStat.StorageCacheMiss)
rStat := process.GetStats()
accountCacheHitMeter.Mark(rStat.AccountCacheHit)
accountCacheMissMeter.Mark(rStat.AccountCacheMiss)
storageCacheHitMeter.Mark(rStat.StorageCacheHit)
storageCacheMissMeter.Mark(rStat.StorageCacheMiss)
if result != nil {
result.stats.StatePrefetchCacheStats = pStat
result.stats.StateReadCacheStats = rStat
}
}()
go func(start time.Time, throwaway *state.StateDB, block *types.Block) {
@ -2083,14 +2154,14 @@ func (bc *BlockChain) ProcessBlock(parentRoot common.Hash, block *types.Block, s
pstart := time.Now()
res, err := bc.processor.Process(block, statedb, bc.cfg.VmConfig)
if err != nil {
bc.reportBlock(block, res, err)
bc.reportBadBlock(block, res, err)
return nil, err
}
ptime := time.Since(pstart)
vstart := time.Now()
if err := bc.validator.ValidateState(block, statedb, res, false); err != nil {
bc.reportBlock(block, res, err)
bc.reportBadBlock(block, res, err)
return nil, err
}
vtime := time.Since(vstart)
@ -2124,26 +2195,30 @@ func (bc *BlockChain) ProcessBlock(parentRoot common.Hash, block *types.Block, s
}
}
xvtime := time.Since(xvstart)
proctime := time.Since(startTime) // processing + validation + cross validation
var (
xvtime = time.Since(xvstart)
proctime = time.Since(startTime) // processing + validation + cross validation
stats = &ExecuteStats{}
)
// Update the metrics touched during block processing and validation
accountReadTimer.Update(statedb.AccountReads) // Account reads are complete(in processing)
storageReadTimer.Update(statedb.StorageReads) // Storage reads are complete(in processing)
if statedb.AccountLoaded != 0 {
accountReadSingleTimer.Update(statedb.AccountReads / time.Duration(statedb.AccountLoaded))
}
if statedb.StorageLoaded != 0 {
storageReadSingleTimer.Update(statedb.StorageReads / time.Duration(statedb.StorageLoaded))
}
accountUpdateTimer.Update(statedb.AccountUpdates) // Account updates are complete(in validation)
storageUpdateTimer.Update(statedb.StorageUpdates) // Storage updates are complete(in validation)
accountHashTimer.Update(statedb.AccountHashes) // Account hashes are complete(in validation)
triehash := statedb.AccountHashes // The time spent on tries hashing
trieUpdate := statedb.AccountUpdates + statedb.StorageUpdates // The time spent on tries update
blockExecutionTimer.Update(ptime - (statedb.AccountReads + statedb.StorageReads)) // The time spent on EVM processing
blockValidationTimer.Update(vtime - (triehash + trieUpdate)) // The time spent on block validation
blockCrossValidationTimer.Update(xvtime) // The time spent on stateless cross validation
stats.AccountReads = statedb.AccountReads // Account reads are complete(in processing)
stats.StorageReads = statedb.StorageReads // Storage reads are complete(in processing)
stats.AccountUpdates = statedb.AccountUpdates // Account updates are complete(in validation)
stats.StorageUpdates = statedb.StorageUpdates // Storage updates are complete(in validation)
stats.AccountHashes = statedb.AccountHashes // Account hashes are complete(in validation)
stats.CodeReads = statedb.CodeReads
stats.AccountLoaded = statedb.AccountLoaded
stats.AccountUpdated = statedb.AccountUpdated
stats.AccountDeleted = statedb.AccountDeleted
stats.StorageLoaded = statedb.StorageLoaded
stats.StorageUpdated = int(statedb.StorageUpdated.Load())
stats.StorageDeleted = int(statedb.StorageDeleted.Load())
stats.CodeLoaded = statedb.CodeLoaded
stats.Execution = ptime - (statedb.AccountReads + statedb.StorageReads + statedb.CodeReads) // The time spent on EVM processing
stats.Validation = vtime - (statedb.AccountHashes + statedb.AccountUpdates + statedb.StorageUpdates) // The time spent on block validation
stats.CrossValidation = xvtime // The time spent on stateless cross validation
// Write the block to the chain and get the status.
var (
@ -2165,24 +2240,22 @@ func (bc *BlockChain) ProcessBlock(parentRoot common.Hash, block *types.Block, s
}
// Update the metrics touched during block commit
accountCommitTimer.Update(statedb.AccountCommits) // Account commits are complete, we can mark them
storageCommitTimer.Update(statedb.StorageCommits) // Storage commits are complete, we can mark them
snapshotCommitTimer.Update(statedb.SnapshotCommits) // Snapshot commits are complete, we can mark them
triedbCommitTimer.Update(statedb.TrieDBCommits) // Trie database commits are complete, we can mark them
stats.AccountCommits = statedb.AccountCommits // Account commits are complete, we can mark them
stats.StorageCommits = statedb.StorageCommits // Storage commits are complete, we can mark them
stats.SnapshotCommit = statedb.SnapshotCommits // Snapshot commits are complete, we can mark them
stats.TrieDBCommit = statedb.TrieDBCommits // Trie database commits are complete, we can mark them
stats.BlockWrite = time.Since(wstart) - max(statedb.AccountCommits, statedb.StorageCommits) /* concurrent */ - statedb.SnapshotCommits - statedb.TrieDBCommits
blockWriteTimer.Update(time.Since(wstart) - max(statedb.AccountCommits, statedb.StorageCommits) /* concurrent */ - statedb.SnapshotCommits - statedb.TrieDBCommits)
elapsed := time.Since(startTime) + 1 // prevent zero division
blockInsertTimer.Update(elapsed)
// TODO(rjl493456442) generalize the ResettingTimer
mgasps := float64(res.GasUsed) * 1000 / float64(elapsed)
chainMgaspsMeter.Update(time.Duration(mgasps))
stats.TotalTime = elapsed
stats.MgasPerSecond = float64(res.GasUsed) * 1000 / float64(elapsed)
return &blockProcessingResult{
usedGas: res.GasUsed,
procTime: proctime,
status: status,
witness: witness,
stats: stats,
}, nil
}
@ -2667,8 +2740,8 @@ func (bc *BlockChain) skipBlock(err error, it *insertIterator) bool {
return false
}
// reportBlock logs a bad block error.
func (bc *BlockChain) reportBlock(block *types.Block, res *ProcessResult, err error) {
// reportBadBlock logs a bad block error.
func (bc *BlockChain) reportBadBlock(block *types.Block, res *ProcessResult, err error) {
var receipts types.Receipts
if res != nil {
receipts = res.Receipts

164
core/blockchain_stats.go Normal file
View file

@ -0,0 +1,164 @@
// 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 core
import (
"fmt"
"strings"
"time"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/log"
)
// ExecuteStats includes all the statistics of a block execution in details.
type ExecuteStats struct {
// State read times
AccountReads time.Duration // Time spent on the account reads
StorageReads time.Duration // Time spent on the storage reads
AccountHashes time.Duration // Time spent on the account trie hash
AccountUpdates time.Duration // Time spent on the account trie update
AccountCommits time.Duration // Time spent on the account trie commit
StorageUpdates time.Duration // Time spent on the storage trie update
StorageCommits time.Duration // Time spent on the storage trie commit
CodeReads time.Duration // Time spent on the contract code read
AccountLoaded int // Number of accounts loaded
AccountUpdated int // Number of accounts updated
AccountDeleted int // Number of accounts deleted
StorageLoaded int // Number of storage slots loaded
StorageUpdated int // Number of storage slots updated
StorageDeleted int // Number of storage slots deleted
CodeLoaded int // Number of contract code loaded
Execution time.Duration // Time spent on the EVM execution
Validation time.Duration // Time spent on the block validation
CrossValidation time.Duration // Optional, time spent on the block cross validation
SnapshotCommit time.Duration // Time spent on snapshot commit
TrieDBCommit time.Duration // Time spent on database commit
BlockWrite time.Duration // Time spent on block write
TotalTime time.Duration // The total time spent on block execution
MgasPerSecond float64 // The million gas processed per second
// Cache hit rates
StateReadCacheStats state.ReaderStats
StatePrefetchCacheStats state.ReaderStats
}
// reportMetrics uploads execution statistics to the metrics system.
func (s *ExecuteStats) reportMetrics() {
if s.AccountLoaded != 0 {
accountReadTimer.Update(s.AccountReads)
accountReadSingleTimer.Update(s.AccountReads / time.Duration(s.AccountLoaded))
}
if s.StorageLoaded != 0 {
storageReadTimer.Update(s.StorageReads)
storageReadSingleTimer.Update(s.StorageReads / time.Duration(s.StorageLoaded))
}
if s.CodeLoaded != 0 {
codeReadTimer.Update(s.CodeReads)
codeReadSingleTimer.Update(s.CodeReads / time.Duration(s.CodeLoaded))
}
accountUpdateTimer.Update(s.AccountUpdates) // Account updates are complete(in validation)
storageUpdateTimer.Update(s.StorageUpdates) // Storage updates are complete(in validation)
accountHashTimer.Update(s.AccountHashes) // Account hashes are complete(in validation)
accountCommitTimer.Update(s.AccountCommits) // Account commits are complete, we can mark them
storageCommitTimer.Update(s.StorageCommits) // Storage commits are complete, we can mark them
blockExecutionTimer.Update(s.Execution) // The time spent on EVM processing
blockValidationTimer.Update(s.Validation) // The time spent on block validation
blockCrossValidationTimer.Update(s.CrossValidation) // The time spent on stateless cross validation
snapshotCommitTimer.Update(s.SnapshotCommit) // Snapshot commits are complete, we can mark them
triedbCommitTimer.Update(s.TrieDBCommit) // Trie database commits are complete, we can mark them
blockWriteTimer.Update(s.BlockWrite) // The time spent on block write
blockInsertTimer.Update(s.TotalTime) // The total time spent on block execution
chainMgaspsMeter.Update(time.Duration(s.MgasPerSecond)) // TODO(rjl493456442) generalize the ResettingTimer
// Cache hit rates
accountCacheHitPrefetchMeter.Mark(s.StatePrefetchCacheStats.AccountCacheHit)
accountCacheMissPrefetchMeter.Mark(s.StatePrefetchCacheStats.AccountCacheMiss)
storageCacheHitPrefetchMeter.Mark(s.StatePrefetchCacheStats.StorageCacheHit)
storageCacheMissPrefetchMeter.Mark(s.StatePrefetchCacheStats.StorageCacheMiss)
accountCacheHitMeter.Mark(s.StateReadCacheStats.AccountCacheHit)
accountCacheMissMeter.Mark(s.StateReadCacheStats.AccountCacheMiss)
storageCacheHitMeter.Mark(s.StateReadCacheStats.StorageCacheHit)
storageCacheMissMeter.Mark(s.StateReadCacheStats.StorageCacheMiss)
}
// logSlow prints the detailed execution statistics if the block is regarded as slow.
func (s *ExecuteStats) logSlow(block *types.Block, slowBlockThreshold time.Duration) {
if slowBlockThreshold == 0 {
return
}
if s.TotalTime < slowBlockThreshold {
return
}
msg := fmt.Sprintf(`
########## SLOW BLOCK #########
Block: %v (%#x) txs: %d, mgasps: %.2f, elapsed: %v
EVM execution: %v
Validation: %v
State read: %v
Account read: %v(%d)
Storage read: %v(%d)
Code read: %v(%d)
State hash: %v
Account hash: %v
Storage hash: %v
Trie commit: %v
DB write: %v
State write: %v
Block write: %v
%s
##############################
`, block.Number(), block.Hash(), len(block.Transactions()), s.MgasPerSecond, common.PrettyDuration(s.TotalTime),
common.PrettyDuration(s.Execution),
common.PrettyDuration(s.Validation+s.CrossValidation),
// State read
common.PrettyDuration(s.AccountReads+s.StorageReads+s.CodeReads),
common.PrettyDuration(s.AccountReads), s.AccountLoaded,
common.PrettyDuration(s.StorageReads), s.StorageLoaded,
common.PrettyDuration(s.CodeReads), s.CodeLoaded,
// State hash
common.PrettyDuration(s.AccountHashes+s.AccountUpdates+s.StorageUpdates+max(s.AccountCommits, s.StorageCommits)),
common.PrettyDuration(s.AccountHashes+s.AccountUpdates),
common.PrettyDuration(s.StorageUpdates),
common.PrettyDuration(max(s.AccountCommits, s.StorageCommits)),
// Database commit
common.PrettyDuration(s.TrieDBCommit+s.SnapshotCommit+s.BlockWrite),
common.PrettyDuration(s.TrieDBCommit+s.SnapshotCommit),
common.PrettyDuration(s.BlockWrite),
// cache statistics
s.StateReadCacheStats)
for _, line := range strings.Split(msg, "\n") {
if line == "" {
continue
}
log.Info(line)
}
}

View file

@ -162,12 +162,12 @@ func testBlockChainImport(chain types.Blocks, blockchain *BlockChain) error {
}
res, err := blockchain.processor.Process(block, statedb, vm.Config{})
if err != nil {
blockchain.reportBlock(block, res, err)
blockchain.reportBadBlock(block, res, err)
return err
}
err = blockchain.validator.ValidateState(block, statedb, res, false)
if err != nil {
blockchain.reportBlock(block, res, err)
blockchain.reportBadBlock(block, res, err)
return err
}
@ -4515,3 +4515,45 @@ func TestGetCanonicalReceipt(t *testing.T) {
}
}
}
// TestSetHeadBeyondRootFinalizedBug tests the issue where the finalized block
// is not cleared when rewinding past it using setHeadBeyondRoot.
func TestSetHeadBeyondRootFinalizedBug(t *testing.T) {
// Create a clean blockchain with 100 blocks using PathScheme (PBSS)
_, _, blockchain, err := newCanonical(ethash.NewFaker(), 100, true, rawdb.PathScheme)
if err != nil {
t.Fatalf("failed to create pristine chain: %v", err)
}
defer blockchain.Stop()
// Set the "Finalized" marker to the current Head (Block 100)
headBlock := blockchain.CurrentBlock()
if headBlock.Number.Uint64() != 100 {
t.Fatalf("Setup failed: expected head 100, got %d", headBlock.Number.Uint64())
}
blockchain.SetFinalized(headBlock)
// Verify setup
if blockchain.CurrentFinalBlock().Number.Uint64() != 100 {
t.Fatalf("Setup failed: Finalized block should be 100")
}
targetBlock := blockchain.GetBlockByNumber(50)
// Call setHeadBeyondRoot with:
// head = 100
// repair = true
if _, err := blockchain.setHeadBeyondRoot(100, 0, targetBlock.Root(), true); err != nil {
t.Fatalf("Failed to rewind: %v", err)
}
currentFinal := blockchain.CurrentFinalBlock()
currentHead := blockchain.CurrentBlock().Number.Uint64()
// The previous finalized block (100) is now invalid because we rewound to 50.
// The function should have cleared the finalized marker (set to nil).
if currentFinal != nil && currentFinal.Number.Uint64() > currentHead {
t.Errorf("Chain Head: %d , Finalized Block: %d , Finalized block was >= head block.",
currentHead,
currentFinal.Number.Uint64())
}
}

View file

@ -434,7 +434,7 @@ func (f *FilterMaps) safeDeleteWithLogs(deleteFn func(db ethdb.KeyValueStore, ha
lastLogPrinted = start
)
switch err := deleteFn(f.db, f.hashScheme, func(deleted bool) bool {
if deleted && !logPrinted || time.Since(lastLogPrinted) > time.Second*10 {
if deleted && (!logPrinted || time.Since(lastLogPrinted) > time.Second*10) {
log.Info(action+" in progress...", "elapsed", common.PrettyDuration(time.Since(start)))
logPrinted, lastLogPrinted = true, time.Now()
}

View file

@ -97,7 +97,7 @@ func LoadTransitionState(db ethdb.KeyValueReader, root common.Hash, isVerkle boo
// Initialize the first transition state, with the "ended"
// field set to true if the database was created
// as a verkle database.
log.Debug("no transition state found, starting fresh", "is verkle", db)
log.Debug("no transition state found, starting fresh", "verkle", isVerkle)
// Start with a fresh state
ts = &TransitionState{Ended: isVerkle}

View file

@ -149,6 +149,8 @@ func InspectFreezerTable(ancient string, freezerName string, tableName string, s
path, tables = resolveChainFreezerDir(ancient), chainFreezerTableConfigs
case MerkleStateFreezerName, VerkleStateFreezerName:
path, tables = filepath.Join(ancient, freezerName), stateFreezerTableConfigs
case MerkleTrienodeFreezerName, VerkleTrienodeFreezerName:
path, tables = filepath.Join(ancient, freezerName), trienodeFreezerTableConfigs
default:
return fmt.Errorf("unknown freezer, supported ones: %v", freezers)
}

View file

@ -91,6 +91,13 @@ func (t *memoryTable) truncateHead(items uint64) error {
if items < t.offset {
return errors.New("truncation below tail")
}
for i := int(items - t.offset); i < len(t.data); i++ {
if t.size > uint64(len(t.data[i])) {
t.size -= uint64(len(t.data[i]))
} else {
t.size = 0
}
}
t.data = t.data[:items-t.offset]
t.items = items
return nil
@ -108,6 +115,13 @@ func (t *memoryTable) truncateTail(items uint64) error {
if t.items < items {
return errors.New("truncation above head")
}
for i := uint64(0); i < items-t.offset; i++ {
if t.size > uint64(len(t.data[i])) {
t.size -= uint64(len(t.data[i]))
} else {
t.size = 0
}
}
t.data = t.data[items-t.offset:]
t.offset = items
return nil

View file

@ -23,7 +23,7 @@ import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/math"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/trie/utils"
"github.com/ethereum/go-ethereum/trie/bintrie"
"github.com/holiman/uint256"
)
@ -45,15 +45,12 @@ var zeroTreeIndex uint256.Int
type AccessEvents struct {
branches map[branchAccessKey]mode
chunks map[chunkAccessKey]mode
pointCache *utils.PointCache
}
func NewAccessEvents(pointCache *utils.PointCache) *AccessEvents {
func NewAccessEvents() *AccessEvents {
return &AccessEvents{
branches: make(map[branchAccessKey]mode),
chunks: make(map[chunkAccessKey]mode),
pointCache: pointCache,
branches: make(map[branchAccessKey]mode),
chunks: make(map[chunkAccessKey]mode),
}
}
@ -75,8 +72,11 @@ func (ae *AccessEvents) Keys() [][]byte {
// TODO: consider if parallelizing this is worth it, probably depending on len(ae.chunks).
keys := make([][]byte, 0, len(ae.chunks))
for chunk := range ae.chunks {
basePoint := ae.pointCache.Get(chunk.addr[:])
key := utils.GetTreeKeyWithEvaluatedAddress(basePoint, &chunk.treeIndex, chunk.leafKey)
var offset [32]byte
treeIndexBytes := chunk.treeIndex.Bytes32()
copy(offset[:31], treeIndexBytes[1:])
offset[31] = chunk.leafKey
key := bintrie.GetBinaryTreeKey(chunk.addr, offset[:])
keys = append(keys, key)
}
return keys
@ -84,9 +84,8 @@ func (ae *AccessEvents) Keys() [][]byte {
func (ae *AccessEvents) Copy() *AccessEvents {
cpy := &AccessEvents{
branches: maps.Clone(ae.branches),
chunks: maps.Clone(ae.chunks),
pointCache: ae.pointCache,
branches: maps.Clone(ae.branches),
chunks: maps.Clone(ae.chunks),
}
return cpy
}
@ -95,12 +94,12 @@ func (ae *AccessEvents) Copy() *AccessEvents {
// member fields of an account.
func (ae *AccessEvents) AddAccount(addr common.Address, isWrite bool, availableGas uint64) uint64 {
var gas uint64 // accumulate the consumed gas
consumed, expected := ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.BasicDataLeafKey, isWrite, availableGas)
consumed, expected := ae.touchAddressAndChargeGas(addr, zeroTreeIndex, bintrie.BasicDataLeafKey, isWrite, availableGas)
if consumed < expected {
return expected
}
gas += consumed
consumed, expected = ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeHashLeafKey, isWrite, availableGas-consumed)
consumed, expected = ae.touchAddressAndChargeGas(addr, zeroTreeIndex, bintrie.CodeHashLeafKey, isWrite, availableGas-consumed)
if consumed < expected {
return expected + gas
}
@ -112,7 +111,7 @@ func (ae *AccessEvents) AddAccount(addr common.Address, isWrite bool, availableG
// cold member fields of an account, that need to be touched when making a message
// call to that account.
func (ae *AccessEvents) MessageCallGas(destination common.Address, availableGas uint64) uint64 {
_, expected := ae.touchAddressAndChargeGas(destination, zeroTreeIndex, utils.BasicDataLeafKey, false, availableGas)
_, expected := ae.touchAddressAndChargeGas(destination, zeroTreeIndex, bintrie.BasicDataLeafKey, false, availableGas)
if expected == 0 {
expected = params.WarmStorageReadCostEIP2929
}
@ -122,11 +121,11 @@ func (ae *AccessEvents) MessageCallGas(destination common.Address, availableGas
// ValueTransferGas returns the gas to be charged for each of the currently
// cold balance member fields of the caller and the callee accounts.
func (ae *AccessEvents) ValueTransferGas(callerAddr, targetAddr common.Address, availableGas uint64) uint64 {
_, expected1 := ae.touchAddressAndChargeGas(callerAddr, zeroTreeIndex, utils.BasicDataLeafKey, true, availableGas)
_, expected1 := ae.touchAddressAndChargeGas(callerAddr, zeroTreeIndex, bintrie.BasicDataLeafKey, true, availableGas)
if expected1 > availableGas {
return expected1
}
_, expected2 := ae.touchAddressAndChargeGas(targetAddr, zeroTreeIndex, utils.BasicDataLeafKey, true, availableGas-expected1)
_, expected2 := ae.touchAddressAndChargeGas(targetAddr, zeroTreeIndex, bintrie.BasicDataLeafKey, true, availableGas-expected1)
if expected1+expected2 == 0 {
return params.WarmStorageReadCostEIP2929
}
@ -138,8 +137,8 @@ func (ae *AccessEvents) ValueTransferGas(callerAddr, targetAddr common.Address,
// address collision is done before the transfer, and so no write
// are guaranteed to happen at this point.
func (ae *AccessEvents) ContractCreatePreCheckGas(addr common.Address, availableGas uint64) uint64 {
consumed, expected1 := ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.BasicDataLeafKey, false, availableGas)
_, expected2 := ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeHashLeafKey, false, availableGas-consumed)
consumed, expected1 := ae.touchAddressAndChargeGas(addr, zeroTreeIndex, bintrie.BasicDataLeafKey, false, availableGas)
_, expected2 := ae.touchAddressAndChargeGas(addr, zeroTreeIndex, bintrie.CodeHashLeafKey, false, availableGas-consumed)
return expected1 + expected2
}
@ -147,9 +146,9 @@ func (ae *AccessEvents) ContractCreatePreCheckGas(addr common.Address, available
// a contract creation.
func (ae *AccessEvents) ContractCreateInitGas(addr common.Address, availableGas uint64) (uint64, uint64) {
var gas uint64
consumed, expected1 := ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.BasicDataLeafKey, true, availableGas)
consumed, expected1 := ae.touchAddressAndChargeGas(addr, zeroTreeIndex, bintrie.BasicDataLeafKey, true, availableGas)
gas += consumed
consumed, expected2 := ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeHashLeafKey, true, availableGas-consumed)
consumed, expected2 := ae.touchAddressAndChargeGas(addr, zeroTreeIndex, bintrie.CodeHashLeafKey, true, availableGas-consumed)
gas += consumed
return gas, expected1 + expected2
}
@ -157,20 +156,20 @@ func (ae *AccessEvents) ContractCreateInitGas(addr common.Address, availableGas
// AddTxOrigin adds the member fields of the sender account to the access event list,
// so that cold accesses are not charged, since they are covered by the 21000 gas.
func (ae *AccessEvents) AddTxOrigin(originAddr common.Address) {
ae.touchAddressAndChargeGas(originAddr, zeroTreeIndex, utils.BasicDataLeafKey, true, gomath.MaxUint64)
ae.touchAddressAndChargeGas(originAddr, zeroTreeIndex, utils.CodeHashLeafKey, false, gomath.MaxUint64)
ae.touchAddressAndChargeGas(originAddr, zeroTreeIndex, bintrie.BasicDataLeafKey, true, gomath.MaxUint64)
ae.touchAddressAndChargeGas(originAddr, zeroTreeIndex, bintrie.CodeHashLeafKey, false, gomath.MaxUint64)
}
// AddTxDestination adds the member fields of the sender account to the access event list,
// so that cold accesses are not charged, since they are covered by the 21000 gas.
func (ae *AccessEvents) AddTxDestination(addr common.Address, sendsValue, doesntExist bool) {
ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.BasicDataLeafKey, sendsValue, gomath.MaxUint64)
ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeHashLeafKey, doesntExist, gomath.MaxUint64)
ae.touchAddressAndChargeGas(addr, zeroTreeIndex, bintrie.BasicDataLeafKey, sendsValue, gomath.MaxUint64)
ae.touchAddressAndChargeGas(addr, zeroTreeIndex, bintrie.CodeHashLeafKey, doesntExist, gomath.MaxUint64)
}
// SlotGas returns the amount of gas to be charged for a cold storage access.
func (ae *AccessEvents) SlotGas(addr common.Address, slot common.Hash, isWrite bool, availableGas uint64, chargeWarmCosts bool) uint64 {
treeIndex, subIndex := utils.StorageIndex(slot.Bytes())
treeIndex, subIndex := bintrie.StorageIndex(slot.Bytes())
_, expected := ae.touchAddressAndChargeGas(addr, *treeIndex, subIndex, isWrite, availableGas)
if expected == 0 && chargeWarmCosts {
expected = params.WarmStorageReadCostEIP2929
@ -313,7 +312,7 @@ func (ae *AccessEvents) CodeChunksRangeGas(contractAddr common.Address, startPC,
// Note that an access in write mode implies an access in read mode, whereas an
// access in read mode does not imply an access in write mode.
func (ae *AccessEvents) BasicDataGas(addr common.Address, isWrite bool, availableGas uint64, chargeWarmCosts bool) uint64 {
_, expected := ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.BasicDataLeafKey, isWrite, availableGas)
_, expected := ae.touchAddressAndChargeGas(addr, zeroTreeIndex, bintrie.BasicDataLeafKey, isWrite, availableGas)
if expected == 0 && chargeWarmCosts {
if availableGas < params.WarmStorageReadCostEIP2929 {
return availableGas
@ -329,7 +328,7 @@ func (ae *AccessEvents) BasicDataGas(addr common.Address, isWrite bool, availabl
// Note that an access in write mode implies an access in read mode, whereas an access in
// read mode does not imply an access in write mode.
func (ae *AccessEvents) CodeHashGas(addr common.Address, isWrite bool, availableGas uint64, chargeWarmCosts bool) uint64 {
_, expected := ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeHashLeafKey, isWrite, availableGas)
_, expected := ae.touchAddressAndChargeGas(addr, zeroTreeIndex, bintrie.CodeHashLeafKey, isWrite, availableGas)
if expected == 0 && chargeWarmCosts {
if availableGas < params.WarmStorageReadCostEIP2929 {
return availableGas

View file

@ -22,7 +22,6 @@ import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/trie/utils"
)
var (
@ -38,7 +37,7 @@ func init() {
}
func TestAccountHeaderGas(t *testing.T) {
ae := NewAccessEvents(utils.NewPointCache(1024))
ae := NewAccessEvents()
// Check cold read cost
gas := ae.BasicDataGas(testAddr, false, math.MaxUint64, false)
@ -93,7 +92,7 @@ func TestAccountHeaderGas(t *testing.T) {
// TestContractCreateInitGas checks that the gas cost of contract creation is correctly
// calculated.
func TestContractCreateInitGas(t *testing.T) {
ae := NewAccessEvents(utils.NewPointCache(1024))
ae := NewAccessEvents()
var testAddr [20]byte
for i := byte(0); i < 20; i++ {
@ -116,7 +115,7 @@ func TestContractCreateInitGas(t *testing.T) {
// TestMessageCallGas checks that the gas cost of message calls is correctly
// calculated.
func TestMessageCallGas(t *testing.T) {
ae := NewAccessEvents(utils.NewPointCache(1024))
ae := NewAccessEvents()
// Check cold read cost, without a value
gas := ae.MessageCallGas(testAddr, math.MaxUint64)

View file

@ -31,7 +31,6 @@ import (
"github.com/ethereum/go-ethereum/trie/bintrie"
"github.com/ethereum/go-ethereum/trie/transitiontrie"
"github.com/ethereum/go-ethereum/trie/trienode"
"github.com/ethereum/go-ethereum/trie/utils"
"github.com/ethereum/go-ethereum/triedb"
)
@ -41,9 +40,6 @@ const (
// Cache size granted for caching clean code.
codeCacheSize = 256 * 1024 * 1024
// Number of address->curve point associations to keep.
pointCacheSize = 4096
)
// Database wraps access to tries and contract code.
@ -57,9 +53,6 @@ type Database interface {
// OpenStorageTrie opens the storage trie of an account.
OpenStorageTrie(stateRoot common.Hash, address common.Address, root common.Hash, trie Trie) (Trie, error)
// PointCache returns the cache holding points used in verkle tree key computation
PointCache() *utils.PointCache
// TrieDB returns the underlying trie database for managing trie nodes.
TrieDB() *triedb.Database
@ -161,7 +154,6 @@ type CachingDB struct {
snap *snapshot.Tree
codeCache *lru.SizeConstrainedCache[common.Hash, []byte]
codeSizeCache *lru.Cache[common.Hash, int]
pointCache *utils.PointCache
// Transition-specific fields
TransitionStatePerRoot *lru.Cache[common.Hash, *overlay.TransitionState]
@ -175,7 +167,6 @@ func NewDatabase(triedb *triedb.Database, snap *snapshot.Tree) *CachingDB {
snap: snap,
codeCache: lru.NewSizeConstrainedCache[common.Hash, []byte](codeCacheSize),
codeSizeCache: lru.NewCache[common.Hash, int](codeSizeCacheSize),
pointCache: utils.NewPointCache(pointCacheSize),
TransitionStatePerRoot: lru.NewCache[common.Hash, *overlay.TransitionState](1000),
}
}
@ -211,7 +202,7 @@ func (db *CachingDB) Reader(stateRoot common.Hash) (Reader, error) {
}
// Configure the trie reader, which is expected to be available as the
// gatekeeper unless the state is corrupted.
tr, err := newTrieReader(stateRoot, db.triedb, db.pointCache)
tr, err := newTrieReader(stateRoot, db.triedb)
if err != nil {
return nil, err
}
@ -289,11 +280,6 @@ func (db *CachingDB) TrieDB() *triedb.Database {
return db.triedb
}
// PointCache returns the cache of evaluated curve points.
func (db *CachingDB) PointCache() *utils.PointCache {
return db.pointCache
}
// Snapshot returns the underlying state snapshot.
func (db *CachingDB) Snapshot() *snapshot.Tree {
return db.snap
@ -304,8 +290,6 @@ func mustCopyTrie(t Trie) Trie {
switch t := t.(type) {
case *trie.StateTrie:
return t.Copy()
case *trie.VerkleTrie:
return t.Copy()
case *transitiontrie.TransitionTrie:
return t.Copy()
default:

View file

@ -25,7 +25,6 @@ import (
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/trie/utils"
"github.com/ethereum/go-ethereum/triedb"
"github.com/ethereum/go-ethereum/triedb/pathdb"
)
@ -105,7 +104,6 @@ type HistoricDB struct {
triedb *triedb.Database
codeCache *lru.SizeConstrainedCache[common.Hash, []byte]
codeSizeCache *lru.Cache[common.Hash, int]
pointCache *utils.PointCache
}
// NewHistoricDatabase creates a historic state database.
@ -115,7 +113,6 @@ func NewHistoricDatabase(disk ethdb.KeyValueStore, triedb *triedb.Database) *His
triedb: triedb,
codeCache: lru.NewSizeConstrainedCache[common.Hash, []byte](codeCacheSize),
codeSizeCache: lru.NewCache[common.Hash, int](codeSizeCacheSize),
pointCache: utils.NewPointCache(pointCacheSize),
}
}
@ -139,11 +136,6 @@ func (db *HistoricDB) OpenStorageTrie(stateRoot common.Hash, address common.Addr
return nil, errors.New("not implemented")
}
// PointCache returns the cache holding points used in verkle tree key computation
func (db *HistoricDB) PointCache() *utils.PointCache {
return db.pointCache
}
// TrieDB returns the underlying trie database for managing trie nodes.
func (db *HistoricDB) TrieDB() *triedb.Database {
return db.triedb

View file

@ -18,6 +18,7 @@ package state
import (
"errors"
"fmt"
"sync"
"sync/atomic"
@ -32,13 +33,16 @@ import (
"github.com/ethereum/go-ethereum/trie"
"github.com/ethereum/go-ethereum/trie/bintrie"
"github.com/ethereum/go-ethereum/trie/transitiontrie"
"github.com/ethereum/go-ethereum/trie/utils"
"github.com/ethereum/go-ethereum/triedb"
"github.com/ethereum/go-ethereum/triedb/database"
)
// ContractCodeReader defines the interface for accessing contract code.
type ContractCodeReader interface {
// Has returns the flag indicating whether the contract code with
// specified address and hash exists or not.
Has(addr common.Address, codeHash common.Hash) bool
// Code retrieves a particular contract's code.
//
// - Returns nil code along with nil error if the requested contract code
@ -88,10 +92,29 @@ type Reader interface {
// ReaderStats wraps the statistics of reader.
type ReaderStats struct {
AccountHit int64
AccountMiss int64
StorageHit int64
StorageMiss int64
// Cache stats
AccountCacheHit int64
AccountCacheMiss int64
StorageCacheHit int64
StorageCacheMiss int64
}
// String implements fmt.Stringer, returning string format statistics.
func (s ReaderStats) String() string {
var (
accountCacheHitRate float64
storageCacheHitRate float64
)
if s.AccountCacheHit > 0 {
accountCacheHitRate = float64(s.AccountCacheHit) / float64(s.AccountCacheHit+s.AccountCacheMiss) * 100
}
if s.StorageCacheHit > 0 {
storageCacheHitRate = float64(s.StorageCacheHit) / float64(s.StorageCacheHit+s.StorageCacheMiss) * 100
}
msg := fmt.Sprintf("Reader statistics\n")
msg += fmt.Sprintf("account: hit: %d, miss: %d, rate: %.2f\n", s.AccountCacheHit, s.AccountCacheMiss, accountCacheHitRate)
msg += fmt.Sprintf("storage: hit: %d, miss: %d, rate: %.2f\n", s.StorageCacheHit, s.StorageCacheMiss, storageCacheHitRate)
return msg
}
// ReaderWithStats wraps the additional method to retrieve the reader statistics from.
@ -150,6 +173,13 @@ func (r *cachingCodeReader) CodeSize(addr common.Address, codeHash common.Hash)
return len(code), nil
}
// Has returns the flag indicating whether the contract code with
// specified address and hash exists or not.
func (r *cachingCodeReader) Has(addr common.Address, codeHash common.Hash) bool {
code, _ := r.Code(addr, codeHash)
return len(code) > 0
}
// flatReader wraps a database state reader and is safe for concurrent access.
type flatReader struct {
reader database.StateReader
@ -236,7 +266,7 @@ type trieReader struct {
// newTrieReader constructs a trie reader of the specific state. An error will be
// returned if the associated trie specified by root is not existent.
func newTrieReader(root common.Hash, db *triedb.Database, cache *utils.PointCache) (*trieReader, error) {
func newTrieReader(root common.Hash, db *triedb.Database) (*trieReader, error) {
var (
tr Trie
err error
@ -544,10 +574,11 @@ func (r *readerWithCache) Storage(addr common.Address, slot common.Hash) (common
type readerWithCacheStats struct {
*readerWithCache
accountHit atomic.Int64
accountMiss atomic.Int64
storageHit atomic.Int64
storageMiss atomic.Int64
accountCacheHit atomic.Int64
accountCacheMiss atomic.Int64
storageCacheHit atomic.Int64
storageCacheMiss atomic.Int64
}
// newReaderWithCacheStats constructs the reader with additional statistics tracked.
@ -567,9 +598,9 @@ func (r *readerWithCacheStats) Account(addr common.Address) (*types.StateAccount
return nil, err
}
if incache {
r.accountHit.Add(1)
r.accountCacheHit.Add(1)
} else {
r.accountMiss.Add(1)
r.accountCacheMiss.Add(1)
}
return account, nil
}
@ -585,9 +616,9 @@ func (r *readerWithCacheStats) Storage(addr common.Address, slot common.Hash) (c
return common.Hash{}, err
}
if incache {
r.storageHit.Add(1)
r.storageCacheHit.Add(1)
} else {
r.storageMiss.Add(1)
r.storageCacheMiss.Add(1)
}
return value, nil
}
@ -595,9 +626,9 @@ func (r *readerWithCacheStats) Storage(addr common.Address, slot common.Hash) (c
// GetStats implements ReaderWithStats, returning the statistics of state reader.
func (r *readerWithCacheStats) GetStats() ReaderStats {
return ReaderStats{
AccountHit: r.accountHit.Load(),
AccountMiss: r.accountMiss.Load(),
StorageHit: r.storageHit.Load(),
StorageMiss: r.storageMiss.Load(),
AccountCacheHit: r.accountCacheHit.Load(),
AccountCacheMiss: r.accountCacheMiss.Load(),
StorageCacheHit: r.storageCacheHit.Load(),
StorageCacheMiss: r.storageCacheMiss.Load(),
}
}

View file

@ -465,6 +465,6 @@ func (dl *diffLayer) StorageList(accountHash common.Hash) []common.Hash {
storageList := slices.SortedFunc(maps.Keys(dl.storageData[accountHash]), common.Hash.Cmp)
dl.storageList[accountHash] = storageList
dl.memory += uint64(len(dl.storageList)*common.HashLength + common.HashLength)
dl.memory += uint64(len(storageList)*common.HashLength + common.HashLength)
return storageList
}

View file

@ -198,6 +198,39 @@ func TestInsertAndMerge(t *testing.T) {
}
}
// TestStorageListMemoryAccounting ensures that StorageList increases dl.memory
// proportionally to the number of storage slots in the requested account and
// does not change memory usage on repeated calls for the same account.
func TestStorageListMemoryAccounting(t *testing.T) {
parent := newDiffLayer(emptyLayer(), common.Hash{}, nil, nil)
account := common.HexToHash("0x01")
slots := make(map[common.Hash][]byte)
for i := 0; i < 3; i++ {
slots[randomHash()] = []byte{0x01}
}
storage := map[common.Hash]map[common.Hash][]byte{
account: slots,
}
dl := newDiffLayer(parent, common.Hash{}, nil, storage)
before := dl.memory
list := dl.StorageList(account)
if have, want := len(list), len(slots); have != want {
t.Fatalf("StorageList length mismatch: have %d, want %d", have, want)
}
expectedDelta := uint64(len(list)*common.HashLength + common.HashLength)
if have, want := dl.memory-before, expectedDelta; have != want {
t.Fatalf("StorageList memory delta mismatch: have %d, want %d", have, want)
}
before = dl.memory
_ = dl.StorageList(account)
if dl.memory != before {
t.Fatalf("StorageList changed memory on cached call: have %d, want %d", dl.memory, before)
}
}
func emptyLayer() *diskLayer {
return &diskLayer{
diskdb: memorydb.New(),

View file

@ -29,6 +29,7 @@ import (
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/trie"
"github.com/ethereum/go-ethereum/trie/bintrie"
"github.com/ethereum/go-ethereum/trie/transitiontrie"
"github.com/ethereum/go-ethereum/trie/trienode"
"github.com/holiman/uint256"
@ -498,8 +499,8 @@ func (s *stateObject) deepCopy(db *StateDB) *stateObject {
}
switch s.trie.(type) {
case *trie.VerkleTrie:
// Verkle uses only one tree, and the copy has already been
case *bintrie.BinaryTrie:
// UBT uses only one tree, and the copy has already been
// made in mustCopyTrie.
obj.trie = db.trie
case *transitiontrie.TransitionTrie:
@ -531,6 +532,11 @@ func (s *stateObject) Code() []byte {
if bytes.Equal(s.CodeHash(), types.EmptyCodeHash.Bytes()) {
return nil
}
defer func(start time.Time) {
s.db.CodeLoaded += 1
s.db.CodeReads += time.Since(start)
}(time.Now())
code, err := s.db.reader.Code(s.address, common.BytesToHash(s.CodeHash()))
if err != nil {
s.db.setError(fmt.Errorf("can't load code hash %x: %v", s.CodeHash(), err))
@ -552,6 +558,11 @@ func (s *stateObject) CodeSize() int {
if bytes.Equal(s.CodeHash(), types.EmptyCodeHash.Bytes()) {
return 0
}
defer func(start time.Time) {
s.db.CodeLoaded += 1
s.db.CodeReads += time.Since(start)
}(time.Now())
size, err := s.db.reader.CodeSize(s.address, common.BytesToHash(s.CodeHash()))
if err != nil {
s.db.setError(fmt.Errorf("can't load code size %x: %v", s.CodeHash(), err))

View file

@ -31,6 +31,7 @@ import (
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/metrics"
"github.com/ethereum/go-ethereum/triedb"
"golang.org/x/sync/errgroup"
)
@ -48,6 +49,21 @@ var (
codeKeySize = int64(len(rawdb.CodePrefix) + common.HashLength)
)
// State size metrics
var (
stateSizeChainHeightGauge = metrics.NewRegisteredGauge("state/height", nil)
stateSizeAccountsCountGauge = metrics.NewRegisteredGauge("state/accounts/count", nil)
stateSizeAccountsBytesGauge = metrics.NewRegisteredGauge("state/accounts/bytes", nil)
stateSizeStoragesCountGauge = metrics.NewRegisteredGauge("state/storages/count", nil)
stateSizeStoragesBytesGauge = metrics.NewRegisteredGauge("state/storages/bytes", nil)
stateSizeAccountTrieNodesCountGauge = metrics.NewRegisteredGauge("state/trienodes/account/count", nil)
stateSizeAccountTrieNodesBytesGauge = metrics.NewRegisteredGauge("state/trienodes/account/bytes", nil)
stateSizeStorageTrieNodesCountGauge = metrics.NewRegisteredGauge("state/trienodes/storage/count", nil)
stateSizeStorageTrieNodesBytesGauge = metrics.NewRegisteredGauge("state/trienodes/storage/bytes", nil)
stateSizeContractsCountGauge = metrics.NewRegisteredGauge("state/contracts/count", nil)
stateSizeContractsBytesGauge = metrics.NewRegisteredGauge("state/contracts/bytes", nil)
)
// SizeStats represents either the current state size statistics or the size
// differences resulting from a state transition.
type SizeStats struct {
@ -76,6 +92,20 @@ func (s SizeStats) String() string {
)
}
func (s SizeStats) publish() {
stateSizeChainHeightGauge.Update(int64(s.BlockNumber))
stateSizeAccountsCountGauge.Update(s.Accounts)
stateSizeAccountsBytesGauge.Update(s.AccountBytes)
stateSizeStoragesCountGauge.Update(s.Storages)
stateSizeStoragesBytesGauge.Update(s.StorageBytes)
stateSizeAccountTrieNodesCountGauge.Update(s.AccountTrienodes)
stateSizeAccountTrieNodesBytesGauge.Update(s.AccountTrienodeBytes)
stateSizeStorageTrieNodesCountGauge.Update(s.StorageTrienodes)
stateSizeStorageTrieNodesBytesGauge.Update(s.StorageTrienodeBytes)
stateSizeContractsCountGauge.Update(s.ContractCodes)
stateSizeContractsBytesGauge.Update(s.ContractCodeBytes)
}
// add applies the given state diffs and produces a new version of the statistics.
func (s SizeStats) add(diff SizeStats) SizeStats {
s.StateRoot = diff.StateRoot
@ -213,12 +243,14 @@ func calSizeStats(update *stateUpdate) (SizeStats, error) {
}
}
// Measure code changes. Note that the reported contract code size may be slightly
// inaccurate due to database deduplication (code is stored by its hash). However,
// this deviation is negligible and acceptable for measurement purposes.
codeExists := make(map[common.Hash]struct{})
for _, code := range update.codes {
if _, ok := codeExists[code.hash]; ok || code.exists {
continue
}
stats.ContractCodes += 1
stats.ContractCodeBytes += codeKeySize + int64(len(code.blob))
codeExists[code.hash] = struct{}{}
}
return stats, nil
}
@ -309,6 +341,10 @@ func (t *SizeTracker) run() {
stats[u.root] = stat
last = u.root
// Publish statistics to metric system
stat.publish()
// Evict the stale statistics
heap.Push(&h, stats[u.root])
for u.blockNumber-h[0].BlockNumber > statEvictThreshold {
delete(stats, h[0].StateRoot)

View file

@ -58,7 +58,7 @@ func TestSizeTracker(t *testing.T) {
state.AddBalance(addr3, uint256.NewInt(3000), tracing.BalanceChangeUnspecified)
state.SetNonce(addr3, 3, tracing.NonceChangeUnspecified)
currentRoot, _, err := state.CommitWithUpdate(1, true, false)
currentRoot, err := state.Commit(1, true, false)
if err != nil {
t.Fatalf("Failed to commit initial state: %v", err)
}
@ -83,7 +83,7 @@ func TestSizeTracker(t *testing.T) {
if i%3 == 0 {
newState.SetCode(testAddr, []byte{byte(i), 0x60, 0x80, byte(i + 1), 0x52}, tracing.CodeChangeUnspecified)
}
root, _, err := newState.CommitWithUpdate(blockNum, true, false)
root, err := newState.Commit(blockNum, true, false)
if err != nil {
t.Fatalf("Failed to commit state at block %d: %v", blockNum, err)
}
@ -154,21 +154,22 @@ func TestSizeTracker(t *testing.T) {
if i%3 == 0 {
newState.SetCode(testAddr, []byte{byte(i), 0x60, 0x80, byte(i + 1), 0x52}, tracing.CodeChangeUnspecified)
}
root, update, err := newState.CommitWithUpdate(blockNum, true, false)
ret, err := newState.commitAndFlush(blockNum, true, false, true)
if err != nil {
t.Fatalf("Failed to commit state at block %d: %v", blockNum, err)
}
if err := tdb.Commit(root, false); err != nil {
tracker.Notify(ret)
if err := tdb.Commit(ret.root, false); err != nil {
t.Fatalf("Failed to commit trie at block %d: %v", blockNum, err)
}
diff, err := calSizeStats(update)
diff, err := calSizeStats(ret)
if err != nil {
t.Fatalf("Failed to calculate size stats for block %d: %v", blockNum, err)
}
trackedUpdates = append(trackedUpdates, diff)
tracker.Notify(update)
currentRoot = root
currentRoot = ret.root
}
finalRoot := rawdb.ReadSnapshotRoot(db)

View file

@ -38,7 +38,6 @@ import (
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/trie"
"github.com/ethereum/go-ethereum/trie/trienode"
"github.com/ethereum/go-ethereum/trie/utils"
"github.com/holiman/uint256"
"golang.org/x/sync/errgroup"
)
@ -141,15 +140,17 @@ type StateDB struct {
witnessStats *stateless.WitnessStats
// Measurements gathered during execution for debugging purposes
AccountReads time.Duration
AccountHashes time.Duration
AccountUpdates time.Duration
AccountCommits time.Duration
AccountReads time.Duration
AccountHashes time.Duration
AccountUpdates time.Duration
AccountCommits time.Duration
StorageReads time.Duration
StorageUpdates time.Duration
StorageCommits time.Duration
SnapshotCommits time.Duration
TrieDBCommits time.Duration
CodeReads time.Duration
AccountLoaded int // Number of accounts retrieved from the database during the state transition
AccountUpdated int // Number of accounts updated during the state transition
@ -157,6 +158,7 @@ type StateDB struct {
StorageLoaded int // Number of storage slots retrieved from the database during the state transition
StorageUpdated atomic.Int64 // Number of storage slots updated during the state transition
StorageDeleted atomic.Int64 // Number of storage slots deleted during the state transition
CodeLoaded int // Number of contract code loaded during the state transition
}
// New creates a new state from a given trie.
@ -185,7 +187,7 @@ func NewWithReader(root common.Hash, db Database, reader Reader) (*StateDB, erro
transientStorage: newTransientStorage(),
}
if db.TrieDB().IsVerkle() {
sdb.accessEvents = NewAccessEvents(db.PointCache())
sdb.accessEvents = NewAccessEvents()
}
return sdb, nil
}
@ -1316,11 +1318,16 @@ func (s *StateDB) commit(deleteEmptyObjects bool, noStorageWiping bool, blockNum
// commitAndFlush is a wrapper of commit which also commits the state mutations
// to the configured data stores.
func (s *StateDB) commitAndFlush(block uint64, deleteEmptyObjects bool, noStorageWiping bool) (*stateUpdate, error) {
func (s *StateDB) commitAndFlush(block uint64, deleteEmptyObjects bool, noStorageWiping bool, dedupCode bool) (*stateUpdate, error) {
ret, err := s.commit(deleteEmptyObjects, noStorageWiping, block)
if err != nil {
return nil, err
}
if dedupCode {
ret.markCodeExistence(s.reader)
}
// Commit dirty contract code if any exists
if db := s.db.TrieDB().Disk(); db != nil && len(ret.codes) > 0 {
batch := db.NewBatch()
@ -1375,21 +1382,21 @@ func (s *StateDB) commitAndFlush(block uint64, deleteEmptyObjects bool, noStorag
// no empty accounts left that could be deleted by EIP-158, storage wiping
// should not occur.
func (s *StateDB) Commit(block uint64, deleteEmptyObjects bool, noStorageWiping bool) (common.Hash, error) {
ret, err := s.commitAndFlush(block, deleteEmptyObjects, noStorageWiping)
ret, err := s.commitAndFlush(block, deleteEmptyObjects, noStorageWiping, false)
if err != nil {
return common.Hash{}, err
}
return ret.root, nil
}
// CommitWithUpdate writes the state mutations and returns both the root hash and the state update.
// This is useful for tracking state changes at the blockchain level.
func (s *StateDB) CommitWithUpdate(block uint64, deleteEmptyObjects bool, noStorageWiping bool) (common.Hash, *stateUpdate, error) {
ret, err := s.commitAndFlush(block, deleteEmptyObjects, noStorageWiping)
// CommitAndTrack writes the state mutations and notifies the size tracker of the state changes.
func (s *StateDB) CommitAndTrack(block uint64, deleteEmptyObjects bool, noStorageWiping bool, sizer *SizeTracker) (common.Hash, error) {
ret, err := s.commitAndFlush(block, deleteEmptyObjects, noStorageWiping, true)
if err != nil {
return common.Hash{}, nil, err
return common.Hash{}, err
}
return ret.root, ret, nil
sizer.Notify(ret)
return ret.root, nil
}
// Prepare handles the preparatory steps for executing a state transition with.
@ -1487,11 +1494,6 @@ func (s *StateDB) markUpdate(addr common.Address) {
s.mutations[addr].typ = update
}
// PointCache returns the point cache used by verkle tree.
func (s *StateDB) PointCache() *utils.PointCache {
return s.db.PointCache()
}
// Witness retrieves the current state witness being collected.
func (s *StateDB) Witness() *stateless.Witness {
return s.witness

View file

@ -228,7 +228,7 @@ func (test *stateTest) run() bool {
} else {
state.IntermediateRoot(true) // call intermediateRoot at the transaction boundary
}
ret, err := state.commitAndFlush(0, true, false) // call commit at the block boundary
ret, err := state.commitAndFlush(0, true, false, false) // call commit at the block boundary
if err != nil {
panic(err)
}

View file

@ -25,7 +25,6 @@ import (
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/trie/utils"
"github.com/holiman/uint256"
)
@ -133,10 +132,6 @@ func (s *hookedStateDB) AddSlotToAccessList(addr common.Address, slot common.Has
s.inner.AddSlotToAccessList(addr, slot)
}
func (s *hookedStateDB) PointCache() *utils.PointCache {
return s.inner.PointCache()
}
func (s *hookedStateDB) Prepare(rules params.Rules, sender, coinbase common.Address, dest *common.Address, precompiles []common.Address, txAccesses types.AccessList) {
s.inner.Prepare(rules, sender, coinbase, dest, precompiles, txAccesses)
}

View file

@ -129,7 +129,7 @@ func TestHooks(t *testing.T) {
for i, want := range wants {
if have := result[i]; have != want {
t.Fatalf("error event %d, have\n%v\nwant%v\n", i, have, want)
t.Fatalf("error event %d\nhave: %v\nwant: %v", i, have, want)
}
}
}
@ -165,7 +165,7 @@ func TestHooks_OnCodeChangeV2(t *testing.T) {
for i, want := range wants {
if have := result[i]; have != want {
t.Fatalf("error event %d, have\n%v\nwant%v\n", i, have, want)
t.Fatalf("error event %d\nhave: %v\nwant: %v", i, have, want)
}
}
}

View file

@ -26,8 +26,9 @@ import (
// contractCode represents a contract code with associated metadata.
type contractCode struct {
hash common.Hash // hash is the cryptographic hash of the contract code.
blob []byte // blob is the binary representation of the contract code.
hash common.Hash // hash is the cryptographic hash of the contract code.
blob []byte // blob is the binary representation of the contract code.
exists bool // flag whether the code has been existent
}
// accountDelete represents an operation for deleting an Ethereum account.
@ -82,8 +83,8 @@ type stateUpdate struct {
storagesOrigin map[common.Address]map[common.Hash][]byte
rawStorageKey bool
codes map[common.Address]contractCode // codes contains the set of dirty codes
nodes *trienode.MergedNodeSet // Aggregated dirty nodes caused by state changes
codes map[common.Address]*contractCode // codes contains the set of dirty codes
nodes *trienode.MergedNodeSet // Aggregated dirty nodes caused by state changes
}
// empty returns a flag indicating the state transition is empty or not.
@ -103,7 +104,7 @@ func newStateUpdate(rawStorageKey bool, originRoot common.Hash, root common.Hash
accountsOrigin = make(map[common.Address][]byte)
storages = make(map[common.Hash]map[common.Hash][]byte)
storagesOrigin = make(map[common.Address]map[common.Hash][]byte)
codes = make(map[common.Address]contractCode)
codes = make(map[common.Address]*contractCode)
)
// Since some accounts might be destroyed and recreated within the same
// block, deletions must be aggregated first.
@ -125,7 +126,7 @@ func newStateUpdate(rawStorageKey bool, originRoot common.Hash, root common.Hash
// Aggregate dirty contract codes if they are available.
addr := op.address
if op.code != nil {
codes[addr] = *op.code
codes[addr] = op.code
}
accounts[addrHash] = op.data
@ -190,3 +191,22 @@ func (sc *stateUpdate) stateSet() *triedb.StateSet {
RawStorageKey: sc.rawStorageKey,
}
}
// markCodeExistence determines whether each piece of contract code referenced
// in this state update actually exists.
//
// Note: This operation is expensive and not needed during normal state transitions.
// It is only required when SizeTracker is enabled to produce accurate state
// statistics.
func (sc *stateUpdate) markCodeExistence(reader ContractCodeReader) {
cache := make(map[common.Hash]bool)
for addr, code := range sc.codes {
if exists, ok := cache[code.hash]; ok {
code.exists = exists
continue
}
res := reader.Has(addr, code.hash)
cache[code.hash] = res
code.exists = res
}
}

View file

@ -62,10 +62,17 @@ func (s *WitnessStats) Add(nodes map[string][]byte, owner common.Hash) {
// If current path is a prefix of the next path, it's not a leaf.
// The last path is always a leaf.
if i == len(paths)-1 || !strings.HasPrefix(paths[i+1], paths[i]) {
depth := len(path)
if owner == (common.Hash{}) {
s.accountTrieLeaves[len(path)] += 1
if depth >= len(s.accountTrieLeaves) {
depth = len(s.accountTrieLeaves) - 1
}
s.accountTrieLeaves[depth] += 1
} else {
s.storageTrieLeaves[len(path)] += 1
if depth >= len(s.storageTrieLeaves) {
depth = len(s.storageTrieLeaves) - 1
}
s.storageTrieLeaves[depth] += 1
}
}
}

View file

@ -21,12 +21,10 @@ import (
"container/heap"
"errors"
"fmt"
"maps"
"math"
"math/big"
"os"
"path/filepath"
"slices"
"sort"
"sync"
"sync/atomic"
@ -96,11 +94,6 @@ const (
// storeVersion is the current slotter layout used for the billy.Database
// store.
storeVersion = 1
// conversionTimeWindow defines the period after the Osaka fork during which
// the pool will still accept and convert legacy blob transactions. After this
// window, all legacy blob transactions will be rejected.
conversionTimeWindow = time.Hour * 2
)
// blobTxMeta is the minimal subset of types.BlobTx necessary to validate and
@ -337,9 +330,8 @@ type BlobPool struct {
stored uint64 // Useful data size of all transactions on disk
limbo *limbo // Persistent data store for the non-finalized blobs
signer types.Signer // Transaction signer to use for sender recovery
chain BlockChain // Chain object to access the state through
cQueue *conversionQueue // The queue for performing legacy sidecar conversion (TODO: remove after Osaka)
signer types.Signer // Transaction signer to use for sender recovery
chain BlockChain // Chain object to access the state through
head atomic.Pointer[types.Header] // Current head of the chain
state *state.StateDB // Current state at the head of the chain
@ -368,7 +360,6 @@ func New(config Config, chain BlockChain, hasPendingAuth func(common.Address) bo
hasPendingAuth: hasPendingAuth,
signer: types.LatestSigner(chain.Config()),
chain: chain,
cQueue: newConversionQueue(), // Deprecate it after the osaka fork
lookup: newLookup(),
index: make(map[common.Address][]*blobTxMeta),
spent: make(map[common.Address]*uint256.Int),
@ -377,7 +368,12 @@ func New(config Config, chain BlockChain, hasPendingAuth func(common.Address) bo
// Filter returns whether the given transaction can be consumed by the blob pool.
func (p *BlobPool) Filter(tx *types.Transaction) bool {
return tx.Type() == types.BlobTxType
return p.FilterType(tx.Type())
}
// FilterType returns whether the blob pool supports the given transaction type.
func (p *BlobPool) FilterType(kind byte) bool {
return kind == types.BlobTxType
}
// Init sets the gas price needed to keep a transaction in the pool and the chain
@ -414,7 +410,7 @@ func (p *BlobPool) Init(gasTip uint64, head *types.Header, reserver txpool.Reser
p.state = state
// Create new slotter for pre-Osaka blob configuration.
slotter := newSlotter(eip4844.LatestMaxBlobsPerBlock(p.chain.Config()))
slotter := newSlotter(params.BlobTxMaxBlobs)
// See if we need to migrate the queue blob store after fusaka
slotter, err = tryMigrate(p.chain.Config(), slotter, queuedir)
@ -485,9 +481,6 @@ func (p *BlobPool) Init(gasTip uint64, head *types.Header, reserver txpool.Reser
// Close closes down the underlying persistent store.
func (p *BlobPool) Close() error {
// Terminate the conversion queue
p.cQueue.close()
var errs []error
if p.limbo != nil { // Close might be invoked due to error in constructor, before p,limbo is set
if err := p.limbo.Close(); err != nil {
@ -885,172 +878,6 @@ func (p *BlobPool) Reset(oldHead, newHead *types.Header) {
basefeeGauge.Update(int64(basefee.Uint64()))
blobfeeGauge.Update(int64(blobfee.Uint64()))
p.updateStorageMetrics()
// Perform the conversion logic at the fork boundary
if !p.chain.Config().IsOsaka(oldHead.Number, oldHead.Time) && p.chain.Config().IsOsaka(newHead.Number, newHead.Time) {
// Deep copy all indexed transaction metadata.
var (
ids = make(map[common.Address]map[uint64]uint64)
txs = make(map[common.Address]map[uint64]common.Hash)
)
for sender, list := range p.index {
ids[sender] = make(map[uint64]uint64)
txs[sender] = make(map[uint64]common.Hash)
for _, m := range list {
ids[sender][m.nonce] = m.id
txs[sender][m.nonce] = m.hash
}
}
// Initiate the background conversion thread.
p.cQueue.launchBillyConversion(func() {
p.convertLegacySidecars(ids, txs)
})
}
}
// compareAndSwap checks if the specified transaction is still tracked in the pool
// and replace the metadata accordingly. It should only be used in the fork boundary
// bulk conversion. If it fails for some reason, the subsequent txs won't be dropped
// for simplicity which we assume it's very likely to happen.
//
// The returned flag indicates whether the replacement succeeded.
func (p *BlobPool) compareAndSwap(address common.Address, hash common.Hash, blob []byte, oldID uint64, oldStorageSize uint32) bool {
p.lock.Lock()
defer p.lock.Unlock()
newId, err := p.store.Put(blob)
if err != nil {
log.Error("Failed to store transaction", "hash", hash, "err", err)
return false
}
newSize := uint64(len(blob))
newStorageSize := p.store.Size(newId)
// Terminate the procedure if the transaction was already evicted. The
// newly added blob should be removed before return.
if !p.lookup.update(hash, newId, newSize) {
if derr := p.store.Delete(newId); derr != nil {
log.Error("Failed to delete the dangling blob tx", "err", derr)
} else {
log.Warn("Deleted the dangling blob tx", "id", newId)
}
return false
}
// Update the metadata of blob transaction
for _, meta := range p.index[address] {
if meta.hash == hash {
meta.id = newId
meta.version = types.BlobSidecarVersion1
meta.storageSize = newStorageSize
meta.size = newSize
p.stored += uint64(newStorageSize)
p.stored -= uint64(oldStorageSize)
break
}
}
if err := p.store.Delete(oldID); err != nil {
log.Error("Failed to delete the legacy transaction", "hash", hash, "id", oldID, "err", err)
}
return true
}
// convertLegacySidecar fetches transaction data from the store, performs an
// on-the-fly conversion. This function is intended for use only during the
// Osaka fork transition period.
//
// The returned flag indicates whether the replacement succeeds or not.
func (p *BlobPool) convertLegacySidecar(sender common.Address, hash common.Hash, id uint64) bool {
start := time.Now()
// Retrieves the legacy blob transaction from the underlying store with
// read lock held, preventing any potential data race around the slot
// specified by the id.
p.lock.RLock()
data, err := p.store.Get(id)
if err != nil {
p.lock.RUnlock()
// The transaction may have been evicted simultaneously, safe to skip conversion.
log.Debug("Blob transaction is missing", "hash", hash, "id", id, "err", err)
return false
}
oldStorageSize := p.store.Size(id)
p.lock.RUnlock()
// Decode the transaction, the failure is not expected and report the error
// loudly if possible. If the blob transaction in this slot is corrupted,
// leave it in the store, it will be dropped during the next pool
// initialization.
var tx types.Transaction
if err = rlp.DecodeBytes(data, &tx); err != nil {
log.Error("Blob transaction is corrupted", "hash", hash, "id", id, "err", err)
return false
}
// Skip conversion if the transaction does not match the expected hash, or if it was
// already converted. This can occur if the original transaction was evicted from the
// pool and the slot was reused by a new one.
if tx.Hash() != hash {
log.Warn("Blob transaction was replaced", "hash", hash, "id", id, "stored", tx.Hash())
return false
}
sc := tx.BlobTxSidecar()
if sc.Version >= types.BlobSidecarVersion1 {
log.Debug("Skipping conversion of blob tx", "hash", hash, "id", id)
return false
}
// Perform the sidecar conversion, the failure is not expected and report the error
// loudly if possible.
if err := tx.BlobTxSidecar().ToV1(); err != nil {
log.Error("Failed to convert blob transaction", "hash", hash, "err", err)
return false
}
// Encode the converted transaction, the failure is not expected and report
// the error loudly if possible.
blob, err := rlp.EncodeToBytes(&tx)
if err != nil {
log.Error("Failed to encode blob transaction", "hash", tx.Hash(), "err", err)
return false
}
// Replace the legacy blob transaction with the converted format.
if !p.compareAndSwap(sender, hash, blob, id, oldStorageSize) {
log.Error("Failed to replace the legacy transaction", "hash", hash)
return false
}
log.Debug("Converted legacy blob transaction", "hash", hash, "elapsed", common.PrettyDuration(time.Since(start)))
return true
}
// convertLegacySidecars converts all given transactions to sidecar version 1.
//
// If any of them fails to be converted, the subsequent transactions will still
// be processed, as we assume the failure is very unlikely to happen. If happens,
// these transactions will be stuck in the pool until eviction.
func (p *BlobPool) convertLegacySidecars(ids map[common.Address]map[uint64]uint64, txs map[common.Address]map[uint64]common.Hash) {
var (
start = time.Now()
success int
failure int
)
for addr, list := range txs {
// Transactions evicted from the pool must be contiguous, if in any case,
// the transactions are gapped with each other, they will be discarded.
nonces := slices.Collect(maps.Keys(list))
slices.Sort(nonces)
// Convert the txs with nonce order
for _, nonce := range nonces {
if p.convertLegacySidecar(addr, list[nonce], ids[addr][nonce]) {
success++
} else {
failure++
}
}
}
log.Info("Completed blob transaction conversion", "discarded", failure, "injected", success, "elapsed", common.PrettyDuration(time.Since(start)))
}
// reorg assembles all the transactors and missing transactions between an old
@ -1530,8 +1357,8 @@ func (p *BlobPool) GetMetadata(hash common.Hash) *txpool.TxMetadata {
//
// The version argument specifies the type of proofs to return, either the
// blob proofs (version 0) or the cell proofs (version 1). Proofs conversion is
// CPU intensive, so only done if explicitly requested with the convert flag.
func (p *BlobPool) GetBlobs(vhashes []common.Hash, version byte, convert bool) ([]*kzg4844.Blob, []kzg4844.Commitment, [][]kzg4844.Proof, error) {
// CPU intensive and prohibited in the blobpool explicitly.
func (p *BlobPool) GetBlobs(vhashes []common.Hash, version byte) ([]*kzg4844.Blob, []kzg4844.Commitment, [][]kzg4844.Proof, error) {
var (
blobs = make([]*kzg4844.Blob, len(vhashes))
commitments = make([]kzg4844.Commitment, len(vhashes))
@ -1582,7 +1409,7 @@ func (p *BlobPool) GetBlobs(vhashes []common.Hash, version byte, convert bool) (
}
// Mark hash as seen.
filled[hash] = struct{}{}
if sidecar.Version != version && !convert {
if sidecar.Version != version {
// Skip blobs with incompatible version. Note we still track the blob hash
// in `filled` here, ensuring that we do not resolve this tx another time.
continue
@ -1591,29 +1418,13 @@ func (p *BlobPool) GetBlobs(vhashes []common.Hash, version byte, convert bool) (
var pf []kzg4844.Proof
switch version {
case types.BlobSidecarVersion0:
if sidecar.Version == types.BlobSidecarVersion0 {
pf = []kzg4844.Proof{sidecar.Proofs[i]}
} else {
proof, err := kzg4844.ComputeBlobProof(&sidecar.Blobs[i], sidecar.Commitments[i])
if err != nil {
return nil, nil, nil, err
}
pf = []kzg4844.Proof{proof}
}
pf = []kzg4844.Proof{sidecar.Proofs[i]}
case types.BlobSidecarVersion1:
if sidecar.Version == types.BlobSidecarVersion0 {
cellProofs, err := kzg4844.ComputeCellProofs(&sidecar.Blobs[i])
if err != nil {
return nil, nil, nil, err
}
pf = cellProofs
} else {
cellProofs, err := sidecar.CellProofsAt(i)
if err != nil {
return nil, nil, nil, err
}
pf = cellProofs
cellProofs, err := sidecar.CellProofsAt(i)
if err != nil {
return nil, nil, nil, err
}
pf = cellProofs
}
for _, index := range list {
blobs[index] = &sidecar.Blobs[i]
@ -1640,56 +1451,15 @@ func (p *BlobPool) AvailableBlobs(vhashes []common.Hash) int {
return available
}
// preCheck performs the static validation upon the provided tx and converts
// the legacy sidecars if Osaka fork has been activated with a short time window.
//
// This function is pure static and lock free.
func (p *BlobPool) preCheck(tx *types.Transaction) error {
var (
head = p.head.Load()
isOsaka = p.chain.Config().IsOsaka(head.Number, head.Time)
deadline time.Time
)
if isOsaka {
deadline = time.Unix(int64(*p.chain.Config().OsakaTime), 0).Add(conversionTimeWindow)
}
// Validate the transaction statically at first to avoid unnecessary
// conversion. This step doesn't require lock protection.
if err := p.ValidateTxBasics(tx); err != nil {
return err
}
// Before the Osaka fork, reject the blob txs with cell proofs
if !isOsaka {
if tx.BlobTxSidecar().Version == types.BlobSidecarVersion0 {
return nil
} else {
return errors.New("cell proof is not supported yet")
}
}
// After the Osaka fork, reject the legacy blob txs if the conversion
// time window is passed.
if tx.BlobTxSidecar().Version == types.BlobSidecarVersion1 {
return nil
}
if head.Time > uint64(deadline.Unix()) {
return errors.New("legacy blob tx is not supported")
}
// Convert the legacy sidecar after Osaka fork. This could be a long
// procedure which takes a few seconds, even minutes if there is a long
// queue. Fortunately it will only block the routine of the source peer
// announcing the tx, without affecting other parts.
return p.cQueue.convert(tx)
}
// Add inserts a set of blob transactions into the pool if they pass validation (both
// consensus validity and pool restrictions).
func (p *BlobPool) Add(txs []*types.Transaction, sync bool) []error {
var (
errs []error = make([]error, len(txs))
adds = make([]*types.Transaction, 0, len(txs))
errs = make([]error, len(txs))
adds = make([]*types.Transaction, 0, len(txs))
)
for i, tx := range txs {
if errs[i] = p.preCheck(tx); errs[i] != nil {
if errs[i] = p.ValidateTxBasics(tx); errs[i] != nil {
continue
}
if errs[i] = p.add(tx); errs[i] == nil {

View file

@ -92,10 +92,6 @@ type testBlockChain struct {
blockTime *uint64
}
func (bc *testBlockChain) setHeadTime(time uint64) {
bc.blockTime = &time
}
func (bc *testBlockChain) Config() *params.ChainConfig {
return bc.config
}
@ -433,11 +429,11 @@ func verifyBlobRetrievals(t *testing.T, pool *BlobPool) {
hashes = append(hashes, tx.vhashes...)
}
}
blobs1, _, proofs1, err := pool.GetBlobs(hashes, types.BlobSidecarVersion0, false)
blobs1, _, proofs1, err := pool.GetBlobs(hashes, types.BlobSidecarVersion0)
if err != nil {
t.Fatal(err)
}
blobs2, _, proofs2, err := pool.GetBlobs(hashes, types.BlobSidecarVersion1, false)
blobs2, _, proofs2, err := pool.GetBlobs(hashes, types.BlobSidecarVersion1)
if err != nil {
t.Fatal(err)
}
@ -1329,7 +1325,7 @@ func TestBlobCountLimit(t *testing.T) {
// Check that first succeeds second fails.
if errs[0] != nil {
t.Fatalf("expected tx with 7 blobs to succeed")
t.Fatalf("expected tx with 7 blobs to succeed, got %v", errs[0])
}
if !errors.Is(errs[1], txpool.ErrTxBlobLimitExceeded) {
t.Fatalf("expected tx with 8 blobs to fail, got: %v", errs[1])
@ -1806,66 +1802,6 @@ func TestAdd(t *testing.T) {
}
}
// Tests that transactions with legacy sidecars are accepted within the
// conversion window but rejected after it has passed.
func TestAddLegacyBlobTx(t *testing.T) {
testAddLegacyBlobTx(t, true) // conversion window has not yet passed
testAddLegacyBlobTx(t, false) // conversion window passed
}
func testAddLegacyBlobTx(t *testing.T, accept bool) {
var (
key1, _ = crypto.GenerateKey()
key2, _ = crypto.GenerateKey()
addr1 = crypto.PubkeyToAddress(key1.PublicKey)
addr2 = crypto.PubkeyToAddress(key2.PublicKey)
)
statedb, _ := state.New(types.EmptyRootHash, state.NewDatabaseForTesting())
statedb.AddBalance(addr1, uint256.NewInt(1_000_000_000), tracing.BalanceChangeUnspecified)
statedb.AddBalance(addr2, uint256.NewInt(1_000_000_000), tracing.BalanceChangeUnspecified)
statedb.Commit(0, true, false)
chain := &testBlockChain{
config: params.MergedTestChainConfig,
basefee: uint256.NewInt(1050),
blobfee: uint256.NewInt(105),
statedb: statedb,
}
var timeDiff uint64
if accept {
timeDiff = uint64(conversionTimeWindow.Seconds()) - 1
} else {
timeDiff = uint64(conversionTimeWindow.Seconds()) + 1
}
time := *params.MergedTestChainConfig.OsakaTime + timeDiff
chain.setHeadTime(time)
pool := New(Config{Datadir: t.TempDir()}, chain, nil)
if err := pool.Init(1, chain.CurrentBlock(), newReserver()); err != nil {
t.Fatalf("failed to create blob pool: %v", err)
}
// Attempt to add legacy blob transactions.
var (
tx1 = makeMultiBlobTx(0, 1, 1000, 100, 6, 0, key1, types.BlobSidecarVersion0)
tx2 = makeMultiBlobTx(0, 1, 800, 70, 6, 6, key2, types.BlobSidecarVersion0)
txs = []*types.Transaction{tx1, tx2}
)
errs := pool.Add(txs, true)
for _, err := range errs {
if accept && err != nil {
t.Fatalf("expected tx add to succeed, %v", err)
}
if !accept && err == nil {
t.Fatal("expected tx add to fail")
}
}
verifyPoolInternals(t, pool)
pool.Close()
}
func TestGetBlobs(t *testing.T) {
//log.SetDefault(log.NewLogger(log.NewTerminalHandlerWithLevel(os.Stderr, log.LevelTrace, true)))
@ -1952,7 +1888,6 @@ func TestGetBlobs(t *testing.T) {
limit int
fillRandom bool // Whether to randomly fill some of the requested blobs with unknowns
version byte // Blob sidecar version to request
convert bool // Whether to convert version on retrieval
}{
{
start: 0, limit: 6,
@ -2018,11 +1953,6 @@ func TestGetBlobs(t *testing.T) {
start: 0, limit: 18, fillRandom: true,
version: types.BlobSidecarVersion1,
},
{
start: 0, limit: 18, fillRandom: true,
version: types.BlobSidecarVersion1,
convert: true, // Convert some version 0 blobs to version 1 while retrieving
},
}
for i, c := range cases {
var (
@ -2044,7 +1974,7 @@ func TestGetBlobs(t *testing.T) {
filled[len(vhashes)] = struct{}{}
vhashes = append(vhashes, testrand.Hash())
}
blobs, _, proofs, err := pool.GetBlobs(vhashes, c.version, c.convert)
blobs, _, proofs, err := pool.GetBlobs(vhashes, c.version)
if err != nil {
t.Errorf("Unexpected error for case %d, %v", i, err)
}
@ -2070,8 +2000,7 @@ func TestGetBlobs(t *testing.T) {
// If an item is missing, but shouldn't, error
if blobs[j] == nil || proofs[j] == nil {
// This is only an error if there was no version mismatch
if c.convert ||
(c.version == types.BlobSidecarVersion1 && 6 <= testBlobIndex && testBlobIndex < 12) ||
if (c.version == types.BlobSidecarVersion1 && 6 <= testBlobIndex && testBlobIndex < 12) ||
(c.version == types.BlobSidecarVersion0 && (testBlobIndex < 6 || 12 <= testBlobIndex)) {
t.Errorf("tracked blob retrieval failed: item %d, hash %x", j, vhashes[j])
}
@ -2098,185 +2027,6 @@ func TestGetBlobs(t *testing.T) {
pool.Close()
}
// TestSidecarConversion will verify that after the Osaka fork, all legacy
// sidecars in the pool are successfully convert to v1 sidecars.
func TestSidecarConversion(t *testing.T) {
// log.SetDefault(log.NewLogger(log.NewTerminalHandlerWithLevel(os.Stderr, log.LevelTrace, true)))
// Create a temporary folder for the persistent backend
storage := t.TempDir()
os.MkdirAll(filepath.Join(storage, pendingTransactionStore), 0700)
var (
preOsakaTxs = make(types.Transactions, 10)
postOsakaTxs = make(types.Transactions, 3)
keys = make([]*ecdsa.PrivateKey, len(preOsakaTxs)+len(postOsakaTxs))
addrs = make([]common.Address, len(preOsakaTxs)+len(postOsakaTxs))
statedb, _ = state.New(types.EmptyRootHash, state.NewDatabaseForTesting())
)
for i := range keys {
keys[i], _ = crypto.GenerateKey()
addrs[i] = crypto.PubkeyToAddress(keys[i].PublicKey)
statedb.AddBalance(addrs[i], uint256.NewInt(1_000_000_000), tracing.BalanceChangeUnspecified)
}
for i := range preOsakaTxs {
preOsakaTxs[i] = makeMultiBlobTx(0, 1, 1000, 100, 2, 0, keys[i], types.BlobSidecarVersion0)
}
for i := range postOsakaTxs {
if i == 0 {
// First has a v0 sidecar.
postOsakaTxs[i] = makeMultiBlobTx(0, 1, 1000, 100, 1, 0, keys[len(preOsakaTxs)+i], types.BlobSidecarVersion0)
}
postOsakaTxs[i] = makeMultiBlobTx(0, 1, 1000, 100, 1, 0, keys[len(preOsakaTxs)+i], types.BlobSidecarVersion1)
}
statedb.Commit(0, true, false)
// Test plan:
// 1) Create a bunch v0 sidecar txs and add to pool before Osaka.
// 2) Pass in new Osaka header to activate the conversion thread.
// 3) Continue adding both v0 and v1 transactions to the pool.
// 4) Verify that as additional blocks come in, transactions involved in the
// migration are correctly discarded.
config := &params.ChainConfig{
ChainID: big.NewInt(1),
LondonBlock: big.NewInt(0),
BerlinBlock: big.NewInt(0),
CancunTime: newUint64(0),
PragueTime: newUint64(0),
OsakaTime: newUint64(1),
BlobScheduleConfig: params.DefaultBlobSchedule,
}
chain := &testBlockChain{
config: config,
basefee: uint256.NewInt(1050),
blobfee: uint256.NewInt(105),
statedb: statedb,
blocks: make(map[uint64]*types.Block),
}
// Create 3 blocks:
// - the current block, before Osaka
// - the first block after Osaka
// - another post-Osaka block with several transactions in it
header0 := chain.CurrentBlock()
header0.Time = 0
chain.blocks[0] = types.NewBlockWithHeader(header0)
header1 := chain.CurrentBlock()
header1.Number = big.NewInt(1)
header1.Time = 1
chain.blocks[1] = types.NewBlockWithHeader(header1)
header2 := chain.CurrentBlock()
header2.Time = 2
header2.Number = big.NewInt(2)
// Make a copy of one of the pre-Osaka transactions and convert it to v1 here
// so that we can add it to the pool later and ensure a duplicate is not added
// by the conversion queue.
tx := preOsakaTxs[len(preOsakaTxs)-1]
sc := *tx.BlobTxSidecar() // copy sidecar
sc.ToV1()
tx.WithBlobTxSidecar(&sc)
block2 := types.NewBlockWithHeader(header2).WithBody(types.Body{Transactions: append(postOsakaTxs, tx)})
chain.blocks[2] = block2
pool := New(Config{Datadir: storage}, chain, nil)
if err := pool.Init(1, header0, newReserver()); err != nil {
t.Fatalf("failed to create blob pool: %v", err)
}
errs := pool.Add(preOsakaTxs, true)
for i, err := range errs {
if err != nil {
t.Errorf("failed to insert blob tx from %s: %s", addrs[i], errs[i])
}
}
// Kick off migration.
pool.Reset(header0, header1)
// Add the v0 sidecar tx, but don't block so we can keep doing other stuff
// while it converts the sidecar.
addDone := make(chan struct{})
go func() {
pool.Add(types.Transactions{postOsakaTxs[0]}, false)
close(addDone)
}()
// Add the post-Osaka v1 sidecar txs.
errs = pool.Add(postOsakaTxs[1:], false)
for _, err := range errs {
if err != nil {
t.Fatalf("expected tx add to succeed: %v", err)
}
}
// Wait for the first tx's conversion to complete, then check that all
// transactions added after Osaka can be accounted for in the pool.
<-addDone
pending := pool.Pending(txpool.PendingFilter{BlobTxs: true, BlobVersion: types.BlobSidecarVersion1})
for _, tx := range postOsakaTxs {
from, _ := pool.signer.Sender(tx)
if len(pending[from]) != 1 || pending[from][0].Hash != tx.Hash() {
t.Fatalf("expected post-Osaka txs to be pending")
}
}
// Now update the pool with the next block. This should cause the pool to
// clear out the post-Osaka txs since they were included in block 2. Since the
// test blockchain doesn't manage nonces, we'll just do that manually before
// the reset is called. Don't forget about the pre-Osaka transaction we also
// added to block 2!
for i := range postOsakaTxs {
statedb.SetNonce(addrs[len(preOsakaTxs)+i], 1, tracing.NonceChangeEoACall)
}
statedb.SetNonce(addrs[len(preOsakaTxs)-1], 1, tracing.NonceChangeEoACall)
pool.Reset(header1, block2.Header())
// Now verify no post-Osaka transactions are tracked by the pool.
for i, tx := range postOsakaTxs {
if pool.Get(tx.Hash()) != nil {
t.Fatalf("expected txs added post-osaka to have been placed in limbo due to inclusion in a block: index %d, hash %s", i, tx.Hash())
}
}
// Wait for the pool migration to complete.
<-pool.cQueue.anyBillyConversionDone
// Verify all transactions in the pool were converted and verify the
// subsequent cell proofs.
count, _ := pool.Stats()
if count != len(preOsakaTxs)-1 {
t.Errorf("expected pending count to match initial tx count: pending=%d, expected=%d", count, len(preOsakaTxs)-1)
}
for addr, acc := range pool.index {
for _, m := range acc {
if m.version != types.BlobSidecarVersion1 {
t.Errorf("expected sidecar to have been converted: from %s, hash %s", addr, m.hash)
}
tx := pool.Get(m.hash)
if tx == nil {
t.Errorf("failed to get tx by hash: %s", m.hash)
}
sc := tx.BlobTxSidecar()
if err := kzg4844.VerifyCellProofs(sc.Blobs, sc.Commitments, sc.Proofs); err != nil {
t.Errorf("failed to verify cell proofs for tx %s after conversion: %s", m.hash, err)
}
}
}
verifyPoolInternals(t, pool)
// Launch conversion a second time.
// This is just a sanity check to ensure we can handle it.
pool.Reset(header0, header1)
pool.Close()
}
// fakeBilly is a billy.Database implementation which just drops data on the floor.
type fakeBilly struct {
billy.Database
@ -2360,5 +2110,3 @@ func benchmarkPoolPending(b *testing.B, datacap uint64) {
}
}
}
func newUint64(val uint64) *uint64 { return &val }

View file

@ -1,212 +0,0 @@
// 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 blobpool
import (
"errors"
"slices"
"sync/atomic"
"time"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/log"
)
// maxPendingConversionTasks caps the number of pending conversion tasks. This
// prevents excessive memory usage; the worst-case scenario (2k transactions
// with 6 blobs each) would consume approximately 1.5GB of memory.
const maxPendingConversionTasks = 2048
// txConvert represents a conversion task with an attached legacy blob transaction.
type txConvert struct {
tx *types.Transaction // Legacy blob transaction
done chan error // Channel for signaling back if the conversion succeeds
}
// conversionQueue is a dedicated queue for converting legacy blob transactions
// received from the network after the Osaka fork. Since conversion is expensive,
// it is performed in the background by a single thread, ensuring the main Geth
// process is not overloaded.
type conversionQueue struct {
tasks chan *txConvert
startBilly chan func()
quit chan struct{}
closed chan struct{}
billyQueue []func()
billyTaskDone chan struct{}
// This channel will be closed when the first billy conversion finishes.
// It's added for unit tests to synchronize with the conversion progress.
anyBillyConversionDone chan struct{}
}
// newConversionQueue constructs the conversion queue.
func newConversionQueue() *conversionQueue {
q := &conversionQueue{
tasks: make(chan *txConvert),
startBilly: make(chan func()),
quit: make(chan struct{}),
closed: make(chan struct{}),
anyBillyConversionDone: make(chan struct{}),
}
go q.loop()
return q
}
// convert accepts a legacy blob transaction with version-0 blobs and queues it
// for conversion.
//
// This function may block for a long time until the transaction is processed.
func (q *conversionQueue) convert(tx *types.Transaction) error {
done := make(chan error, 1)
select {
case q.tasks <- &txConvert{tx: tx, done: done}:
return <-done
case <-q.closed:
return errors.New("conversion queue closed")
}
}
// launchBillyConversion starts a conversion task in the background.
func (q *conversionQueue) launchBillyConversion(fn func()) error {
select {
case q.startBilly <- fn:
return nil
case <-q.closed:
return errors.New("conversion queue closed")
}
}
// close terminates the conversion queue.
func (q *conversionQueue) close() {
select {
case <-q.closed:
return
default:
close(q.quit)
<-q.closed
}
}
// run converts a batch of legacy blob txs to the new cell proof format.
func (q *conversionQueue) run(tasks []*txConvert, done chan struct{}, interrupt *atomic.Int32) {
defer close(done)
for _, t := range tasks {
if interrupt != nil && interrupt.Load() != 0 {
t.done <- errors.New("conversion is interrupted")
continue
}
sidecar := t.tx.BlobTxSidecar()
if sidecar == nil {
t.done <- errors.New("tx without sidecar")
continue
}
// Run the conversion, the original sidecar will be mutated in place
start := time.Now()
err := sidecar.ToV1()
t.done <- err
log.Trace("Converted legacy blob tx", "hash", t.tx.Hash(), "err", err, "elapsed", common.PrettyDuration(time.Since(start)))
}
}
func (q *conversionQueue) loop() {
defer close(q.closed)
var (
done chan struct{} // Non-nil if background routine is active
interrupt *atomic.Int32 // Flag to signal conversion interruption
// The pending tasks for sidecar conversion. We assume the number of legacy
// blob transactions requiring conversion will not be excessive. However,
// a hard cap is applied as a protective measure.
txTasks []*txConvert
firstBilly = true
)
for {
select {
case t := <-q.tasks:
if len(txTasks) >= maxPendingConversionTasks {
t.done <- errors.New("conversion queue is overloaded")
continue
}
txTasks = append(txTasks, t)
// Launch the background conversion thread if it's idle
if done == nil {
done, interrupt = make(chan struct{}), new(atomic.Int32)
tasks := slices.Clone(txTasks)
txTasks = txTasks[:0]
go q.run(tasks, done, interrupt)
}
case <-done:
done, interrupt = nil, nil
case fn := <-q.startBilly:
q.billyQueue = append(q.billyQueue, fn)
q.runNextBillyTask()
case <-q.billyTaskDone:
if firstBilly {
close(q.anyBillyConversionDone)
firstBilly = false
}
q.runNextBillyTask()
case <-q.quit:
if done != nil {
log.Debug("Waiting for blob proof conversion to exit")
interrupt.Store(1)
<-done
}
if q.billyTaskDone != nil {
log.Debug("Waiting for blobpool billy conversion to exit")
<-q.billyTaskDone
}
// Signal any tasks that were queued for the next batch but never started
// so callers blocked in convert() receive an error instead of hanging.
for _, t := range txTasks {
// Best-effort notify; t.done is a buffered channel of size 1
// created by convert(), and we send exactly once per task.
t.done <- errors.New("conversion queue closed")
}
// Drop references to allow GC of the backing array.
txTasks = txTasks[:0]
return
}
}
}
func (q *conversionQueue) runNextBillyTask() {
if len(q.billyQueue) == 0 {
q.billyTaskDone = nil
return
}
fn := q.billyQueue[0]
q.billyQueue = append(q.billyQueue[:0], q.billyQueue[1:]...)
done := make(chan struct{})
go func() { defer close(done); fn() }()
q.billyTaskDone = done
}

View file

@ -1,101 +0,0 @@
// 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 blobpool
import (
"crypto/ecdsa"
"crypto/sha256"
"testing"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/crypto/kzg4844"
"github.com/ethereum/go-ethereum/params"
"github.com/holiman/uint256"
)
// createV1BlobTx creates a blob transaction with version 1 sidecar for testing.
func createV1BlobTx(nonce uint64, key *ecdsa.PrivateKey) *types.Transaction {
blob := &kzg4844.Blob{byte(nonce)}
commitment, _ := kzg4844.BlobToCommitment(blob)
cellProofs, _ := kzg4844.ComputeCellProofs(blob)
blobtx := &types.BlobTx{
ChainID: uint256.MustFromBig(params.MainnetChainConfig.ChainID),
Nonce: nonce,
GasTipCap: uint256.NewInt(1),
GasFeeCap: uint256.NewInt(1000),
Gas: 21000,
BlobFeeCap: uint256.NewInt(100),
BlobHashes: []common.Hash{kzg4844.CalcBlobHashV1(sha256.New(), &commitment)},
Value: uint256.NewInt(100),
Sidecar: types.NewBlobTxSidecar(types.BlobSidecarVersion1, []kzg4844.Blob{*blob}, []kzg4844.Commitment{commitment}, cellProofs),
}
return types.MustSignNewTx(key, types.LatestSigner(params.MainnetChainConfig), blobtx)
}
func TestConversionQueueBasic(t *testing.T) {
queue := newConversionQueue()
defer queue.close()
key, _ := crypto.GenerateKey()
tx := makeTx(0, 1, 1, 1, key)
if err := queue.convert(tx); err != nil {
t.Fatalf("Expected successful conversion, got error: %v", err)
}
if tx.BlobTxSidecar().Version != types.BlobSidecarVersion1 {
t.Errorf("Expected sidecar version to be %d, got %d", types.BlobSidecarVersion1, tx.BlobTxSidecar().Version)
}
}
func TestConversionQueueV1BlobTx(t *testing.T) {
queue := newConversionQueue()
defer queue.close()
key, _ := crypto.GenerateKey()
tx := createV1BlobTx(0, key)
version := tx.BlobTxSidecar().Version
err := queue.convert(tx)
if err != nil {
t.Fatalf("Expected successful conversion, got error: %v", err)
}
if tx.BlobTxSidecar().Version != version {
t.Errorf("Expected sidecar version to remain %d, got %d", version, tx.BlobTxSidecar().Version)
}
}
func TestConversionQueueClosed(t *testing.T) {
queue := newConversionQueue()
// Close the queue first
queue.close()
key, _ := crypto.GenerateKey()
tx := makeTx(0, 1, 1, 1, key)
err := queue.convert(tx)
if err == nil {
t.Fatal("Expected error when converting on closed queue, got nil")
}
}
func TestConversionQueueDoubleClose(t *testing.T) {
queue := newConversionQueue()
queue.close()
queue.close() // Should not panic
}

View file

@ -20,7 +20,6 @@ import (
"errors"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/consensus/misc/eip4844"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/params"
@ -57,7 +56,7 @@ func newLimbo(config *params.ChainConfig, datadir string) (*limbo, error) {
}
// Create new slotter for pre-Osaka blob configuration.
slotter := newSlotter(eip4844.LatestMaxBlobsPerBlock(config))
slotter := newSlotter(params.BlobTxMaxBlobs)
// See if we need to migrate the limbo after fusaka.
slotter, err := tryMigrate(config, slotter, datadir)

View file

@ -110,13 +110,3 @@ func (l *lookup) untrack(tx *blobTxMeta) {
}
}
}
// update updates the transaction index. It should only be used in the conversion.
func (l *lookup) update(hash common.Hash, id uint64, size uint64) bool {
meta, exists := l.txIndex[hash]
if !exists {
return false
}
meta.id, meta.size = id, size
return true
}

View file

@ -17,7 +17,6 @@
package blobpool
import (
"github.com/ethereum/go-ethereum/consensus/misc/eip4844"
"github.com/ethereum/go-ethereum/params"
"github.com/holiman/billy"
)
@ -42,7 +41,7 @@ func tryMigrate(config *params.ChainConfig, slotter billy.SlotSizeFn, datadir st
// If the version found is less than the currently configured store version,
// perform a migration then write the updated version of the store.
if version < storeVersion {
newSlotter := newSlotterEIP7594(eip4844.LatestMaxBlobsPerBlock(config))
newSlotter := newSlotterEIP7594(params.BlobTxMaxBlobs)
if err := billy.Migrate(billy.Options{Path: datadir, Repair: true}, slotter, newSlotter); err != nil {
return nil, err
}
@ -54,7 +53,7 @@ func tryMigrate(config *params.ChainConfig, slotter billy.SlotSizeFn, datadir st
store.Close()
}
// Set the slotter to the format now that the Osaka is active.
slotter = newSlotterEIP7594(eip4844.LatestMaxBlobsPerBlock(config))
slotter = newSlotterEIP7594(params.BlobTxMaxBlobs)
}
return slotter, nil
}

View file

@ -288,7 +288,12 @@ func New(config Config, chain BlockChain) *LegacyPool {
// Filter returns whether the given transaction can be consumed by the legacy
// pool, specifically, whether it is a Legacy, AccessList or Dynamic transaction.
func (pool *LegacyPool) Filter(tx *types.Transaction) bool {
switch tx.Type() {
return pool.FilterType(tx.Type())
}
// FilterType returns whether the legacy pool supports the given transaction type.
func (pool *LegacyPool) FilterType(kind byte) bool {
switch kind {
case types.LegacyTxType, types.AccessListTxType, types.DynamicFeeTxType, types.SetCodeTxType:
return true
default:

View file

@ -100,6 +100,9 @@ type SubPool interface {
// to this particular subpool.
Filter(tx *types.Transaction) bool
// FilterType returns whether the subpool supports the given transaction type.
FilterType(kind byte) bool
// Init sets the base parameters of the subpool, allowing it to load any saved
// transactions from disk and also permitting internal maintenance routines to
// start up.

View file

@ -489,3 +489,14 @@ func (p *TxPool) Clear() {
subpool.Clear()
}
}
// FilterType returns whether a transaction with the given type is supported
// (can be added) by the pool.
func (p *TxPool) FilterType(kind byte) bool {
for _, subpool := range p.subpools {
if subpool.FilterType(kind) {
return true
}
}
return false
}

View file

@ -130,7 +130,7 @@ func ValidateTransaction(tx *types.Transaction, head *types.Header, signer types
return fmt.Errorf("%w: gas %v, minimum needed %v", core.ErrIntrinsicGas, tx.Gas(), intrGas)
}
// Ensure the transaction can cover floor data gas.
if opts.Config.IsPrague(head.Number, head.Time) {
if rules.IsPrague {
floorDataGas, err := core.FloorDataGas(tx.Data())
if err != nil {
return err
@ -160,6 +160,15 @@ func validateBlobTx(tx *types.Transaction, head *types.Header, opts *ValidationO
if sidecar == nil {
return errors.New("missing sidecar in blob transaction")
}
// Ensure the sidecar is constructed with the correct version, consistent
// with the current fork.
version := types.BlobSidecarVersion0
if opts.Config.IsOsaka(head.Number, head.Time) {
version = types.BlobSidecarVersion1
}
if sidecar.Version != version {
return fmt.Errorf("unexpected sidecar version, want: %d, got: %d", version, sidecar.Version)
}
// Ensure the blob fee cap satisfies the minimum blob gas price
if tx.BlobGasFeeCapIntCmp(blobTxMinBlobGasPrice) < 0 {
return fmt.Errorf("%w: blob fee cap %v, minimum needed %v", ErrTxGasPriceTooLow, tx.BlobGasFeeCap(), blobTxMinBlobGasPrice)

View file

@ -31,7 +31,6 @@ import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-verkle"
)
// A BlockNonce is a 64-bit hash which proves (combined with the
@ -61,13 +60,6 @@ func (n *BlockNonce) UnmarshalText(input []byte) error {
return hexutil.UnmarshalFixedText("BlockNonce", input, n[:])
}
// ExecutionWitness represents the witness + proof used in a verkle context,
// to provide the ability to execute a block statelessly.
type ExecutionWitness struct {
StateDiff verkle.StateDiff `json:"stateDiff"`
VerkleProof *verkle.VerkleProof `json:"verkleProof"`
}
//go:generate go run github.com/fjl/gencodec -type Header -field-override headerMarshaling -out gen_header_json.go
//go:generate go run ../../rlp/rlpgen -type Header -out gen_header_rlp.go
@ -209,11 +201,6 @@ type Block struct {
transactions Transactions
withdrawals Withdrawals
// witness is not an encoded part of the block body.
// It is held in Block in order for easy relaying to the places
// that process it.
witness *ExecutionWitness
// caches
hash atomic.Pointer[common.Hash]
size atomic.Uint64
@ -429,9 +416,6 @@ func (b *Block) BlobGasUsed() *uint64 {
return blobGasUsed
}
// ExecutionWitness returns the verkle execution witneess + proof for a block
func (b *Block) ExecutionWitness() *ExecutionWitness { return b.witness }
// Size returns the true RLP encoded storage size of the block, either by encoding
// and returning it, or returning a previously cached value.
func (b *Block) Size() uint64 {
@ -494,7 +478,6 @@ func (b *Block) WithSeal(header *Header) *Block {
transactions: b.transactions,
uncles: b.uncles,
withdrawals: b.withdrawals,
witness: b.witness,
}
}
@ -506,7 +489,6 @@ func (b *Block) WithBody(body Body) *Block {
transactions: slices.Clone(body.Transactions),
uncles: make([]*Header, len(body.Uncles)),
withdrawals: slices.Clone(body.Withdrawals),
witness: b.witness,
}
for i := range body.Uncles {
block.uncles[i] = CopyHeader(body.Uncles[i])
@ -514,16 +496,6 @@ func (b *Block) WithBody(body Body) *Block {
return block
}
func (b *Block) WithWitness(witness *ExecutionWitness) *Block {
return &Block{
header: b.header,
transactions: b.transactions,
uncles: b.uncles,
withdrawals: b.withdrawals,
witness: witness,
}
}
// Hash returns the keccak256 hash of b's header.
// The hash is computed on the first call and cached thereafter.
func (b *Block) Hash() common.Hash {

View file

@ -214,7 +214,7 @@ func (evm *EVM) SetJumpDestCache(jumpDests JumpDestCache) {
// This is not threadsafe and should only be done very cautiously.
func (evm *EVM) SetTxContext(txCtx TxContext) {
if evm.chainRules.IsEIP4762 {
txCtx.AccessEvents = state.NewAccessEvents(evm.StateDB.PointCache())
txCtx.AccessEvents = state.NewAccessEvents()
}
evm.TxContext = txCtx
}

View file

@ -964,7 +964,7 @@ func opDupN(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
//The nth stack item is duplicated at the top of the stack.
scope.Stack.push(scope.Stack.Back(n - 1))
*pc += 2
*pc += 1
return nil, nil
}
@ -993,7 +993,7 @@ func opSwapN(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
indexTop := scope.Stack.len() - 1
indexN := scope.Stack.len() - 1 - n
scope.Stack.data[indexTop], scope.Stack.data[indexN] = scope.Stack.data[indexN], scope.Stack.data[indexTop]
*pc += 2
*pc += 1
return nil, nil
}
@ -1025,7 +1025,7 @@ func opExchange(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
indexN := scope.Stack.len() - 1 - n
indexM := scope.Stack.len() - 1 - m
scope.Stack.data[indexN], scope.Stack.data[indexM] = scope.Stack.data[indexM], scope.Stack.data[indexN]
*pc += 2
*pc += 1
return nil, nil
}

View file

@ -1107,6 +1107,11 @@ func TestEIP8024_Execution(t *testing.T) {
codeHex: "e8", // no operand
wantErr: true,
},
{
name: "PC_INCREMENT",
codeHex: "600060006000e80115",
wantVals: []uint64{1, 0, 0},
},
}
for _, tc := range tests {
@ -1123,17 +1128,15 @@ func TestEIP8024_Execution(t *testing.T) {
return
case 0x60:
_, err = opPush1(&pc, evm, scope)
pc++
case 0x80:
dup1 := makeDup(1)
_, err = dup1(&pc, evm, scope)
pc++
case 0x56:
_, err = opJump(&pc, evm, scope)
pc++
case 0x5b:
_, err = opJumpdest(&pc, evm, scope)
pc++
case 0x15:
_, err = opIszero(&pc, evm, scope)
case 0xe6:
_, err = opDupN(&pc, evm, scope)
case 0xe7:
@ -1143,6 +1146,7 @@ func TestEIP8024_Execution(t *testing.T) {
default:
err = &ErrInvalidOpCode{opcode: OpCode(op)}
}
pc++
}
if tc.wantErr {
if err == nil {

View file

@ -23,7 +23,6 @@ import (
"github.com/ethereum/go-ethereum/core/tracing"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/trie/utils"
"github.com/holiman/uint256"
)
@ -84,9 +83,6 @@ type StateDB interface {
// even if the feature/fork is not active yet
AddSlotToAccessList(addr common.Address, slot common.Hash)
// PointCache returns the point cache used in computations
PointCache() *utils.PointCache
Prepare(rules params.Rules, sender, coinbase common.Address, dest *common.Address, precompiles []common.Address, txAccesses types.AccessList)
RevertToSnapshot(int)

View file

@ -56,7 +56,7 @@ func Fuzz(data []byte) int {
fmt.Printf("untrusted: %v\n", untrustedComment)
fmt.Printf("trusted: %v\n", trustedComment)
err = SignifySignFile(tmpFile.Name(), tmpFile.Name()+".sig", testSecKey, untrustedComment, trustedComment)
err = SignFile(tmpFile.Name(), tmpFile.Name()+".sig", testSecKey, untrustedComment, trustedComment)
if err != nil {
panic(err)
}
@ -68,7 +68,7 @@ func Fuzz(data []byte) int {
signify = path
}
_, err := exec.LookPath(signify)
_, err = exec.LookPath(signify)
if err != nil {
panic(err)
}

View file

@ -499,17 +499,14 @@ func (api *DebugAPI) ExecutionWitness(bn rpc.BlockNumber) (*stateless.ExtWitness
if err != nil {
return &stateless.ExtWitness{}, fmt.Errorf("block number %v not found", bn)
}
parent := bc.GetHeader(block.ParentHash(), block.NumberU64()-1)
if parent == nil {
return &stateless.ExtWitness{}, fmt.Errorf("block number %v found, but parent missing", bn)
}
result, err := bc.ProcessBlock(parent.Root, block, false, true)
if err != nil {
return nil, err
}
return result.Witness().ToExtWitness(), nil
}
@ -519,16 +516,13 @@ func (api *DebugAPI) ExecutionWitnessByHash(hash common.Hash) (*stateless.ExtWit
if block == nil {
return &stateless.ExtWitness{}, fmt.Errorf("block hash %x not found", hash)
}
parent := bc.GetHeader(block.ParentHash(), block.NumberU64()-1)
if parent == nil {
return &stateless.ExtWitness{}, fmt.Errorf("block number %x found, but parent missing", hash)
}
result, err := bc.ProcessBlock(parent.Root, block, false, true)
if err != nil {
return nil, err
}
return result.Witness().ToExtWitness(), nil
}

View file

@ -244,6 +244,7 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) {
// - DATADIR/triedb/verkle.journal
TrieJournalDirectory: stack.ResolvePath("triedb"),
StateSizeTracking: config.EnableStateSizeTracking,
SlowBlockThreshold: config.SlowBlockThreshold,
}
)
if config.VMTrace != "" {
@ -591,29 +592,3 @@ func (s *Ethereum) Stop() error {
return nil
}
// SyncMode retrieves the current sync mode, either explicitly set, or derived
// from the chain status.
func (s *Ethereum) SyncMode() ethconfig.SyncMode {
// If we're in snap sync mode, return that directly
if s.handler.snapSync.Load() {
return ethconfig.SnapSync
}
// We are probably in full sync, but we might have rewound to before the
// snap sync pivot, check if we should re-enable snap sync.
head := s.blockchain.CurrentBlock()
if pivot := rawdb.ReadLastPivotNumber(s.chainDb); pivot != nil {
if head.Number.Uint64() < *pivot {
return ethconfig.SnapSync
}
}
// We are in a full sync, but the associated head state is missing. To complete
// the head state, forcefully rerun the snap sync. Note it doesn't mean the
// persistent state is corrupted, just mismatch with the head block.
if !s.blockchain.HasState(head.Root) {
log.Info("Reenabled snap sync as chain is stateless")
return ethconfig.SnapSync
}
// Nope, we're really full syncing
return ethconfig.FullSync
}

View file

@ -36,7 +36,6 @@ import (
"github.com/ethereum/go-ethereum/eth/ethconfig"
"github.com/ethereum/go-ethereum/internal/version"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/metrics"
"github.com/ethereum/go-ethereum/miner"
"github.com/ethereum/go-ethereum/node"
"github.com/ethereum/go-ethereum/params"
@ -81,20 +80,6 @@ const (
beaconUpdateWarnFrequency = 5 * time.Minute
)
var (
// Number of blobs requested via getBlobsV2
getBlobsRequestedCounter = metrics.NewRegisteredCounter("engine/getblobs/requested", nil)
// Number of blobs requested via getBlobsV2 that are present in the blobpool
getBlobsAvailableCounter = metrics.NewRegisteredCounter("engine/getblobs/available", nil)
// Number of times getBlobsV2 responded with “hit”
getBlobsV2RequestHit = metrics.NewRegisteredCounter("engine/getblobs/hit", nil)
// Number of times getBlobsV2 responded with “miss”
getBlobsV2RequestMiss = metrics.NewRegisteredCounter("engine/getblobs/miss", nil)
)
type ConsensusAPI struct {
eth *eth.Ethereum
@ -137,6 +122,9 @@ type ConsensusAPI struct {
// NewConsensusAPI creates a new consensus api for the given backend.
// The underlying blockchain needs to have a valid terminal total difficulty set.
//
// This function creates a long-lived object with an attached background thread.
// For testing or other short-term use cases, please use newConsensusAPIWithoutHeartbeat.
func NewConsensusAPI(eth *eth.Ethereum) *ConsensusAPI {
api := newConsensusAPIWithoutHeartbeat(eth)
go api.heartbeat()
@ -270,7 +258,7 @@ func (api *ConsensusAPI) forkchoiceUpdated(update engine.ForkchoiceStateV1, payl
}
}
log.Info("Forkchoice requested sync to new head", context...)
if err := api.eth.Downloader().BeaconSync(api.eth.SyncMode(), header, finalized); err != nil {
if err := api.eth.Downloader().BeaconSync(header, finalized); err != nil {
return engine.STATUS_SYNCING, err
}
return engine.STATUS_SYNCING, nil
@ -402,10 +390,12 @@ func (api *ConsensusAPI) ExchangeTransitionConfigurationV1(config engine.Transit
// GetPayloadV1 returns a cached payload by id.
func (api *ConsensusAPI) GetPayloadV1(payloadID engine.PayloadID) (*engine.ExecutableData, error) {
if !payloadID.Is(engine.PayloadV1) {
return nil, engine.UnsupportedFork
}
data, err := api.getPayload(payloadID, false)
data, err := api.getPayload(
payloadID,
false,
[]engine.PayloadVersion{engine.PayloadV1},
nil,
)
if err != nil {
return nil, err
}
@ -414,35 +404,34 @@ func (api *ConsensusAPI) GetPayloadV1(payloadID engine.PayloadID) (*engine.Execu
// GetPayloadV2 returns a cached payload by id.
func (api *ConsensusAPI) GetPayloadV2(payloadID engine.PayloadID) (*engine.ExecutionPayloadEnvelope, error) {
// executionPayload: ExecutionPayloadV1 | ExecutionPayloadV2 where:
//
// - ExecutionPayloadV1 MUST be returned if the payload timestamp is lower
// than the Shanghai timestamp
//
// - ExecutionPayloadV2 MUST be returned if the payload timestamp is greater
// or equal to the Shanghai timestamp
if !payloadID.Is(engine.PayloadV1, engine.PayloadV2) {
return nil, engine.UnsupportedFork
}
return api.getPayload(payloadID, false)
return api.getPayload(
payloadID,
false,
[]engine.PayloadVersion{engine.PayloadV1, engine.PayloadV2},
[]forks.Fork{forks.Shanghai},
)
}
// GetPayloadV3 returns a cached payload by id. This endpoint should only
// be used for the Cancun fork.
func (api *ConsensusAPI) GetPayloadV3(payloadID engine.PayloadID) (*engine.ExecutionPayloadEnvelope, error) {
if !payloadID.Is(engine.PayloadV3) {
return nil, engine.UnsupportedFork
}
return api.getPayload(payloadID, false)
return api.getPayload(
payloadID,
false,
[]engine.PayloadVersion{engine.PayloadV3},
[]forks.Fork{forks.Cancun},
)
}
// GetPayloadV4 returns a cached payload by id. This endpoint should only
// be used for the Prague fork.
func (api *ConsensusAPI) GetPayloadV4(payloadID engine.PayloadID) (*engine.ExecutionPayloadEnvelope, error) {
if !payloadID.Is(engine.PayloadV3) {
return nil, engine.UnsupportedFork
}
return api.getPayload(payloadID, false)
return api.getPayload(
payloadID,
false,
[]engine.PayloadVersion{engine.PayloadV3},
[]forks.Fork{forks.Prague},
)
}
// GetPayloadV5 returns a cached payload by id. This endpoint should only
@ -451,18 +440,37 @@ func (api *ConsensusAPI) GetPayloadV4(payloadID engine.PayloadID) (*engine.Execu
// This method follows the same specification as engine_getPayloadV4 with
// changes of returning BlobsBundleV2 with BlobSidecar version 1.
func (api *ConsensusAPI) GetPayloadV5(payloadID engine.PayloadID) (*engine.ExecutionPayloadEnvelope, error) {
if !payloadID.Is(engine.PayloadV3) {
return nil, engine.UnsupportedFork
}
return api.getPayload(payloadID, false)
return api.getPayload(
payloadID,
false,
[]engine.PayloadVersion{engine.PayloadV3},
[]forks.Fork{
forks.Osaka,
forks.BPO1,
forks.BPO2,
forks.BPO3,
forks.BPO4,
forks.BPO5,
})
}
func (api *ConsensusAPI) getPayload(payloadID engine.PayloadID, full bool) (*engine.ExecutionPayloadEnvelope, error) {
// getPayload will retrieve the specified payload and verify it conforms to the
// endpoint's allowed payload versions and forks.
//
// Note passing nil `forks`, `versions` disables the respective check.
func (api *ConsensusAPI) getPayload(payloadID engine.PayloadID, full bool, versions []engine.PayloadVersion, forks []forks.Fork) (*engine.ExecutionPayloadEnvelope, error) {
log.Trace("Engine API request received", "method", "GetPayload", "id", payloadID)
if versions != nil && !payloadID.Is(versions...) {
return nil, engine.UnsupportedFork
}
data := api.localBlocks.get(payloadID, full)
if data == nil {
return nil, engine.UnknownPayload
}
if forks != nil && !api.checkFork(data.ExecutionPayload.Timestamp, forks...) {
return nil, engine.UnsupportedFork
}
return data, nil
}
@ -497,7 +505,7 @@ func (api *ConsensusAPI) GetBlobsV1(hashes []common.Hash) ([]*engine.BlobAndProo
if len(hashes) > 128 {
return nil, engine.TooLargeRequest.With(fmt.Errorf("requested blob count too large: %v", len(hashes)))
}
blobs, _, proofs, err := api.eth.BlobTxPool().GetBlobs(hashes, types.BlobSidecarVersion0, false)
blobs, _, proofs, err := api.eth.BlobTxPool().GetBlobs(hashes, types.BlobSidecarVersion0)
if err != nil {
return nil, engine.InvalidParams.With(err)
}
@ -543,8 +551,25 @@ func (api *ConsensusAPI) GetBlobsV1(hashes []common.Hash) ([]*engine.BlobAndProo
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")
return nil, nil
}
return api.getBlobs(hashes, true)
}
// GetBlobsV3 returns a set of blobs from the transaction pool. Same as
// GetBlobsV2, except will return partial responses in case there is a missing
// blob.
func (api *ConsensusAPI) GetBlobsV3(hashes []common.Hash) ([]*engine.BlobAndProofV2, error) {
head := api.eth.BlockChain().CurrentHeader()
if api.config().LatestFork(head.Time) < forks.Osaka {
return nil, nil
}
return api.getBlobs(hashes, false)
}
// getBlobs returns all available blobs. In v2, partial responses are not allowed,
// while v3 supports partial responses.
func (api *ConsensusAPI) getBlobs(hashes []common.Hash, v2 bool) ([]*engine.BlobAndProofV2, error) {
if len(hashes) > 128 {
return nil, engine.TooLargeRequest.With(fmt.Errorf("requested blob count too large: %v", len(hashes)))
}
@ -552,28 +577,30 @@ func (api *ConsensusAPI) GetBlobsV2(hashes []common.Hash) ([]*engine.BlobAndProo
getBlobsRequestedCounter.Inc(int64(len(hashes)))
getBlobsAvailableCounter.Inc(int64(available))
// Optimization: check first if all blobs are available, if not, return empty response
if available != len(hashes) {
getBlobsV2RequestMiss.Inc(1)
// Short circuit if partial response is not allowed
if v2 && available != len(hashes) {
getBlobsRequestMiss.Inc(1)
return nil, nil
}
blobs, _, proofs, err := api.eth.BlobTxPool().GetBlobs(hashes, types.BlobSidecarVersion1, false)
// Retrieve blobs from the pool. This operation is expensive and may involve
// heavy disk I/O.
blobs, _, proofs, err := api.eth.BlobTxPool().GetBlobs(hashes, types.BlobSidecarVersion1)
if err != nil {
return nil, engine.InvalidParams.With(err)
}
// To comply with API spec, check again that we really got all data needed
for _, blob := range blobs {
if blob == nil {
getBlobsV2RequestMiss.Inc(1)
return nil, nil
}
}
getBlobsV2RequestHit.Inc(1)
// Validate the blobs from the pool and assemble the response
res := make([]*engine.BlobAndProofV2, len(hashes))
for i := 0; i < len(blobs); i++ {
for i := range blobs {
// The blob has been evicted since the last AvailableBlobs call.
// Return null if partial response is not allowed.
if blobs[i] == nil {
if !v2 {
continue
} else {
getBlobsRequestMiss.Inc(1)
return nil, nil
}
}
var cellProofs []hexutil.Bytes
for _, proof := range proofs[i] {
cellProofs = append(cellProofs, proof[:])
@ -583,6 +610,13 @@ func (api *ConsensusAPI) GetBlobsV2(hashes []common.Hash) ([]*engine.BlobAndProo
CellProofs: cellProofs,
}
}
if len(res) == len(hashes) {
getBlobsRequestCompleteHit.Inc(1)
} else if len(res) > 0 {
getBlobsRequestPartialHit.Inc(1)
} else {
getBlobsRequestMiss.Inc(1)
}
return res, nil
}
@ -737,14 +771,14 @@ func (api *ConsensusAPI) newPayload(params engine.ExecutableData, versionedHashe
return api.delayPayloadImport(block), nil
}
if block.Time() <= parent.Time() {
log.Warn("Invalid timestamp", "parent", block.Time(), "block", block.Time())
log.Warn("Invalid timestamp", "parent", parent.Time(), "block", block.Time())
return api.invalid(errors.New("invalid timestamp"), parent.Header()), nil
}
// Another corner case: if the node is in snap sync mode, but the CL client
// tries to make it import a block. That should be denied as pushing something
// into the database directly will conflict with the assumptions of snap sync
// that it has an empty db that it can fill itself.
if api.eth.SyncMode() != ethconfig.FullSync {
if api.eth.Downloader().ConfigSyncMode() == ethconfig.SnapSync {
return api.delayPayloadImport(block), nil
}
if !api.eth.BlockChain().HasBlockAndState(block.ParentHash(), block.NumberU64()-1) {
@ -792,16 +826,16 @@ func (api *ConsensusAPI) delayPayloadImport(block *types.Block) engine.PayloadSt
// Although we don't want to trigger a sync, if there is one already in
// progress, try to extend it with the current payload request to relieve
// some strain from the forkchoice update.
err := api.eth.Downloader().BeaconExtend(api.eth.SyncMode(), block.Header())
err := api.eth.Downloader().BeaconExtend(block.Header())
if err == nil {
log.Debug("Payload accepted for sync extension", "number", block.NumberU64(), "hash", block.Hash())
return engine.PayloadStatusV1{Status: engine.SYNCING}
}
// Either no beacon sync was started yet, or it rejected the delivered
// payload as non-integratable on top of the existing sync. We'll just
// payload as non-integrate on top of the existing sync. We'll just
// have to rely on the beacon client to forcefully update the head with
// a forkchoice update request.
if api.eth.SyncMode() == ethconfig.FullSync {
if api.eth.Downloader().ConfigSyncMode() == ethconfig.FullSync {
// In full sync mode, failure to import a well-formed block can only mean
// that the parent state is missing and the syncer rejected extending the
// current cycle with the new payload.
@ -896,8 +930,6 @@ func (api *ConsensusAPI) invalid(err error, latestValid *types.Header) engine.Pa
// heartbeat loops indefinitely, and checks if there have been beacon client updates
// received in the last while. If not - or if they but strange ones - it warns the
// user that something might be off with their consensus node.
//
// TODO(karalabe): Spin this goroutine down somehow
func (api *ConsensusAPI) heartbeat() {
// Sleep a bit on startup since there's obviously no beacon client yet
// attached, so no need to print scary warnings to the user.

View file

@ -203,7 +203,7 @@ func TestEth2PrepareAndGetPayload(t *testing.T) {
BeaconRoot: blockParams.BeaconRoot,
Version: engine.PayloadV1,
}).Id()
execData, err := api.getPayload(payloadID, true)
execData, err := api.getPayload(payloadID, true, nil, nil)
if err != nil {
t.Fatalf("error getting payload, err=%v", err)
}
@ -426,7 +426,7 @@ func TestEth2DeepReorg(t *testing.T) {
}
// startEthService creates a full node instance for testing.
func startEthService(t *testing.T, genesis *core.Genesis, blocks []*types.Block) (*node.Node, *eth.Ethereum) {
func startEthService(t testing.TB, genesis *core.Genesis, blocks []*types.Block) (*node.Node, *eth.Ethereum) {
t.Helper()
n, err := node.New(&node.Config{
@ -636,7 +636,7 @@ func TestNewPayloadOnInvalidChain(t *testing.T) {
t.Fatalf("error preparing payload, invalid status: %v", resp.PayloadStatus.Status)
}
// give the payload some time to be built
if payload, err = api.getPayload(*resp.PayloadID, true); err != nil {
if payload, err = api.getPayload(*resp.PayloadID, true, nil, nil); err != nil {
t.Fatalf("can't get payload: %v", err)
}
if len(payload.ExecutionPayload.Transactions) > 0 {
@ -1219,7 +1219,7 @@ func TestNilWithdrawals(t *testing.T) {
Random: test.blockParams.Random,
Version: payloadVersion,
}).Id()
execData, err := api.GetPayloadV2(payloadID)
execData, err := api.getPayload(payloadID, false, nil, nil)
if err != nil {
t.Fatalf("error getting payload, err=%v", err)
}
@ -1674,7 +1674,7 @@ func TestWitnessCreationAndConsumption(t *testing.T) {
BeaconRoot: blockParams.BeaconRoot,
Version: engine.PayloadV3,
}).Id()
envelope, err := api.getPayload(payloadID, true)
envelope, err := api.getPayload(payloadID, true, nil, nil)
if err != nil {
t.Fatalf("error getting payload, err=%v", err)
}
@ -1873,7 +1873,7 @@ func makeMultiBlobTx(chainConfig *params.ChainConfig, nonce uint64, blobCount in
return types.MustSignNewTx(key, types.LatestSigner(chainConfig), blobtx)
}
func newGetBlobEnv(t *testing.T, version byte) (*node.Node, *ConsensusAPI) {
func newGetBlobEnv(t testing.TB, version byte) (*node.Node, *ConsensusAPI) {
var (
// Create a database pre-initialize with a genesis block
config = *params.MergedTestChainConfig
@ -2016,7 +2016,7 @@ func TestGetBlobsV1AfterOsakaFork(t *testing.T) {
}
}
func TestGetBlobsV2(t *testing.T) {
func TestGetBlobsV2And3(t *testing.T) {
n, api := newGetBlobEnv(t, 1)
defer n.Close()
@ -2045,36 +2045,64 @@ func TestGetBlobsV2(t *testing.T) {
},
}
for i, suite := range suites {
// Fill the request for retrieving blobs
var (
vhashes []common.Hash
expect []*engine.BlobAndProofV2
)
// fill missing blob
if suite.fillRandom {
vhashes = append(vhashes, testrand.Hash())
}
for j := suite.start; j < suite.limit; j++ {
vhashes = append(vhashes, testBlobVHashes[j])
var cellProofs []hexutil.Bytes
for _, proof := range testBlobCellProofs[j] {
cellProofs = append(cellProofs, proof[:])
}
expect = append(expect, &engine.BlobAndProofV2{
Blob: testBlobs[j][:],
CellProofs: cellProofs,
})
}
result, err := api.GetBlobsV2(vhashes)
if err != nil {
t.Errorf("Unexpected error for case %d, %v", i, err)
}
// null is responded if any blob is missing
if suite.fillRandom {
expect = nil
}
if !reflect.DeepEqual(result, expect) {
t.Fatalf("Unexpected result for case %d", i)
}
runGetBlobs(t, api.GetBlobsV2, suite.start, suite.limit, suite.fillRandom, false, fmt.Sprintf("GetBlobsV2 suite=%d", i))
runGetBlobs(t, api.GetBlobsV3, suite.start, suite.limit, suite.fillRandom, true, fmt.Sprintf("GetBlobsV3 suite=%d %v", i, suite))
}
}
// Benchmark GetBlobsV2 internals
// Note that this is not an RPC-level benchmark, so JSON-RPC overhead is not included.
func BenchmarkGetBlobsV2(b *testing.B) {
n, api := newGetBlobEnv(b, 1)
defer n.Close()
// for blobs in [1, 2, 4, 6], print string and run benchmark
for _, blobs := range []int{1, 2, 4, 6} {
name := fmt.Sprintf("blobs=%d", blobs)
b.Run(name, func(b *testing.B) {
for b.Loop() {
runGetBlobs(b, api.GetBlobsV2, 0, blobs, false, false, name)
}
})
}
}
type getBlobsFn func(hashes []common.Hash) ([]*engine.BlobAndProofV2, error)
func runGetBlobs(t testing.TB, getBlobs getBlobsFn, start, limit int, fillRandom bool, expectPartialResponse bool, name string) {
// Fill the request for retrieving blobs
var (
vhashes []common.Hash
expect []*engine.BlobAndProofV2
)
for j := start; j < limit; j++ {
vhashes = append(vhashes, testBlobVHashes[j])
var cellProofs []hexutil.Bytes
for _, proof := range testBlobCellProofs[j] {
cellProofs = append(cellProofs, proof[:])
}
expect = append(expect, &engine.BlobAndProofV2{
Blob: testBlobs[j][:],
CellProofs: cellProofs,
})
}
// fill missing blob
if fillRandom {
vhashes = append(vhashes, testrand.Hash())
}
result, err := getBlobs(vhashes)
if err != nil {
t.Errorf("Unexpected error for case %s, %v", name, err)
}
if fillRandom {
if expectPartialResponse {
expect = append(expect, nil)
} else {
// Nil is expected if getBlobs can not return a partial response
expect = nil
}
}
if !reflect.DeepEqual(result, expect) {
t.Fatalf("Unexpected result for case %s", name)
}
}

37
eth/catalyst/metrics.go Normal file
View file

@ -0,0 +1,37 @@
// 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 catalyst
import "github.com/ethereum/go-ethereum/metrics"
var (
// Number of blobs requested via getBlobsV2
getBlobsRequestedCounter = metrics.NewRegisteredCounter("engine/getblobs/requested", nil)
// Number of blobs requested via getBlobsV2 that are present in the blobpool
getBlobsAvailableCounter = metrics.NewRegisteredCounter("engine/getblobs/available", nil)
// Number of times getBlobsV2/V3 responded with all blobs
getBlobsRequestCompleteHit = metrics.NewRegisteredCounter("engine/getblobs/hit", nil)
// Number of times getBlobsV2/V3 responded with no blobs. V2 will return no
// blobs if it doesn't have all the blobs (all or nothing).
getBlobsRequestMiss = metrics.NewRegisteredCounter("engine/getblobs/miss", nil)
// Number of times getBlobsV3 responded with some, but not all, blobs
getBlobsRequestPartialHit = metrics.NewRegisteredCounter("engine/getblobs/partial", nil)
)

View file

@ -214,7 +214,7 @@ func (c *SimulatedBeacon) sealBlock(withdrawals []*types.Withdrawal, timestamp u
return nil
}
envelope, err := c.engineAPI.getPayload(*fcResponse.PayloadID, true)
envelope, err := c.engineAPI.getPayload(*fcResponse.PayloadID, true, nil, nil)
if err != nil {
return err
}

View file

@ -33,14 +33,14 @@ import (
// Note, this must not be used in live code. If the forkchcoice endpoint where
// to use this instead of giving us the payload first, then essentially nobody
// in the network would have the block yet that we'd attempt to retrieve.
func (d *Downloader) BeaconDevSync(mode SyncMode, header *types.Header) error {
func (d *Downloader) BeaconDevSync(header *types.Header) error {
// Be very loud that this code should not be used in a live node
log.Warn("----------------------------------")
log.Warn("Beacon syncing with hash as target", "number", header.Number, "hash", header.Hash())
log.Warn("This is unhealthy for a live node!")
log.Warn("This is incompatible with the consensus layer!")
log.Warn("----------------------------------")
return d.BeaconSync(mode, header, header)
return d.BeaconSync(header, header)
}
// GetHeader tries to retrieve the header with a given hash from a random peer.

View file

@ -34,10 +34,8 @@ import (
// directed by the skeleton sync's head/tail events.
type beaconBackfiller struct {
downloader *Downloader // Downloader to direct via this callback implementation
syncMode SyncMode // Sync mode to use for backfilling the skeleton chains
success func() // Callback to run on successful sync cycle completion
filling bool // Flag whether the downloader is backfilling or not
filled *types.Header // Last header filled by the last terminated sync loop
started chan struct{} // Notification channel whether the downloader inited
lock sync.Mutex // Mutex protecting the sync lock
}
@ -57,12 +55,15 @@ func (b *beaconBackfiller) suspend() *types.Header {
// If no filling is running, don't waste cycles
b.lock.Lock()
filling := b.filling
filled := b.filled
started := b.started
b.lock.Unlock()
if !filling {
return filled // Return the filled header on the previous sync completion
// Sync cycle was inactive, retrieve and return the latest snap block
// as the filled header.
log.Debug("Backfiller was inactive")
return b.downloader.blockchain.CurrentSnapBlock()
}
// A previous filling should be running, though it may happen that it hasn't
// yet started (being done on a new goroutine). Many concurrent beacon head
@ -74,9 +75,9 @@ func (b *beaconBackfiller) suspend() *types.Header {
// Now that we're sure the downloader successfully started up, we can cancel
// it safely without running the risk of data races.
b.downloader.Cancel()
log.Debug("Backfiller has been suspended")
// Sync cycle was just terminated, retrieve and return the last filled header.
// Can't use `filled` as that contains a stale value from before cancellation.
return b.downloader.blockchain.CurrentSnapBlock()
}
@ -87,12 +88,11 @@ func (b *beaconBackfiller) resume() {
// If a previous filling cycle is still running, just ignore this start
// request. // TODO(karalabe): We should make this channel driven
b.lock.Unlock()
log.Debug("Backfiller is running")
return
}
b.filling = true
b.filled = nil
b.started = make(chan struct{})
mode := b.syncMode
b.lock.Unlock()
// Start the backfilling on its own thread since the downloader does not have
@ -102,42 +102,22 @@ func (b *beaconBackfiller) resume() {
defer func() {
b.lock.Lock()
b.filling = false
b.filled = b.downloader.blockchain.CurrentSnapBlock()
b.lock.Unlock()
}()
// If the downloader fails, report an error as in beacon chain mode there
// should be no errors as long as the chain we're syncing to is valid.
if err := b.downloader.synchronise(mode, b.started); err != nil {
if err := b.downloader.synchronise(b.started); err != nil {
log.Error("Beacon backfilling failed", "err", err)
return
}
// Synchronization succeeded. Since this happens async, notify the outer
// context to disable snap syncing and enable transaction propagation.
// context to enable transaction propagation.
if b.success != nil {
b.success()
}
log.Debug("Backfilling completed")
}()
}
// setMode updates the sync mode from the current one to the requested one. If
// there's an active sync in progress, it will be cancelled and restarted.
func (b *beaconBackfiller) setMode(mode SyncMode) {
// Update the old sync mode and track if it was changed
b.lock.Lock()
oldMode := b.syncMode
updated := oldMode != mode
filling := b.filling
b.syncMode = mode
b.lock.Unlock()
// If the sync mode was changed mid-sync, restart. This should never ever
// really happen, we just handle it to detect programming errors.
if !updated || !filling {
return
}
log.Error("Downloader sync mode changed mid-run", "old", oldMode.String(), "new", mode.String())
b.suspend()
b.resume()
log.Debug("Backfilling started")
}
// SetBadBlockCallback sets the callback to run when a bad block is hit by the
@ -153,8 +133,8 @@ func (d *Downloader) SetBadBlockCallback(onBadBlock badBlockFn) {
//
// Internally backfilling and state sync is done the same way, but the header
// retrieval and scheduling is replaced.
func (d *Downloader) BeaconSync(mode SyncMode, head *types.Header, final *types.Header) error {
return d.beaconSync(mode, head, final, true)
func (d *Downloader) BeaconSync(head *types.Header, final *types.Header) error {
return d.beaconSync(head, final, true)
}
// BeaconExtend is an optimistic version of BeaconSync, where an attempt is made
@ -163,8 +143,8 @@ func (d *Downloader) BeaconSync(mode SyncMode, head *types.Header, final *types.
//
// This is useful if a beacon client is feeding us large chunks of payloads to run,
// but is not setting the head after each.
func (d *Downloader) BeaconExtend(mode SyncMode, head *types.Header) error {
return d.beaconSync(mode, head, nil, false)
func (d *Downloader) BeaconExtend(head *types.Header) error {
return d.beaconSync(head, nil, false)
}
// beaconSync is the post-merge version of the chain synchronization, where the
@ -173,20 +153,9 @@ func (d *Downloader) BeaconExtend(mode SyncMode, head *types.Header) error {
//
// Internally backfilling and state sync is done the same way, but the header
// retrieval and scheduling is replaced.
func (d *Downloader) beaconSync(mode SyncMode, head *types.Header, final *types.Header, force bool) error {
// When the downloader starts a sync cycle, it needs to be aware of the sync
// mode to use (full, snap). To keep the skeleton chain oblivious, inject the
// mode into the backfiller directly.
//
// Super crazy dangerous type cast. Should be fine (TM), we're only using a
// different backfiller implementation for skeleton tests.
d.skeleton.filler.(*beaconBackfiller).setMode(mode)
func (d *Downloader) beaconSync(head *types.Header, final *types.Header, force bool) error {
// Signal the skeleton sync to switch to a new head, however it wants
if err := d.skeleton.Sync(head, final, force); err != nil {
return err
}
return nil
return d.skeleton.Sync(head, final, force)
}
// findBeaconAncestor tries to locate the common ancestor link of the local chain
@ -217,6 +186,8 @@ func (d *Downloader) findBeaconAncestor() (uint64, error) {
log.Error("Failed to retrieve beacon bounds", "err", err)
return 0, err
}
log.Debug("Searching beacon ancestor", "local", number, "beaconhead", beaconHead.Number, "beacontail", beaconTail.Number)
var linked bool
switch d.getMode() {
case ethconfig.FullSync:
@ -270,6 +241,7 @@ func (d *Downloader) findBeaconAncestor() (uint64, error) {
}
start = check
}
log.Debug("Found beacon ancestor", "number", start)
return start, nil
}

View file

@ -97,8 +97,9 @@ type headerTask struct {
}
type Downloader struct {
mode atomic.Uint32 // Synchronisation mode defining the strategy used (per sync cycle), use d.getMode() to get the SyncMode
mux *event.TypeMux // Event multiplexer to announce sync operation events
mode atomic.Uint32 // Synchronisation mode defining the strategy used (per sync cycle), use d.getMode() to get the SyncMode
moder *syncModer // Sync mode management, deliver the appropriate sync mode choice for each cycle
mux *event.TypeMux // Event multiplexer to announce sync operation events
queue *queue // Scheduler for selecting the hashes to download
peers *peerSet // Set of active peers from which download can proceed
@ -165,6 +166,9 @@ type BlockChain interface {
// HasHeader verifies a header's presence in the local chain.
HasHeader(common.Hash, uint64) bool
// HasState checks if state trie is fully present in the database or not.
HasState(root common.Hash) bool
// GetHeaderByHash retrieves a header from the local chain.
GetHeaderByHash(common.Hash) *types.Header
@ -189,8 +193,12 @@ type BlockChain interface {
// CurrentSnapBlock retrieves the head snap block from the local chain.
CurrentSnapBlock() *types.Header
// SnapSyncCommitHead directly commits the head block to a certain entity.
SnapSyncCommitHead(common.Hash) error
// SnapSyncStart explicitly notifies the chain that snap sync is scheduled and
// marks chain mutations as disallowed.
SnapSyncStart() error
// SnapSyncComplete directly commits the head block to a certain entity.
SnapSyncComplete(common.Hash) error
// InsertHeadersBeforeCutoff inserts a batch of headers before the configured
// chain cutoff into the ancient store.
@ -221,10 +229,11 @@ type BlockChain interface {
}
// New creates a new downloader to fetch hashes and blocks from remote peers.
func New(stateDb ethdb.Database, mux *event.TypeMux, chain BlockChain, dropPeer peerDropFn, success func()) *Downloader {
func New(stateDb ethdb.Database, mode ethconfig.SyncMode, mux *event.TypeMux, chain BlockChain, dropPeer peerDropFn, success func()) *Downloader {
cutoffNumber, cutoffHash := chain.HistoryPruningCutoff()
dl := &Downloader{
stateDB: stateDb,
moder: newSyncModer(mode, chain, stateDb),
mux: mux,
queue: newQueue(blockCacheMaxItems, blockCacheInitialItems),
peers: newPeerSet(),
@ -239,7 +248,7 @@ func New(stateDb ethdb.Database, mux *event.TypeMux, chain BlockChain, dropPeer
syncStartBlock: chain.CurrentSnapBlock().Number.Uint64(),
}
// Create the post-merge skeleton syncer and start the process
dl.skeleton = newSkeleton(stateDb, dl.peers, dropPeer, newBeaconBackfiller(dl, success))
dl.skeleton = newSkeleton(stateDb, dl.peers, dropPeer, newBeaconBackfiller(dl, success), chain)
go dl.stateFetcher()
return dl
@ -331,7 +340,7 @@ func (d *Downloader) UnregisterPeer(id string) error {
// synchronise will select the peer and use it for synchronising. If an empty string is given
// it will use the best peer possible and synchronize if its TD is higher than our own. If any of the
// checks fail an error will be returned. This method is synchronous
func (d *Downloader) synchronise(mode SyncMode, beaconPing chan struct{}) error {
func (d *Downloader) synchronise(beaconPing chan struct{}) (err error) {
// The beacon header syncer is async. It will start this synchronization and
// will continue doing other tasks. However, if synchronization needs to be
// cancelled, the syncer needs to know if we reached the startup point (and
@ -356,21 +365,21 @@ func (d *Downloader) synchronise(mode SyncMode, beaconPing chan struct{}) error
if d.notified.CompareAndSwap(false, true) {
log.Info("Block synchronisation started")
}
if mode == ethconfig.SnapSync {
// Snap sync will directly modify the persistent state, making the entire
// trie database unusable until the state is fully synced. To prevent any
// subsequent state reads, explicitly disable the trie database and state
// syncer is responsible to address and correct any state missing.
if d.blockchain.TrieDB().Scheme() == rawdb.PathScheme {
if err := d.blockchain.TrieDB().Disable(); err != nil {
return err
}
// Obtain the synchronized used in this cycle
mode := d.moder.get(true)
defer func() {
if err == nil && mode == ethconfig.SnapSync {
d.moder.disableSnap()
log.Info("Disabled snap-sync after the initial sync cycle")
}
// Snap sync uses the snapshot namespace to store potentially flaky data until
// sync completely heals and finishes. Pause snapshot maintenance in the mean-
// time to prevent access.
if snapshots := d.blockchain.Snapshots(); snapshots != nil { // Only nil in tests
snapshots.Disable()
}()
// Disable chain mutations when snap sync is selected, ensuring the
// downloader is the sole mutator.
if mode == ethconfig.SnapSync {
if err := d.blockchain.SnapSyncStart(); err != nil {
return err
}
}
// Reset the queue, peer set and wake channels to clean any internal leftover state
@ -399,6 +408,7 @@ func (d *Downloader) synchronise(mode SyncMode, beaconPing chan struct{}) error
// Atomically set the requested sync mode
d.mode.Store(uint32(mode))
defer d.mode.Store(0)
if beaconPing != nil {
close(beaconPing)
@ -406,10 +416,17 @@ func (d *Downloader) synchronise(mode SyncMode, beaconPing chan struct{}) error
return d.syncToHead()
}
// getMode returns the sync mode used within current cycle.
func (d *Downloader) getMode() SyncMode {
return SyncMode(d.mode.Load())
}
// ConfigSyncMode returns the sync mode configured for the node.
// The actual running sync mode can differ from this.
func (d *Downloader) ConfigSyncMode() SyncMode {
return d.moder.get(false)
}
// syncToHead starts a block synchronization based on the hash chain from
// the specified head hash.
func (d *Downloader) syncToHead() (err error) {
@ -1066,7 +1083,7 @@ func (d *Downloader) commitPivotBlock(result *fetchResult) error {
if _, err := d.blockchain.InsertReceiptChain([]*types.Block{block}, []rlp.RawValue{result.Receipts}, d.ancientLimit); err != nil {
return err
}
if err := d.blockchain.SnapSyncCommitHead(block.Hash()); err != nil {
if err := d.blockchain.SnapSyncComplete(block.Hash()); err != nil {
return err
}
d.committed.Store(true)

View file

@ -30,6 +30,7 @@ import (
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/eth/ethconfig"
"github.com/ethereum/go-ethereum/eth/protocols/eth"
"github.com/ethereum/go-ethereum/eth/protocols/snap"
"github.com/ethereum/go-ethereum/event"
@ -49,12 +50,12 @@ type downloadTester struct {
}
// newTester creates a new downloader test mocker.
func newTester(t *testing.T) *downloadTester {
return newTesterWithNotification(t, nil)
func newTester(t *testing.T, mode ethconfig.SyncMode) *downloadTester {
return newTesterWithNotification(t, mode, nil)
}
// newTesterWithNotification creates a new downloader test mocker.
func newTesterWithNotification(t *testing.T, success func()) *downloadTester {
func newTesterWithNotification(t *testing.T, mode ethconfig.SyncMode, success func()) *downloadTester {
db, err := rawdb.Open(rawdb.NewMemoryDatabase(), rawdb.OpenOptions{})
if err != nil {
panic(err)
@ -75,7 +76,7 @@ func newTesterWithNotification(t *testing.T, success func()) *downloadTester {
chain: chain,
peers: make(map[string]*downloadTesterPeer),
}
tester.downloader = New(db, new(event.TypeMux), tester.chain, tester.dropPeer, success)
tester.downloader = New(db, mode, new(event.TypeMux), tester.chain, tester.dropPeer, success)
return tester
}
@ -393,7 +394,7 @@ func TestCanonicalSynchronisation68Snap(t *testing.T) { testCanonSync(t, eth.ETH
func testCanonSync(t *testing.T, protocol uint, mode SyncMode) {
success := make(chan struct{})
tester := newTesterWithNotification(t, func() {
tester := newTesterWithNotification(t, mode, func() {
close(success)
})
defer tester.terminate()
@ -403,7 +404,7 @@ func testCanonSync(t *testing.T, protocol uint, mode SyncMode) {
tester.newPeer("peer", protocol, chain.blocks[1:])
// Synchronise with the peer and make sure all relevant data was retrieved
if err := tester.downloader.BeaconSync(mode, chain.blocks[len(chain.blocks)-1].Header(), nil); err != nil {
if err := tester.downloader.BeaconSync(chain.blocks[len(chain.blocks)-1].Header(), nil); err != nil {
t.Fatalf("failed to beacon-sync chain: %v", err)
}
select {
@ -420,7 +421,7 @@ func TestThrottling68Full(t *testing.T) { testThrottling(t, eth.ETH68, FullSync)
func TestThrottling68Snap(t *testing.T) { testThrottling(t, eth.ETH68, SnapSync) }
func testThrottling(t *testing.T, protocol uint, mode SyncMode) {
tester := newTester(t)
tester := newTester(t, mode)
defer tester.terminate()
// Create a long block chain to download and the tester
@ -437,7 +438,7 @@ func testThrottling(t *testing.T, protocol uint, mode SyncMode) {
// Start a synchronisation concurrently
errc := make(chan error, 1)
go func() {
errc <- tester.downloader.BeaconSync(mode, testChainBase.blocks[len(testChainBase.blocks)-1].Header(), nil)
errc <- tester.downloader.BeaconSync(testChainBase.blocks[len(testChainBase.blocks)-1].Header(), nil)
}()
// Iteratively take some blocks, always checking the retrieval count
for {
@ -502,7 +503,7 @@ func testCancel(t *testing.T, protocol uint, mode SyncMode) {
success := func() {
close(complete)
}
tester := newTesterWithNotification(t, success)
tester := newTesterWithNotification(t, mode, success)
defer tester.terminate()
chain := testChainBase.shorten(MaxHeaderFetch)
@ -514,7 +515,7 @@ func testCancel(t *testing.T, protocol uint, mode SyncMode) {
t.Errorf("download queue not idle")
}
// Synchronise with the peer, but cancel afterwards
if err := tester.downloader.BeaconSync(mode, chain.blocks[len(chain.blocks)-1].Header(), nil); err != nil {
if err := tester.downloader.BeaconSync(chain.blocks[len(chain.blocks)-1].Header(), nil); err != nil {
t.Fatalf("failed to synchronise blocks: %v", err)
}
<-complete
@ -534,7 +535,7 @@ func testMultiProtoSync(t *testing.T, protocol uint, mode SyncMode) {
success := func() {
close(complete)
}
tester := newTesterWithNotification(t, success)
tester := newTesterWithNotification(t, mode, success)
defer tester.terminate()
// Create a small enough block chain to download
@ -543,7 +544,7 @@ func testMultiProtoSync(t *testing.T, protocol uint, mode SyncMode) {
// Create peers of every type
tester.newPeer("peer 68", eth.ETH68, chain.blocks[1:])
if err := tester.downloader.BeaconSync(mode, chain.blocks[len(chain.blocks)-1].Header(), nil); err != nil {
if err := tester.downloader.BeaconSync(chain.blocks[len(chain.blocks)-1].Header(), nil); err != nil {
t.Fatalf("failed to start beacon sync: %v", err)
}
select {
@ -570,7 +571,7 @@ func TestEmptyShortCircuit68Snap(t *testing.T) { testEmptyShortCircuit(t, eth.ET
func testEmptyShortCircuit(t *testing.T, protocol uint, mode SyncMode) {
success := make(chan struct{})
tester := newTesterWithNotification(t, func() {
tester := newTesterWithNotification(t, mode, func() {
close(success)
})
defer tester.terminate()
@ -588,7 +589,7 @@ func testEmptyShortCircuit(t *testing.T, protocol uint, mode SyncMode) {
receiptsHave.Add(int32(len(headers)))
}
if err := tester.downloader.BeaconSync(mode, chain.blocks[len(chain.blocks)-1].Header(), nil); err != nil {
if err := tester.downloader.BeaconSync(chain.blocks[len(chain.blocks)-1].Header(), nil); err != nil {
t.Fatalf("failed to synchronise blocks: %v", err)
}
select {
@ -650,7 +651,7 @@ func testBeaconSync(t *testing.T, protocol uint, mode SyncMode) {
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
success := make(chan struct{})
tester := newTesterWithNotification(t, func() {
tester := newTesterWithNotification(t, mode, func() {
close(success)
})
defer tester.terminate()
@ -662,7 +663,7 @@ func testBeaconSync(t *testing.T, protocol uint, mode SyncMode) {
if c.local > 0 {
tester.chain.InsertChain(chain.blocks[1 : c.local+1])
}
if err := tester.downloader.BeaconSync(mode, chain.blocks[len(chain.blocks)-1].Header(), nil); err != nil {
if err := tester.downloader.BeaconSync(chain.blocks[len(chain.blocks)-1].Header(), nil); err != nil {
t.Fatalf("Failed to beacon sync chain %v %v", c.name, err)
}
select {
@ -685,7 +686,7 @@ func TestSyncProgress68Snap(t *testing.T) { testSyncProgress(t, eth.ETH68, SnapS
func testSyncProgress(t *testing.T, protocol uint, mode SyncMode) {
success := make(chan struct{})
tester := newTesterWithNotification(t, func() {
tester := newTesterWithNotification(t, mode, func() {
success <- struct{}{}
})
defer tester.terminate()
@ -700,7 +701,7 @@ func testSyncProgress(t *testing.T, protocol uint, mode SyncMode) {
faultyPeer.withholdBodies[header.Hash()] = struct{}{}
}
if err := tester.downloader.BeaconSync(mode, chain.blocks[len(chain.blocks)/2-1].Header(), nil); err != nil {
if err := tester.downloader.BeaconSync(chain.blocks[len(chain.blocks)/2-1].Header(), nil); err != nil {
t.Fatalf("failed to beacon-sync chain: %v", err)
}
select {
@ -716,7 +717,7 @@ func testSyncProgress(t *testing.T, protocol uint, mode SyncMode) {
// Synchronise all the blocks and check continuation progress
tester.newPeer("peer-full", protocol, chain.blocks[1:])
if err := tester.downloader.BeaconSync(mode, chain.blocks[len(chain.blocks)-1].Header(), nil); err != nil {
if err := tester.downloader.BeaconSync(chain.blocks[len(chain.blocks)-1].Header(), nil); err != nil {
t.Fatalf("failed to beacon-sync chain: %v", err)
}
startingBlock := uint64(len(chain.blocks)/2 - 1)

View file

@ -418,7 +418,7 @@ func (q *queue) reserveHeaders(p *peerConnection, count int, taskPool map[common
skip := make([]*types.Header, 0)
progress := false
throttled := false
for proc := 0; len(send) < count && !taskQueue.Empty(); proc++ {
for len(send) < count && !taskQueue.Empty() {
// the task queue will pop items in order, so the highest prio block
// is also the lowest block number.
header, _ := taskQueue.Peek()
@ -433,7 +433,6 @@ func (q *queue) reserveHeaders(p *peerConnection, count int, taskPool map[common
taskQueue.PopItem()
progress = true
delete(taskPool, header.Hash())
proc = proc - 1
log.Error("Fetch reservation already delivered", "number", header.Number.Uint64())
continue
}
@ -455,7 +454,6 @@ func (q *queue) reserveHeaders(p *peerConnection, count int, taskPool map[common
// If it's a noop, we can skip this task
delete(taskPool, header.Hash())
taskQueue.PopItem()
proc = proc - 1
progress = true
continue
}

View file

@ -64,6 +64,12 @@ var errSyncMerged = errors.New("sync merged")
// should abort and restart with the new state.
var errSyncReorged = errors.New("sync reorged")
// errSyncTrimmed is an internal helper error to signal that the local chain
// has been trimmed (e.g, via debug_setHead explicitly) and the skeleton chain
// is no longer linked with the local chain. In this case, the skeleton sync
// should be re-scheduled again.
var errSyncTrimmed = errors.New("sync trimmed")
// errTerminated is returned if the sync mechanism was terminated for this run of
// the process. This is usually the case when Geth is shutting down and some events
// might still be propagating.
@ -201,6 +207,7 @@ type backfiller interface {
type skeleton struct {
db ethdb.Database // Database backing the skeleton
filler backfiller // Chain syncer suspended/resumed by head events
chain chainReader // Underlying block chain
peers *peerSet // Set of peers we can sync from
idles map[string]*peerConnection // Set of idle peers in the current sync cycle
@ -225,12 +232,19 @@ type skeleton struct {
syncStarting func() // callback triggered after a sync cycle is inited but before started
}
// chainReader wraps the method to retrieve the head of the local chain.
type chainReader interface {
// CurrentSnapBlock retrieves the head snap block from the local chain.
CurrentSnapBlock() *types.Header
}
// newSkeleton creates a new sync skeleton that tracks a potentially dangling
// header chain until it's linked into an existing set of blocks.
func newSkeleton(db ethdb.Database, peers *peerSet, drop peerDropFn, filler backfiller) *skeleton {
func newSkeleton(db ethdb.Database, peers *peerSet, drop peerDropFn, filler backfiller, chain chainReader) *skeleton {
sk := &skeleton{
db: db,
filler: filler,
chain: chain,
peers: peers,
drop: drop,
requests: make(map[uint64]*headerRequest),
@ -296,6 +310,11 @@ func (s *skeleton) startup() {
// head to force a cleanup.
head = newhead
case err == errSyncTrimmed:
// The skeleton chain is not linked with the local chain anymore,
// restart the sync.
head = nil
case err == errTerminated:
// Sync was requested to be terminated from within, stop and
// return (no need to pass a message, was already done internally)
@ -343,6 +362,29 @@ func (s *skeleton) Sync(head *types.Header, final *types.Header, force bool) err
}
}
// linked returns the flag indicating whether the skeleton has been linked with
// the local chain.
func (s *skeleton) linked(number uint64, hash common.Hash) bool {
linked := rawdb.HasHeader(s.db, hash, number) &&
rawdb.HasBody(s.db, hash, number) &&
rawdb.HasReceipts(s.db, hash, number)
// Ensure the skeleton chain links to the local chain below the chain head.
// This accounts for edge cases where leftover chain segments above the head
// may still link to the skeleton chain. In such cases, synchronization is
// likely to fail due to potentially missing segments in the middle.
//
// You can try to produce the edge case by these steps:
// - sync the chain
// - debug.setHead(`0x1`)
// - kill the geth process (the chain segment will be left with chain head rewound)
// - restart
if s.chain.CurrentSnapBlock() != nil {
linked = linked && s.chain.CurrentSnapBlock().Number.Uint64() >= number
}
return linked
}
// sync is the internal version of Sync that executes a single sync cycle, either
// until some termination condition is reached, or until the current cycle merges
// with a previously aborted run.
@ -367,10 +409,7 @@ func (s *skeleton) sync(head *types.Header) (*types.Header, error) {
// If the sync is already done, resume the backfiller. When the loop stops,
// terminate the backfiller too.
linked := len(s.progress.Subchains) == 1 &&
rawdb.HasHeader(s.db, s.progress.Subchains[0].Next, s.scratchHead) &&
rawdb.HasBody(s.db, s.progress.Subchains[0].Next, s.scratchHead) &&
rawdb.HasReceipts(s.db, s.progress.Subchains[0].Next, s.scratchHead)
linked := len(s.progress.Subchains) == 1 && s.linked(s.scratchHead, s.progress.Subchains[0].Next)
if linked {
s.filler.resume()
}
@ -486,7 +525,17 @@ func (s *skeleton) sync(head *types.Header) (*types.Header, error) {
// is still running, it will pick it up. If it already terminated,
// a new cycle needs to be spun up.
if linked {
s.filler.resume()
if len(s.progress.Subchains) == 1 && s.linked(s.scratchHead, s.progress.Subchains[0].Next) {
// The skeleton chain has been extended and is still linked with the local
// chain, try to re-schedule the backfiller if it's already terminated.
s.filler.resume()
} else {
// The skeleton chain is no longer linked to the local chain for some reason
// (e.g. debug_setHead was used to trim the local chain). Re-schedule the
// skeleton sync to fill the chain gap.
log.Warn("Local chain has been trimmed", "tailnumber", s.scratchHead, "tailhash", s.progress.Subchains[0].Next)
return nil, errSyncTrimmed
}
}
case req := <-requestFails:
@ -649,9 +698,19 @@ func (s *skeleton) processNewHead(head *types.Header, final *types.Header) error
// Not a noop / double head announce, abort with a reorg
return fmt.Errorf("%w, tail: %d, head: %d, newHead: %d", errChainReorged, lastchain.Tail, lastchain.Head, number)
}
// Terminate the sync if the chain head is gapped
if lastchain.Head+1 < number {
return fmt.Errorf("%w, head: %d, newHead: %d", errChainGapped, lastchain.Head, number)
}
// Ignore the duplicated beacon header announcement
if lastchain.Head == number {
local := rawdb.ReadSkeletonHeader(s.db, number)
if local != nil && local.Hash() == head.Hash() {
log.Debug("Ignored the identical beacon header", "number", number, "hash", local.Hash())
return nil
}
}
// Terminate the sync if the chain head is forked
if parent := rawdb.ReadSkeletonHeader(s.db, number-1); parent.Hash() != head.ParentHash {
return fmt.Errorf("%w, ancestor: %d, hash: %s, want: %s", errChainForked, number-1, parent.Hash(), head.ParentHash)
}
@ -669,6 +728,7 @@ func (s *skeleton) processNewHead(head *types.Header, final *types.Header) error
if err := batch.Write(); err != nil {
log.Crit("Failed to write skeleton sync status", "err", err)
}
log.Debug("Extended beacon header chain", "number", head.Number, "hash", head.Hash())
return nil
}
@ -909,6 +969,45 @@ func (s *skeleton) revertRequest(req *headerRequest) {
s.scratchOwners[(s.scratchHead-req.head)/requestHeaders] = ""
}
// mergeSubchains is invoked once certain beacon headers have been persisted locally
// and the subchains should be merged in case there are some overlaps between. An
// indicator will be returned if the last subchain is merged with previous subchain.
func (s *skeleton) mergeSubchains() bool {
// If the subchain extended into the next subchain, we need to handle
// the overlap. Since there could be many overlaps, do this in a loop.
var merged bool
for len(s.progress.Subchains) > 1 && s.progress.Subchains[1].Head >= s.progress.Subchains[0].Tail {
// Extract some stats from the second subchain
head := s.progress.Subchains[1].Head
tail := s.progress.Subchains[1].Tail
next := s.progress.Subchains[1].Next
// Since we just overwrote part of the next subchain, we need to trim
// its head independent of matching or mismatching content
if s.progress.Subchains[1].Tail >= s.progress.Subchains[0].Tail {
// Fully overwritten, get rid of the subchain as a whole
log.Debug("Previous subchain fully overwritten", "head", head, "tail", tail, "next", next)
s.progress.Subchains = append(s.progress.Subchains[:1], s.progress.Subchains[2:]...)
continue
} else {
// Partially overwritten, trim the head to the overwritten size
log.Debug("Previous subchain partially overwritten", "head", head, "tail", tail, "next", next)
s.progress.Subchains[1].Head = s.progress.Subchains[0].Tail - 1
}
// If the old subchain is an extension of the new one, merge the two
// and let the skeleton syncer restart (to clean internal state)
if rawdb.ReadSkeletonHeader(s.db, s.progress.Subchains[1].Head).Hash() == s.progress.Subchains[0].Next {
log.Debug("Previous subchain merged", "head", head, "tail", tail, "next", next)
s.progress.Subchains[0].Tail = s.progress.Subchains[1].Tail
s.progress.Subchains[0].Next = s.progress.Subchains[1].Next
s.progress.Subchains = append(s.progress.Subchains[:1], s.progress.Subchains[2:]...)
merged = true
}
}
return merged
}
func (s *skeleton) processResponse(res *headerResponse) (linked bool, merged bool) {
res.peer.log.Trace("Processing header response", "head", res.headers[0].Number, "hash", res.headers[0].Hash(), "count", len(res.headers))
@ -982,10 +1081,9 @@ func (s *skeleton) processResponse(res *headerResponse) (linked bool, merged boo
// processing is done, so it's just one more "needless" check.
//
// The weird cascading checks are done to minimize the database reads.
linked = rawdb.HasHeader(s.db, header.ParentHash, header.Number.Uint64()-1) &&
rawdb.HasBody(s.db, header.ParentHash, header.Number.Uint64()-1) &&
rawdb.HasReceipts(s.db, header.ParentHash, header.Number.Uint64()-1)
linked = s.linked(header.Number.Uint64()-1, header.ParentHash)
if linked {
log.Debug("Primary subchain linked", "number", header.Number.Uint64()-1, "hash", header.ParentHash)
break
}
}
@ -999,6 +1097,9 @@ func (s *skeleton) processResponse(res *headerResponse) (linked bool, merged boo
// If the beacon chain was linked to the local chain, completely swap out
// all internal progress and abort header synchronization.
if linked {
// Merge all overlapped subchains beforehand
s.mergeSubchains()
// Linking into the local chain should also mean that there are no
// leftover subchains, but in the case of importing the blocks via
// the engine API, we will not push the subchains forward. This will
@ -1056,41 +1157,10 @@ func (s *skeleton) processResponse(res *headerResponse) (linked bool, merged boo
s.scratchHead -= uint64(consumed)
// If the subchain extended into the next subchain, we need to handle
// the overlap. Since there could be many overlaps (come on), do this
// in a loop.
for len(s.progress.Subchains) > 1 && s.progress.Subchains[1].Head >= s.progress.Subchains[0].Tail {
// Extract some stats from the second subchain
head := s.progress.Subchains[1].Head
tail := s.progress.Subchains[1].Tail
next := s.progress.Subchains[1].Next
// Since we just overwrote part of the next subchain, we need to trim
// its head independent of matching or mismatching content
if s.progress.Subchains[1].Tail >= s.progress.Subchains[0].Tail {
// Fully overwritten, get rid of the subchain as a whole
log.Debug("Previous subchain fully overwritten", "head", head, "tail", tail, "next", next)
s.progress.Subchains = append(s.progress.Subchains[:1], s.progress.Subchains[2:]...)
continue
} else {
// Partially overwritten, trim the head to the overwritten size
log.Debug("Previous subchain partially overwritten", "head", head, "tail", tail, "next", next)
s.progress.Subchains[1].Head = s.progress.Subchains[0].Tail - 1
}
// If the old subchain is an extension of the new one, merge the two
// and let the skeleton syncer restart (to clean internal state)
if rawdb.ReadSkeletonHeader(s.db, s.progress.Subchains[1].Head).Hash() == s.progress.Subchains[0].Next {
log.Debug("Previous subchain merged", "head", head, "tail", tail, "next", next)
s.progress.Subchains[0].Tail = s.progress.Subchains[1].Tail
s.progress.Subchains[0].Next = s.progress.Subchains[1].Next
s.progress.Subchains = append(s.progress.Subchains[:1], s.progress.Subchains[2:]...)
merged = true
}
}
// If subchains were merged, all further available headers in the scratch
// space are invalid since we skipped ahead. Stop processing the scratch
// space to avoid dropping peers thinking they delivered invalid data.
merged = s.mergeSubchains()
if merged {
break
}
@ -1121,15 +1191,17 @@ func (s *skeleton) processResponse(res *headerResponse) (linked bool, merged boo
// due to the downloader backfilling past the tracked tail.
func (s *skeleton) cleanStales(filled *types.Header) error {
number := filled.Number.Uint64()
log.Trace("Cleaning stale beacon headers", "filled", number, "hash", filled.Hash())
log.Debug("Cleaning stale beacon headers", "filled", number, "hash", filled.Hash())
// If the filled header is below the linked subchain, something's corrupted
// internally. Report and error and refuse to do anything.
// If the filled header is below the subchain, it means the skeleton is not
// linked with local chain yet, don't bother to do cleanup.
if number+1 < s.progress.Subchains[0].Tail {
return fmt.Errorf("filled header below beacon header tail: %d < %d", number, s.progress.Subchains[0].Tail)
log.Debug("filled header below beacon header tail", "filled", number, "tail", s.progress.Subchains[0].Tail)
return nil
}
// If nothing in subchain is filled, don't bother to do cleanup.
if number+1 == s.progress.Subchains[0].Tail {
log.Debug("Skeleton chain not yet consumed", "filled", number, "hash", filled.Hash(), "tail", s.progress.Subchains[0].Tail)
return nil
}
// If the latest fill was on a different subchain, it means the backfiller
@ -1206,6 +1278,7 @@ func (s *skeleton) cleanStales(filled *types.Header) error {
if err := batch.Write(); err != nil {
log.Crit("Failed to write beacon trim data", "err", err)
}
log.Debug("Cleaned stale beacon headers", "start", start, "end", end)
return nil
}

View file

@ -20,6 +20,7 @@ import (
"encoding/json"
"errors"
"fmt"
"math"
"math/big"
"sync/atomic"
"testing"
@ -71,6 +72,12 @@ func (hf *hookedBackfiller) resume() {
}
}
type fakeChainReader struct{}
func (fc *fakeChainReader) CurrentSnapBlock() *types.Header {
return &types.Header{Number: big.NewInt(math.MaxInt64)}
}
// skeletonTestPeer is a mock peer that can only serve header requests from a
// pre-perated header chain (which may be arbitrarily wrong for testing).
//
@ -369,7 +376,7 @@ func TestSkeletonSyncInit(t *testing.T) {
// Create a skeleton sync and run a cycle
wait := make(chan struct{})
skeleton := newSkeleton(db, newPeerSet(), nil, newHookedBackfiller())
skeleton := newSkeleton(db, newPeerSet(), nil, newHookedBackfiller(), &fakeChainReader{})
skeleton.syncStarting = func() { close(wait) }
skeleton.Sync(tt.head, nil, true)
@ -472,7 +479,7 @@ func TestSkeletonSyncExtend(t *testing.T) {
// Create a skeleton sync and run a cycle
wait := make(chan struct{})
skeleton := newSkeleton(db, newPeerSet(), nil, newHookedBackfiller())
skeleton := newSkeleton(db, newPeerSet(), nil, newHookedBackfiller(), &fakeChainReader{})
skeleton.syncStarting = func() { close(wait) }
skeleton.Sync(tt.head, nil, true)
@ -885,7 +892,7 @@ func TestSkeletonSyncRetrievals(t *testing.T) {
}
}
// Create a skeleton sync and run a cycle
skeleton := newSkeleton(db, peerset, drop, filler)
skeleton := newSkeleton(db, peerset, drop, filler, &fakeChainReader{})
skeleton.Sync(tt.head, nil, true)
// Wait a bit (bleah) for the initial sync loop to go to idle. This might

115
eth/downloader/syncmode.go Normal file
View file

@ -0,0 +1,115 @@
// 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 downloader
import (
"sync"
"github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/eth/ethconfig"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/log"
)
// syncModer is responsible for managing the downloader's sync mode. It takes the
// user's preference at startup and then determines the appropriate sync mode
// based on the current chain status.
type syncModer struct {
mode ethconfig.SyncMode
chain BlockChain
disk ethdb.KeyValueReader
lock sync.Mutex
}
func newSyncModer(mode ethconfig.SyncMode, chain BlockChain, disk ethdb.KeyValueReader) *syncModer {
if mode == ethconfig.FullSync {
// The database seems empty as the current block is the genesis. Yet the snap
// block is ahead, so snap sync was enabled for this node at a certain point.
// The scenarios where this can happen is
// * if the user manually (or via a bad block) rolled back a snap sync node
// below the sync point.
// * the last snap sync is not finished while user specifies a full sync this
// time. But we don't have any recent state for full sync.
// In these cases however it's safe to reenable snap sync.
fullBlock, snapBlock := chain.CurrentBlock(), chain.CurrentSnapBlock()
if fullBlock.Number.Uint64() == 0 && snapBlock.Number.Uint64() > 0 {
mode = ethconfig.SnapSync
log.Warn("Switching from full-sync to snap-sync", "reason", "snap-sync incomplete")
} else if !chain.HasState(fullBlock.Root) {
mode = ethconfig.SnapSync
log.Warn("Switching from full-sync to snap-sync", "reason", "head state missing")
} else {
// Grant the full sync mode
log.Info("Enabled full-sync", "head", fullBlock.Number, "hash", fullBlock.Hash())
}
} else {
head := chain.CurrentBlock()
if head.Number.Uint64() > 0 && chain.HasState(head.Root) {
mode = ethconfig.FullSync
log.Info("Switching from snap-sync to full-sync", "reason", "snap-sync complete")
} else {
// If snap sync was requested and our database is empty, grant it
log.Info("Enabled snap-sync", "head", head.Number, "hash", head.Hash())
}
}
return &syncModer{
mode: mode,
chain: chain,
disk: disk,
}
}
// get retrieves the current sync mode, either explicitly set, or derived
// from the chain status.
func (m *syncModer) get(report bool) ethconfig.SyncMode {
m.lock.Lock()
defer m.lock.Unlock()
// If we're in snap sync mode, return that directly
if m.mode == ethconfig.SnapSync {
return ethconfig.SnapSync
}
logger := log.Debug
if report {
logger = log.Info
}
// We are probably in full sync, but we might have rewound to before the
// snap sync pivot, check if we should re-enable snap sync.
head := m.chain.CurrentBlock()
if pivot := rawdb.ReadLastPivotNumber(m.disk); pivot != nil {
if head.Number.Uint64() < *pivot {
logger("Reenabled snap-sync as chain is lagging behind the pivot", "head", head.Number, "pivot", pivot)
return ethconfig.SnapSync
}
}
// We are in a full sync, but the associated head state is missing. To complete
// the head state, forcefully rerun the snap sync. Note it doesn't mean the
// persistent state is corrupted, just mismatch with the head block.
if !m.chain.HasState(head.Root) {
logger("Reenabled snap-sync as chain is stateless")
return ethconfig.SnapSync
}
// Nope, we're really full syncing
return ethconfig.FullSync
}
// disableSnap disables the snap sync mode, usually it's called after a successful snap sync.
func (m *syncModer) disableSnap() {
m.lock.Lock()
m.mode = ethconfig.FullSync
m.lock.Unlock()
}

View file

@ -72,6 +72,7 @@ var Defaults = Config{
RPCTxFeeCap: 1, // 1 ether
TxSyncDefaultTimeout: 20 * time.Second,
TxSyncMaxTimeout: 1 * time.Minute,
SlowBlockThreshold: time.Second * 2,
}
//go:generate go run github.com/fjl/gencodec -type Config -formats toml -out gen_config.go
@ -118,6 +119,10 @@ type Config struct {
// presence of these blocks for every new peer connection.
RequiredBlocks map[uint64]common.Hash `toml:"-"`
// SlowBlockThreshold is the block execution speed threshold (Mgas/s)
// below which detailed statistics are logged.
SlowBlockThreshold time.Duration `toml:",omitempty"`
// Database options
SkipBcVersionCheck bool `toml:"-"`
DatabaseHandles int `toml:"-"`

View file

@ -33,6 +33,7 @@ func (c Config) MarshalTOML() (interface{}, error) {
StateHistory uint64 `toml:",omitempty"`
StateScheme string `toml:",omitempty"`
RequiredBlocks map[uint64]common.Hash `toml:"-"`
SlowBlockThreshold time.Duration `toml:",omitempty"`
SkipBcVersionCheck bool `toml:"-"`
DatabaseHandles int `toml:"-"`
DatabaseCache int
@ -82,6 +83,7 @@ func (c Config) MarshalTOML() (interface{}, error) {
enc.StateHistory = c.StateHistory
enc.StateScheme = c.StateScheme
enc.RequiredBlocks = c.RequiredBlocks
enc.SlowBlockThreshold = c.SlowBlockThreshold
enc.SkipBcVersionCheck = c.SkipBcVersionCheck
enc.DatabaseHandles = c.DatabaseHandles
enc.DatabaseCache = c.DatabaseCache
@ -135,6 +137,7 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error {
StateHistory *uint64 `toml:",omitempty"`
StateScheme *string `toml:",omitempty"`
RequiredBlocks map[uint64]common.Hash `toml:"-"`
SlowBlockThreshold *time.Duration `toml:",omitempty"`
SkipBcVersionCheck *bool `toml:"-"`
DatabaseHandles *int `toml:"-"`
DatabaseCache *int
@ -219,6 +222,9 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error {
if dec.RequiredBlocks != nil {
c.RequiredBlocks = dec.RequiredBlocks
}
if dec.SlowBlockThreshold != nil {
c.SlowBlockThreshold = *dec.SlowBlockThreshold
}
if dec.SkipBcVersionCheck != nil {
c.SkipBcVersionCheck = *dec.SkipBcVersionCheck
}

View file

@ -170,10 +170,10 @@ type TxFetcher struct {
alternates map[common.Hash]map[string]struct{} // In-flight transaction alternate origins if retrieval fails
// Callbacks
hasTx func(common.Hash) bool // Retrieves a tx from the local txpool
addTxs func([]*types.Transaction) []error // Insert a batch of transactions into local txpool
fetchTxs func(string, []common.Hash) error // Retrieves a set of txs from a remote peer
dropPeer func(string) // Drops a peer in case of announcement violation
validateMeta func(common.Hash, byte) error // Validate a tx metadata based on the local txpool
addTxs func([]*types.Transaction) []error // Insert a batch of transactions into local txpool
fetchTxs func(string, []common.Hash) error // Retrieves a set of txs from a remote peer
dropPeer func(string) // Drops a peer in case of announcement violation
step chan struct{} // Notification channel when the fetcher loop iterates
clock mclock.Clock // Monotonic clock or simulated clock for tests
@ -183,36 +183,36 @@ type TxFetcher struct {
// NewTxFetcher creates a transaction fetcher to retrieve transaction
// based on hash announcements.
func NewTxFetcher(hasTx func(common.Hash) bool, addTxs func([]*types.Transaction) []error, fetchTxs func(string, []common.Hash) error, dropPeer func(string)) *TxFetcher {
return NewTxFetcherForTests(hasTx, addTxs, fetchTxs, dropPeer, mclock.System{}, time.Now, nil)
func NewTxFetcher(validateMeta func(common.Hash, byte) error, addTxs func([]*types.Transaction) []error, fetchTxs func(string, []common.Hash) error, dropPeer func(string)) *TxFetcher {
return NewTxFetcherForTests(validateMeta, addTxs, fetchTxs, dropPeer, mclock.System{}, time.Now, nil)
}
// NewTxFetcherForTests is a testing method to mock out the realtime clock with
// a simulated version and the internal randomness with a deterministic one.
func NewTxFetcherForTests(
hasTx func(common.Hash) bool, addTxs func([]*types.Transaction) []error, fetchTxs func(string, []common.Hash) error, dropPeer func(string),
validateMeta func(common.Hash, byte) error, addTxs func([]*types.Transaction) []error, fetchTxs func(string, []common.Hash) error, dropPeer func(string),
clock mclock.Clock, realTime func() time.Time, rand *mrand.Rand) *TxFetcher {
return &TxFetcher{
notify: make(chan *txAnnounce),
cleanup: make(chan *txDelivery),
drop: make(chan *txDrop),
quit: make(chan struct{}),
waitlist: make(map[common.Hash]map[string]struct{}),
waittime: make(map[common.Hash]mclock.AbsTime),
waitslots: make(map[string]map[common.Hash]*txMetadataWithSeq),
announces: make(map[string]map[common.Hash]*txMetadataWithSeq),
announced: make(map[common.Hash]map[string]struct{}),
fetching: make(map[common.Hash]string),
requests: make(map[string]*txRequest),
alternates: make(map[common.Hash]map[string]struct{}),
underpriced: lru.NewCache[common.Hash, time.Time](maxTxUnderpricedSetSize),
hasTx: hasTx,
addTxs: addTxs,
fetchTxs: fetchTxs,
dropPeer: dropPeer,
clock: clock,
realTime: realTime,
rand: rand,
notify: make(chan *txAnnounce),
cleanup: make(chan *txDelivery),
drop: make(chan *txDrop),
quit: make(chan struct{}),
waitlist: make(map[common.Hash]map[string]struct{}),
waittime: make(map[common.Hash]mclock.AbsTime),
waitslots: make(map[string]map[common.Hash]*txMetadataWithSeq),
announces: make(map[string]map[common.Hash]*txMetadataWithSeq),
announced: make(map[common.Hash]map[string]struct{}),
fetching: make(map[common.Hash]string),
requests: make(map[string]*txRequest),
alternates: make(map[common.Hash]map[string]struct{}),
underpriced: lru.NewCache[common.Hash, time.Time](maxTxUnderpricedSetSize),
validateMeta: validateMeta,
addTxs: addTxs,
fetchTxs: fetchTxs,
dropPeer: dropPeer,
clock: clock,
realTime: realTime,
rand: rand,
}
}
@ -235,19 +235,26 @@ func (f *TxFetcher) Notify(peer string, types []byte, sizes []uint32, hashes []c
underpriced int64
)
for i, hash := range hashes {
switch {
case f.hasTx(hash):
err := f.validateMeta(hash, types[i])
if errors.Is(err, txpool.ErrAlreadyKnown) {
duplicate++
case f.isKnownUnderpriced(hash):
underpriced++
default:
unknownHashes = append(unknownHashes, hash)
// Transaction metadata has been available since eth68, and all
// legacy eth protocols (prior to eth68) have been deprecated.
// Therefore, metadata is always expected in the announcement.
unknownMetas = append(unknownMetas, txMetadata{kind: types[i], size: sizes[i]})
continue
}
if err != nil {
continue
}
if f.isKnownUnderpriced(hash) {
underpriced++
continue
}
unknownHashes = append(unknownHashes, hash)
// Transaction metadata has been available since eth68, and all
// legacy eth protocols (prior to eth68) have been deprecated.
// Therefore, metadata is always expected in the announcement.
unknownMetas = append(unknownMetas, txMetadata{kind: types[i], size: sizes[i]})
}
txAnnounceKnownMeter.Mark(duplicate)
txAnnounceUnderpricedMeter.Mark(underpriced)
@ -345,9 +352,9 @@ func (f *TxFetcher) Enqueue(peer string, txs []*types.Transaction, direct bool)
otherRejectMeter.Mark(otherreject)
// If 'other reject' is >25% of the deliveries in any batch, sleep a bit.
if otherreject > addTxsBatchSize/4 {
if otherreject > int64((len(batch)+3)/4) {
log.Debug("Peer delivering stale or invalid transactions", "peer", peer, "rejected", otherreject)
time.Sleep(200 * time.Millisecond)
log.Debug("Peer delivering stale transactions", "peer", peer, "rejected", otherreject)
}
}
select {

View file

@ -93,7 +93,7 @@ func TestTransactionFetcherWaiting(t *testing.T) {
testTransactionFetcherParallel(t, txFetcherTest{
init: func() *TxFetcher {
return NewTxFetcher(
func(common.Hash) bool { return false },
func(common.Hash, byte) error { return nil },
nil,
func(string, []common.Hash) error { return nil },
nil,
@ -295,7 +295,7 @@ func TestTransactionFetcherSkipWaiting(t *testing.T) {
testTransactionFetcherParallel(t, txFetcherTest{
init: func() *TxFetcher {
return NewTxFetcher(
func(common.Hash) bool { return false },
func(common.Hash, byte) error { return nil },
nil,
func(string, []common.Hash) error { return nil },
nil,
@ -385,7 +385,7 @@ func TestTransactionFetcherSingletonRequesting(t *testing.T) {
testTransactionFetcherParallel(t, txFetcherTest{
init: func() *TxFetcher {
return NewTxFetcher(
func(common.Hash) bool { return false },
func(common.Hash, byte) error { return nil },
nil,
func(string, []common.Hash) error { return nil },
nil,
@ -490,7 +490,7 @@ func TestTransactionFetcherFailedRescheduling(t *testing.T) {
testTransactionFetcherParallel(t, txFetcherTest{
init: func() *TxFetcher {
return NewTxFetcher(
func(common.Hash) bool { return false },
func(common.Hash, byte) error { return nil },
nil,
func(origin string, hashes []common.Hash) error {
<-proceed
@ -574,7 +574,7 @@ func TestTransactionFetcherCleanup(t *testing.T) {
testTransactionFetcherParallel(t, txFetcherTest{
init: func() *TxFetcher {
return NewTxFetcher(
func(common.Hash) bool { return false },
func(common.Hash, byte) error { return nil },
func(txs []*types.Transaction) []error {
return make([]error, len(txs))
},
@ -618,7 +618,7 @@ func TestTransactionFetcherCleanupEmpty(t *testing.T) {
testTransactionFetcherParallel(t, txFetcherTest{
init: func() *TxFetcher {
return NewTxFetcher(
func(common.Hash) bool { return false },
func(common.Hash, byte) error { return nil },
func(txs []*types.Transaction) []error {
return make([]error, len(txs))
},
@ -661,7 +661,7 @@ func TestTransactionFetcherMissingRescheduling(t *testing.T) {
testTransactionFetcherParallel(t, txFetcherTest{
init: func() *TxFetcher {
return NewTxFetcher(
func(common.Hash) bool { return false },
func(common.Hash, byte) error { return nil },
func(txs []*types.Transaction) []error {
return make([]error, len(txs))
},
@ -722,7 +722,7 @@ func TestTransactionFetcherMissingCleanup(t *testing.T) {
testTransactionFetcherParallel(t, txFetcherTest{
init: func() *TxFetcher {
return NewTxFetcher(
func(common.Hash) bool { return false },
func(common.Hash, byte) error { return nil },
func(txs []*types.Transaction) []error {
return make([]error, len(txs))
},
@ -771,7 +771,7 @@ func TestTransactionFetcherBroadcasts(t *testing.T) {
testTransactionFetcherParallel(t, txFetcherTest{
init: func() *TxFetcher {
return NewTxFetcher(
func(common.Hash) bool { return false },
func(common.Hash, byte) error { return nil },
func(txs []*types.Transaction) []error {
return make([]error, len(txs))
},
@ -827,7 +827,7 @@ func TestTransactionFetcherWaitTimerResets(t *testing.T) {
testTransactionFetcherParallel(t, txFetcherTest{
init: func() *TxFetcher {
return NewTxFetcher(
func(common.Hash) bool { return false },
func(common.Hash, byte) error { return nil },
nil,
func(string, []common.Hash) error { return nil },
nil,
@ -897,7 +897,7 @@ func TestTransactionFetcherTimeoutRescheduling(t *testing.T) {
testTransactionFetcherParallel(t, txFetcherTest{
init: func() *TxFetcher {
return NewTxFetcher(
func(common.Hash) bool { return false },
func(common.Hash, byte) error { return nil },
func(txs []*types.Transaction) []error {
return make([]error, len(txs))
},
@ -975,7 +975,7 @@ func TestTransactionFetcherTimeoutTimerResets(t *testing.T) {
testTransactionFetcherParallel(t, txFetcherTest{
init: func() *TxFetcher {
return NewTxFetcher(
func(common.Hash) bool { return false },
func(common.Hash, byte) error { return nil },
nil,
func(string, []common.Hash) error { return nil },
nil,
@ -1053,7 +1053,7 @@ func TestTransactionFetcherRateLimiting(t *testing.T) {
testTransactionFetcherParallel(t, txFetcherTest{
init: func() *TxFetcher {
return NewTxFetcher(
func(common.Hash) bool { return false },
func(common.Hash, byte) error { return nil },
nil,
func(string, []common.Hash) error { return nil },
nil,
@ -1083,7 +1083,7 @@ func TestTransactionFetcherBandwidthLimiting(t *testing.T) {
testTransactionFetcherParallel(t, txFetcherTest{
init: func() *TxFetcher {
return NewTxFetcher(
func(common.Hash) bool { return false },
func(common.Hash, byte) error { return nil },
nil,
func(string, []common.Hash) error { return nil },
nil,
@ -1200,7 +1200,7 @@ func TestTransactionFetcherDoSProtection(t *testing.T) {
testTransactionFetcherParallel(t, txFetcherTest{
init: func() *TxFetcher {
return NewTxFetcher(
func(common.Hash) bool { return false },
func(common.Hash, byte) error { return nil },
nil,
func(string, []common.Hash) error { return nil },
nil,
@ -1267,7 +1267,7 @@ func TestTransactionFetcherUnderpricedDedup(t *testing.T) {
testTransactionFetcherParallel(t, txFetcherTest{
init: func() *TxFetcher {
return NewTxFetcher(
func(common.Hash) bool { return false },
func(common.Hash, byte) error { return nil },
func(txs []*types.Transaction) []error {
errs := make([]error, len(txs))
for i := 0; i < len(errs); i++ {
@ -1368,7 +1368,7 @@ func TestTransactionFetcherUnderpricedDoSProtection(t *testing.T) {
testTransactionFetcher(t, txFetcherTest{
init: func() *TxFetcher {
return NewTxFetcher(
func(common.Hash) bool { return false },
func(common.Hash, byte) error { return nil },
func(txs []*types.Transaction) []error {
errs := make([]error, len(txs))
for i := 0; i < len(errs); i++ {
@ -1400,7 +1400,7 @@ func TestTransactionFetcherOutOfBoundDeliveries(t *testing.T) {
testTransactionFetcherParallel(t, txFetcherTest{
init: func() *TxFetcher {
return NewTxFetcher(
func(common.Hash) bool { return false },
func(common.Hash, byte) error { return nil },
func(txs []*types.Transaction) []error {
return make([]error, len(txs))
},
@ -1459,7 +1459,7 @@ func TestTransactionFetcherDrop(t *testing.T) {
testTransactionFetcherParallel(t, txFetcherTest{
init: func() *TxFetcher {
return NewTxFetcher(
func(common.Hash) bool { return false },
func(common.Hash, byte) error { return nil },
func(txs []*types.Transaction) []error {
return make([]error, len(txs))
},
@ -1533,7 +1533,7 @@ func TestTransactionFetcherDropRescheduling(t *testing.T) {
testTransactionFetcherParallel(t, txFetcherTest{
init: func() *TxFetcher {
return NewTxFetcher(
func(common.Hash) bool { return false },
func(common.Hash, byte) error { return nil },
func(txs []*types.Transaction) []error {
return make([]error, len(txs))
},
@ -1579,7 +1579,7 @@ func TestInvalidAnnounceMetadata(t *testing.T) {
testTransactionFetcherParallel(t, txFetcherTest{
init: func() *TxFetcher {
return NewTxFetcher(
func(common.Hash) bool { return false },
func(common.Hash, byte) error { return nil },
func(txs []*types.Transaction) []error {
return make([]error, len(txs))
},
@ -1662,7 +1662,7 @@ func TestTransactionFetcherFuzzCrash01(t *testing.T) {
testTransactionFetcherParallel(t, txFetcherTest{
init: func() *TxFetcher {
return NewTxFetcher(
func(common.Hash) bool { return false },
func(common.Hash, byte) error { return nil },
func(txs []*types.Transaction) []error {
return make([]error, len(txs))
},
@ -1690,7 +1690,7 @@ func TestTransactionFetcherFuzzCrash02(t *testing.T) {
testTransactionFetcherParallel(t, txFetcherTest{
init: func() *TxFetcher {
return NewTxFetcher(
func(common.Hash) bool { return false },
func(common.Hash, byte) error { return nil },
func(txs []*types.Transaction) []error {
return make([]error, len(txs))
},
@ -1720,7 +1720,7 @@ func TestTransactionFetcherFuzzCrash03(t *testing.T) {
testTransactionFetcherParallel(t, txFetcherTest{
init: func() *TxFetcher {
return NewTxFetcher(
func(common.Hash) bool { return false },
func(common.Hash, byte) error { return nil },
func(txs []*types.Transaction) []error {
return make([]error, len(txs))
},
@ -1759,7 +1759,7 @@ func TestTransactionFetcherFuzzCrash04(t *testing.T) {
testTransactionFetcherParallel(t, txFetcherTest{
init: func() *TxFetcher {
return NewTxFetcher(
func(common.Hash) bool { return false },
func(common.Hash, byte) error { return nil },
func(txs []*types.Transaction) []error {
return make([]error, len(txs))
},
@ -1794,7 +1794,7 @@ func TestBlobTransactionAnnounce(t *testing.T) {
testTransactionFetcherParallel(t, txFetcherTest{
init: func() *TxFetcher {
return NewTxFetcher(
func(common.Hash) bool { return false },
func(common.Hash, byte) error { return nil },
nil,
func(string, []common.Hash) error { return nil },
nil,
@ -1862,7 +1862,7 @@ func TestTransactionFetcherDropAlternates(t *testing.T) {
testTransactionFetcherParallel(t, txFetcherTest{
init: func() *TxFetcher {
return NewTxFetcher(
func(common.Hash) bool { return false },
func(common.Hash, byte) error { return nil },
func(txs []*types.Transaction) []error {
return make([]error, len(txs))
},
@ -1908,6 +1908,35 @@ func TestTransactionFetcherDropAlternates(t *testing.T) {
})
}
func TestTransactionFetcherWrongMetadata(t *testing.T) {
testTransactionFetcherParallel(t, txFetcherTest{
init: func() *TxFetcher {
return NewTxFetcher(
func(_ common.Hash, kind byte) error {
switch kind {
case types.LegacyTxType, types.AccessListTxType, types.DynamicFeeTxType, types.BlobTxType, types.SetCodeTxType:
return nil
}
return types.ErrTxTypeNotSupported
},
func(txs []*types.Transaction) []error {
return make([]error, len(txs))
},
func(string, []common.Hash) error { return nil },
nil,
)
},
steps: []interface{}{
doTxNotify{peer: "A", hashes: []common.Hash{{0x01}, {0x02}}, types: []byte{0xff, types.LegacyTxType}, sizes: []uint32{111, 222}},
isWaiting(map[string][]announce{
"A": {
{common.Hash{0x02}, types.LegacyTxType, 222},
},
}),
},
})
}
func testTransactionFetcherParallel(t *testing.T, tt txFetcherTest) {
t.Parallel()
testTransactionFetcher(t, tt)
@ -2245,7 +2274,7 @@ func TestTransactionForgotten(t *testing.T) {
}
fetcher := NewTxFetcherForTests(
func(common.Hash) bool { return false },
func(common.Hash, byte) error { return nil },
func(txs []*types.Transaction) []error {
errs := make([]error, len(txs))
for i := 0; i < len(errs); i++ {

Some files were not shown because too many files have changed in this diff Show more