Merge branch 'master' into engine-otel-tracing

This commit is contained in:
jonny rhea 2026-02-14 10:25:57 -06:00
commit adb8e8fb23
204 changed files with 15283 additions and 2184 deletions

View file

@ -158,10 +158,10 @@ func testLinkCase(tcInput linkTestCaseInput) error {
overrideAddrs = make(map[rune]common.Address) overrideAddrs = make(map[rune]common.Address)
) )
// generate deterministic addresses for the override set. // generate deterministic addresses for the override set.
rand.Seed(42) rng := rand.New(rand.NewSource(42))
for contract := range tcInput.overrides { for contract := range tcInput.overrides {
var addr common.Address var addr common.Address
rand.Read(addr[:]) rng.Read(addr[:])
overrideAddrs[contract] = addr overrideAddrs[contract] = addr
overridesAddrs[addr] = struct{}{} overridesAddrs[addr] = struct{}{}
} }

View file

@ -24,8 +24,8 @@ import (
"github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto/keccak"
"github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/event"
"golang.org/x/crypto/sha3"
) )
// Account represents an Ethereum account located at a specific location defined // Account represents an Ethereum account located at a specific location defined
@ -196,7 +196,7 @@ func TextHash(data []byte) []byte {
// This gives context to the signed message and prevents signing of transactions. // This gives context to the signed message and prevents signing of transactions.
func TextAndHash(data []byte) ([]byte, string) { func TextAndHash(data []byte) ([]byte, string) {
msg := fmt.Sprintf("\x19Ethereum Signed Message:\n%d%s", len(data), data) msg := fmt.Sprintf("\x19Ethereum Signed Message:\n%d%s", len(data), data)
hasher := sha3.NewLegacyKeccak256() hasher := keccak.NewLegacyKeccak256()
hasher.Write([]byte(msg)) hasher.Write([]byte(msg))
return hasher.Sum(nil), msg return hasher.Sum(nil), msg
} }

View file

@ -300,6 +300,10 @@ func (s *SecureChannelSession) decryptAPDU(data []byte) ([]byte, error) {
return nil, err return nil, err
} }
if len(data) == 0 || len(data)%aes.BlockSize != 0 {
return nil, fmt.Errorf("invalid ciphertext length: %d", len(data))
}
ret := make([]byte, len(data)) ret := make([]byte, len(data))
crypter := cipher.NewCBCDecrypter(a, s.iv) crypter := cipher.NewCBCDecrypter(a, s.iv)

View file

@ -43,6 +43,14 @@ const refreshCycle = time.Second
// trashing. // trashing.
const refreshThrottling = 500 * time.Millisecond const refreshThrottling = 500 * time.Millisecond
const (
// deviceUsagePage identifies Ledger devices by HID usage page (0xffa0) on Windows and macOS.
// See: https://github.com/LedgerHQ/ledger-live/blob/05a2980e838955a11a1418da638ef8ac3df4fb74/libs/ledgerjs/packages/hw-transport-node-hid-noevents/src/TransportNodeHid.ts
deviceUsagePage = 0xffa0
// deviceInterface identifies Ledger devices by USB interface number (0) on Linux.
deviceInterface = 0
)
// Hub is a accounts.Backend that can find and handle generic USB hardware wallets. // Hub is a accounts.Backend that can find and handle generic USB hardware wallets.
type Hub struct { type Hub struct {
scheme string // Protocol scheme prefixing account and wallet URLs. scheme string // Protocol scheme prefixing account and wallet URLs.
@ -82,6 +90,7 @@ func NewLedgerHub() (*Hub, error) {
0x0005, /* Ledger Nano S Plus */ 0x0005, /* Ledger Nano S Plus */
0x0006, /* Ledger Nano FTS */ 0x0006, /* Ledger Nano FTS */
0x0007, /* Ledger Flex */ 0x0007, /* Ledger Flex */
0x0008, /* Ledger Nano Gen5 */
0x0000, /* WebUSB Ledger Blue */ 0x0000, /* WebUSB Ledger Blue */
0x1000, /* WebUSB Ledger Nano S */ 0x1000, /* WebUSB Ledger Nano S */
@ -89,7 +98,8 @@ func NewLedgerHub() (*Hub, error) {
0x5000, /* WebUSB Ledger Nano S Plus */ 0x5000, /* WebUSB Ledger Nano S Plus */
0x6000, /* WebUSB Ledger Nano FTS */ 0x6000, /* WebUSB Ledger Nano FTS */
0x7000, /* WebUSB Ledger Flex */ 0x7000, /* WebUSB Ledger Flex */
}, 0xffa0, 0, newLedgerDriver) 0x8000, /* WebUSB Ledger Nano Gen5 */
}, deviceUsagePage, deviceInterface, newLedgerDriver)
} }
// NewTrezorHubWithHID creates a new hardware wallet manager for Trezor devices. // NewTrezorHubWithHID creates a new hardware wallet manager for Trezor devices.

View file

@ -1,32 +0,0 @@
machine:
services:
- docker
dependencies:
cache_directories:
- "~/.ethash" # Cache the ethash DAG generated by hive for consecutive builds
- "~/.docker" # Cache all docker images manually to avoid lengthy rebuilds
override:
# Restore all previously cached docker images
- mkdir -p ~/.docker
- for img in `ls ~/.docker`; do docker load -i ~/.docker/$img; done
# Pull in and hive, restore cached ethash DAGs and do a dry run
- go get -u github.com/karalabe/hive
- (cd ~/.go_workspace/src/github.com/karalabe/hive && mkdir -p workspace/ethash/ ~/.ethash)
- (cd ~/.go_workspace/src/github.com/karalabe/hive && cp -r ~/.ethash/. workspace/ethash/)
- (cd ~/.go_workspace/src/github.com/karalabe/hive && hive --docker-noshell --client=NONE --test=. --sim=. --loglevel=6)
# Cache all the docker images and the ethash DAGs
- for img in `docker images | grep -v "^<none>" | tail -n +2 | awk '{print $1}'`; do docker save $img > ~/.docker/`echo $img | tr '/' ':'`.tar; done
- cp -r ~/.go_workspace/src/github.com/karalabe/hive/workspace/ethash/. ~/.ethash
test:
override:
# Build Geth and move into a known folder
- make geth
- cp ./build/bin/geth $HOME/geth
# Run hive and move all generated logs into the public artifacts folder
- (cd ~/.go_workspace/src/github.com/karalabe/hive && hive --docker-noshell --client=go-ethereum:local --override=$HOME/geth --test=. --sim=.)
- cp -r ~/.go_workspace/src/github.com/karalabe/hive/workspace/logs/* $CIRCLE_ARTIFACTS

View file

@ -30,6 +30,8 @@ import (
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/internal/era" "github.com/ethereum/go-ethereum/internal/era"
"github.com/ethereum/go-ethereum/internal/era/execdb"
"github.com/ethereum/go-ethereum/internal/era/onedb"
"github.com/ethereum/go-ethereum/internal/ethapi" "github.com/ethereum/go-ethereum/internal/ethapi"
"github.com/ethereum/go-ethereum/internal/flags" "github.com/ethereum/go-ethereum/internal/flags"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
@ -53,7 +55,7 @@ var (
eraSizeFlag = &cli.IntFlag{ eraSizeFlag = &cli.IntFlag{
Name: "size", Name: "size",
Usage: "number of blocks per era", Usage: "number of blocks per era",
Value: era.MaxEra1Size, Value: era.MaxSize,
} }
txsFlag = &cli.BoolFlag{ txsFlag = &cli.BoolFlag{
Name: "txs", Name: "txs",
@ -131,7 +133,7 @@ func block(ctx *cli.Context) error {
return nil return nil
} }
// info prints some high-level information about the era1 file. // info prints some high-level information about the era file.
func info(ctx *cli.Context) error { func info(ctx *cli.Context) error {
epoch, err := strconv.ParseUint(ctx.Args().First(), 10, 64) epoch, err := strconv.ParseUint(ctx.Args().First(), 10, 64)
if err != nil { if err != nil {
@ -142,33 +144,34 @@ func info(ctx *cli.Context) error {
return err return err
} }
defer e.Close() defer e.Close()
acc, err := e.Accumulator() var (
if err != nil { accHex string
return fmt.Errorf("error reading accumulator: %w", err) tdStr string
)
if acc, err := e.Accumulator(); err == nil {
accHex = acc.Hex()
} }
td, err := e.InitialTD() if td, err := e.InitialTD(); err == nil {
if err != nil { tdStr = td.String()
return fmt.Errorf("error reading total difficulty: %w", err)
} }
info := struct { info := struct {
Accumulator common.Hash `json:"accumulator"` Accumulator string `json:"accumulator,omitempty"`
TotalDifficulty *big.Int `json:"totalDifficulty"` TotalDifficulty string `json:"totalDifficulty,omitempty"`
StartBlock uint64 `json:"startBlock"` StartBlock uint64 `json:"startBlock"`
Count uint64 `json:"count"` Count uint64 `json:"count"`
}{ }{
acc, td, e.Start(), e.Count(), accHex, tdStr, e.Start(), e.Count(),
} }
b, _ := json.MarshalIndent(info, "", " ") b, _ := json.MarshalIndent(info, "", " ")
fmt.Println(string(b)) fmt.Println(string(b))
return nil return nil
} }
// open opens an era1 file at a certain epoch. // open opens an era file at a certain epoch.
func open(ctx *cli.Context, epoch uint64) (*era.Era, error) { func open(ctx *cli.Context, epoch uint64) (era.Era, error) {
var ( dir := ctx.String(dirFlag.Name)
dir = ctx.String(dirFlag.Name) network := ctx.String(networkFlag.Name)
network = ctx.String(networkFlag.Name)
)
entries, err := era.ReadDir(dir, network) entries, err := era.ReadDir(dir, network)
if err != nil { if err != nil {
return nil, fmt.Errorf("error reading era dir: %w", err) return nil, fmt.Errorf("error reading era dir: %w", err)
@ -176,7 +179,28 @@ func open(ctx *cli.Context, epoch uint64) (*era.Era, error) {
if epoch >= uint64(len(entries)) { if epoch >= uint64(len(entries)) {
return nil, fmt.Errorf("epoch out-of-bounds: last %d, want %d", len(entries)-1, epoch) return nil, fmt.Errorf("epoch out-of-bounds: last %d, want %d", len(entries)-1, epoch)
} }
return era.Open(filepath.Join(dir, entries[epoch])) path := filepath.Join(dir, entries[epoch])
return openByPath(path)
}
// openByPath tries to open a single file as either eraE or era1 based on extension,
// falling back to the other reader if needed.
func openByPath(path string) (era.Era, error) {
switch strings.ToLower(filepath.Ext(path)) {
case ".erae":
if e, err := execdb.Open(path); err != nil {
return nil, err
} else {
return e, nil
}
case ".era1":
if e, err := onedb.Open(path); err != nil {
return nil, err
} else {
return e, nil
}
}
return nil, fmt.Errorf("unsupported or unreadable era file: %s", path)
} }
// verify checks each era1 file in a directory to ensure it is well-formed and // verify checks each era1 file in a directory to ensure it is well-formed and
@ -203,18 +227,58 @@ func verify(ctx *cli.Context) error {
return fmt.Errorf("error reading %s: %w", dir, err) return fmt.Errorf("error reading %s: %w", dir, err)
} }
if len(entries) != len(roots) { // Build the verification list respecting the rule:
return errors.New("number of era1 files should match the number of accumulator hashes") // era1: must have accumulator, always verify
// erae: verify only if accumulator exists (pre-merge)
// Build list of files to verify.
verify := make([]string, 0, len(entries))
for _, name := range entries {
path := filepath.Join(dir, name)
ext := strings.ToLower(filepath.Ext(name))
switch ext {
case ".era1":
e, err := onedb.Open(path)
if err != nil {
return fmt.Errorf("error opening era1 file %s: %w", name, err)
}
_, accErr := e.Accumulator()
e.Close()
if accErr != nil {
return fmt.Errorf("era1 file %s missing accumulator: %w", name, accErr)
}
verify = append(verify, path)
case ".erae":
e, err := execdb.Open(path)
if err != nil {
return fmt.Errorf("error opening erae file %s: %w", name, err)
}
_, accErr := e.Accumulator()
e.Close()
if accErr == nil {
verify = append(verify, path) // pre-merge only
}
default:
return fmt.Errorf("unsupported era file: %s", name)
}
}
if len(verify) != len(roots) {
return fmt.Errorf("mismatch between eras to verify (%d) and provided roots (%d)", len(verify), len(roots))
} }
// Verify each epoch matches the expected root. // Verify each epoch matches the expected root.
for i, want := range roots { for i, want := range roots {
// Wrap in function so defers don't stack. // Wrap in function so defers don't stack.
err := func() error { err := func() error {
name := entries[i] path := verify[i]
e, err := era.Open(filepath.Join(dir, name)) name := filepath.Base(path)
e, err := openByPath(path)
if err != nil { if err != nil {
return fmt.Errorf("error opening era1 file %s: %w", name, err) return fmt.Errorf("error opening era file %s: %w", name, err)
} }
defer e.Close() defer e.Close()
// Read accumulator and check against expected. // Read accumulator and check against expected.
@ -243,7 +307,7 @@ func verify(ctx *cli.Context) error {
} }
// checkAccumulator verifies the accumulator matches the data in the Era. // checkAccumulator verifies the accumulator matches the data in the Era.
func checkAccumulator(e *era.Era) error { func checkAccumulator(e era.Era) error {
var ( var (
err error err error
want common.Hash want common.Hash
@ -257,7 +321,7 @@ func checkAccumulator(e *era.Era) error {
if td, err = e.InitialTD(); err != nil { if td, err = e.InitialTD(); err != nil {
return fmt.Errorf("error reading total difficulty: %w", err) return fmt.Errorf("error reading total difficulty: %w", err)
} }
it, err := era.NewIterator(e) it, err := e.Iterator()
if err != nil { if err != nil {
return fmt.Errorf("error making era iterator: %w", err) return fmt.Errorf("error making era iterator: %w", err)
} }
@ -290,9 +354,13 @@ func checkAccumulator(e *era.Era) error {
if rr != block.ReceiptHash() { if rr != block.ReceiptHash() {
return fmt.Errorf("receipt root in block %d mismatch: want %s, got %s", block.NumberU64(), block.ReceiptHash(), rr) return fmt.Errorf("receipt root in block %d mismatch: want %s, got %s", block.NumberU64(), block.ReceiptHash(), rr)
} }
hashes = append(hashes, block.Hash()) // Only include pre-merge blocks in accumulator calculation.
td.Add(td, block.Difficulty()) // Post-merge blocks have difficulty == 0.
tds = append(tds, new(big.Int).Set(td)) if block.Difficulty().Sign() > 0 {
hashes = append(hashes, block.Hash())
td.Add(td, block.Difficulty())
tds = append(tds, new(big.Int).Set(td))
}
} }
if it.Error() != nil { if it.Error() != nil {
return fmt.Errorf("error reading block %d: %w", it.Number(), it.Error()) return fmt.Errorf("error reading block %d: %w", it.Number(), it.Error())

View file

@ -33,6 +33,7 @@ import (
"github.com/ethereum/go-ethereum/core/tracing" "github.com/ethereum/go-ethereum/core/tracing"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/crypto/keccak"
"github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
@ -40,7 +41,6 @@ import (
"github.com/ethereum/go-ethereum/trie" "github.com/ethereum/go-ethereum/trie"
"github.com/ethereum/go-ethereum/triedb" "github.com/ethereum/go-ethereum/triedb"
"github.com/holiman/uint256" "github.com/holiman/uint256"
"golang.org/x/crypto/sha3"
) )
type Prestate struct { type Prestate struct {
@ -415,7 +415,7 @@ func MakePreState(db ethdb.Database, accounts types.GenesisAlloc, isBintrie bool
} }
func rlpHash(x any) (h common.Hash) { func rlpHash(x any) (h common.Hash) {
hw := sha3.NewLegacyKeccak256() hw := keccak.NewLegacyKeccak256()
rlp.Encode(hw, x) rlp.Encode(hw, x)
hw.Sum(h[:0]) hw.Sum(h[:0])
return h return h

View file

@ -115,9 +115,6 @@ func Transaction(ctx *cli.Context) error {
} }
var results []result var results []result
for it.Next() { for it.Next() {
if err := it.Err(); err != nil {
return NewError(ErrorIO, err)
}
var tx types.Transaction var tx types.Transaction
err := rlp.DecodeBytes(it.Value(), &tx) err := rlp.DecodeBytes(it.Value(), &tx)
if err != nil { if err != nil {
@ -188,6 +185,10 @@ func Transaction(ctx *cli.Context) error {
} }
results = append(results, r) results = append(results, r)
} }
if err := it.Err(); err != nil {
return NewError(ErrorIO, err)
}
out, err := json.MarshalIndent(results, "", " ") out, err := json.MarshalIndent(results, "", " ")
fmt.Println(string(out)) fmt.Println(string(out))
return err return err

View file

@ -20,6 +20,7 @@ import (
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
"io"
"os" "os"
"path/filepath" "path/filepath"
"regexp" "regexp"
@ -43,6 +44,8 @@ import (
"github.com/ethereum/go-ethereum/internal/debug" "github.com/ethereum/go-ethereum/internal/debug"
"github.com/ethereum/go-ethereum/internal/era" "github.com/ethereum/go-ethereum/internal/era"
"github.com/ethereum/go-ethereum/internal/era/eradl" "github.com/ethereum/go-ethereum/internal/era/eradl"
"github.com/ethereum/go-ethereum/internal/era/execdb"
"github.com/ethereum/go-ethereum/internal/era/onedb"
"github.com/ethereum/go-ethereum/internal/flags" "github.com/ethereum/go-ethereum/internal/flags"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/node" "github.com/ethereum/go-ethereum/node"
@ -120,6 +123,8 @@ if one is set. Otherwise it prints the genesis from the datadir.`,
utils.LogNoHistoryFlag, utils.LogNoHistoryFlag,
utils.LogExportCheckpointsFlag, utils.LogExportCheckpointsFlag,
utils.StateHistoryFlag, utils.StateHistoryFlag,
utils.TrienodeHistoryFlag,
utils.TrienodeHistoryFullValueCheckpointFlag,
}, utils.DatabaseFlags, debug.Flags), }, utils.DatabaseFlags, debug.Flags),
Before: func(ctx *cli.Context) error { Before: func(ctx *cli.Context) error {
flags.MigrateGlobalFlags(ctx) flags.MigrateGlobalFlags(ctx)
@ -151,7 +156,7 @@ be gzipped.`,
Name: "import-history", Name: "import-history",
Usage: "Import an Era archive", Usage: "Import an Era archive",
ArgsUsage: "<dir>", ArgsUsage: "<dir>",
Flags: slices.Concat([]cli.Flag{utils.TxLookupLimitFlag, utils.TransactionHistoryFlag}, utils.DatabaseFlags, utils.NetworkFlags), Flags: slices.Concat([]cli.Flag{utils.TxLookupLimitFlag, utils.TransactionHistoryFlag, utils.EraFormatFlag}, utils.DatabaseFlags, utils.NetworkFlags),
Description: ` Description: `
The import-history command will import blocks and their corresponding receipts The import-history command will import blocks and their corresponding receipts
from Era archives. from Era archives.
@ -162,7 +167,7 @@ from Era archives.
Name: "export-history", Name: "export-history",
Usage: "Export blockchain history to Era archives", Usage: "Export blockchain history to Era archives",
ArgsUsage: "<dir> <first> <last>", ArgsUsage: "<dir> <first> <last>",
Flags: utils.DatabaseFlags, Flags: slices.Concat([]cli.Flag{utils.EraFormatFlag}, utils.DatabaseFlags),
Description: ` Description: `
The export-history command will export blocks and their corresponding receipts The export-history command will export blocks and their corresponding receipts
into Era archives. Eras are typically packaged in steps of 8192 blocks. into Era archives. Eras are typically packaged in steps of 8192 blocks.
@ -514,15 +519,27 @@ func importHistory(ctx *cli.Context) error {
network = networks[0] network = networks[0]
} }
if err := utils.ImportHistory(chain, dir, network); err != nil { var (
format = ctx.String(utils.EraFormatFlag.Name)
from func(era.ReadAtSeekCloser) (era.Era, error)
)
switch format {
case "era1", "era":
from = onedb.From
case "erae":
from = execdb.From
default:
return fmt.Errorf("unknown --era.format %q (expected 'era1' or 'erae')", format)
}
if err := utils.ImportHistory(chain, dir, network, from); err != nil {
return err return err
} }
fmt.Printf("Import done in %v\n", time.Since(start)) fmt.Printf("Import done in %v\n", time.Since(start))
return nil return nil
} }
// exportHistory exports chain history in Era archives at a specified // exportHistory exports chain history in Era archives at a specified directory.
// directory.
func exportHistory(ctx *cli.Context) error { func exportHistory(ctx *cli.Context) error {
if ctx.Args().Len() != 3 { if ctx.Args().Len() != 3 {
utils.Fatalf("usage: %s", ctx.Command.ArgsUsage) utils.Fatalf("usage: %s", ctx.Command.ArgsUsage)
@ -548,10 +565,26 @@ func exportHistory(ctx *cli.Context) error {
if head := chain.CurrentSnapBlock(); uint64(last) > head.Number.Uint64() { if head := chain.CurrentSnapBlock(); uint64(last) > head.Number.Uint64() {
utils.Fatalf("Export error: block number %d larger than head block %d\n", uint64(last), head.Number.Uint64()) utils.Fatalf("Export error: block number %d larger than head block %d\n", uint64(last), head.Number.Uint64())
} }
err := utils.ExportHistory(chain, dir, uint64(first), uint64(last), uint64(era.MaxEra1Size))
if err != nil { var (
format = ctx.String(utils.EraFormatFlag.Name)
filename func(network string, epoch int, root common.Hash) string
newBuilder func(w io.Writer) era.Builder
)
switch format {
case "era1", "era":
newBuilder = func(w io.Writer) era.Builder { return onedb.NewBuilder(w) }
filename = func(network string, epoch int, root common.Hash) string { return onedb.Filename(network, epoch, root) }
case "erae":
newBuilder = func(w io.Writer) era.Builder { return execdb.NewBuilder(w) }
filename = func(network string, epoch int, root common.Hash) string { return execdb.Filename(network, epoch, root) }
default:
return fmt.Errorf("unknown archive format %q (use 'era1' or 'erae')", format)
}
if err := utils.ExportHistory(chain, dir, uint64(first), uint64(last), newBuilder, filename); err != nil {
utils.Fatalf("Export error: %v\n", err) utils.Fatalf("Export error: %v\n", err)
} }
fmt.Printf("Export done in %v\n", time.Since(start)) fmt.Printf("Export done in %v\n", time.Since(start))
return nil return nil
} }

View file

@ -39,6 +39,7 @@ import (
"github.com/ethereum/go-ethereum/eth/catalyst" "github.com/ethereum/go-ethereum/eth/catalyst"
"github.com/ethereum/go-ethereum/eth/ethconfig" "github.com/ethereum/go-ethereum/eth/ethconfig"
"github.com/ethereum/go-ethereum/internal/flags" "github.com/ethereum/go-ethereum/internal/flags"
"github.com/ethereum/go-ethereum/internal/telemetry/tracesetup"
"github.com/ethereum/go-ethereum/internal/version" "github.com/ethereum/go-ethereum/internal/version"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/metrics" "github.com/ethereum/go-ethereum/metrics"
@ -239,9 +240,15 @@ func makeFullNode(ctx *cli.Context) *node.Node {
cfg.Eth.OverrideVerkle = &v cfg.Eth.OverrideVerkle = &v
} }
// Start metrics export if enabled // Start metrics export if enabled.
utils.SetupMetrics(&cfg.Metrics) utils.SetupMetrics(&cfg.Metrics)
// Setup OpenTelemetry reporting if enabled.
if err := tracesetup.SetupTelemetry(cfg.Node.OpenTelemetry, stack); err != nil {
utils.Fatalf("failed to setup OpenTelemetry: %v", err)
}
// Add Ethereum service.
backend, eth := utils.RegisterEthService(stack, &cfg.Eth) backend, eth := utils.RegisterEthService(stack, &cfg.Eth)
// Create gauge with geth system and build information // Create gauge with geth system and build information
@ -398,9 +405,9 @@ func applyMetricConfig(ctx *cli.Context, cfg *gethConfig) {
ctx.IsSet(utils.MetricsInfluxDBBucketFlag.Name) ctx.IsSet(utils.MetricsInfluxDBBucketFlag.Name)
if enableExport && v2FlagIsSet { if enableExport && v2FlagIsSet {
utils.Fatalf("Flags --influxdb.metrics.organization, --influxdb.metrics.token, --influxdb.metrics.bucket are only available for influxdb-v2") utils.Fatalf("Flags --%s, --%s, --%s are only available for influxdb-v2", utils.MetricsInfluxDBOrganizationFlag.Name, utils.MetricsInfluxDBTokenFlag.Name, utils.MetricsInfluxDBBucketFlag.Name)
} else if enableExportV2 && v1FlagIsSet { } else if enableExportV2 && v1FlagIsSet {
utils.Fatalf("Flags --influxdb.metrics.username, --influxdb.metrics.password are only available for influxdb-v1") utils.Fatalf("Flags --%s, --%s are only available for influxdb-v1", utils.MetricsInfluxDBUsernameFlag.Name, utils.MetricsInfluxDBPasswordFlag.Name)
} }
} }
} }

View file

@ -94,6 +94,8 @@ var (
utils.LogNoHistoryFlag, utils.LogNoHistoryFlag,
utils.LogExportCheckpointsFlag, utils.LogExportCheckpointsFlag,
utils.StateHistoryFlag, utils.StateHistoryFlag,
utils.TrienodeHistoryFlag,
utils.TrienodeHistoryFullValueCheckpointFlag,
utils.LightKDFFlag, utils.LightKDFFlag,
utils.EthRequiredBlocksFlag, utils.EthRequiredBlocksFlag,
utils.LegacyWhitelistFlag, // deprecated utils.LegacyWhitelistFlag, // deprecated
@ -193,6 +195,14 @@ var (
utils.BatchResponseMaxSize, utils.BatchResponseMaxSize,
utils.RPCTxSyncDefaultTimeoutFlag, utils.RPCTxSyncDefaultTimeoutFlag,
utils.RPCTxSyncMaxTimeoutFlag, utils.RPCTxSyncMaxTimeoutFlag,
utils.RPCGlobalRangeLimitFlag,
utils.RPCTelemetryFlag,
utils.RPCTelemetryEndpointFlag,
utils.RPCTelemetryUserFlag,
utils.RPCTelemetryPasswordFlag,
utils.RPCTelemetryInstanceIDFlag,
utils.RPCTelemetryTagsFlag,
utils.RPCTelemetrySampleRatioFlag,
} }
metricsFlags = []cli.Flag{ metricsFlags = []cli.Flag{

View file

@ -0,0 +1,36 @@
// Copyright 2026 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
//go:build wasm
// +build wasm
package main
import (
"unsafe"
)
//go:wasmimport geth_io len
func hintLen() uint32
//go:wasmimport geth_io read
func hintRead(data unsafe.Pointer)
func getInput() []byte {
data := make([]byte, hintLen())
hintRead(unsafe.Pointer(&data[0]))
return data
}

View file

@ -38,8 +38,8 @@ require (
go.opentelemetry.io/otel v1.39.0 // indirect go.opentelemetry.io/otel v1.39.0 // indirect
go.opentelemetry.io/otel/metric v1.39.0 // indirect go.opentelemetry.io/otel/metric v1.39.0 // indirect
go.opentelemetry.io/otel/trace v1.39.0 // indirect go.opentelemetry.io/otel/trace v1.39.0 // indirect
golang.org/x/crypto v0.36.0 // indirect golang.org/x/crypto v0.44.0 // indirect
golang.org/x/sync v0.12.0 // indirect golang.org/x/sync v0.18.0 // indirect
golang.org/x/sys v0.39.0 // indirect golang.org/x/sys v0.39.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect
) )

View file

@ -70,8 +70,8 @@ github.com/holiman/bloomfilter/v2 v2.0.3 h1:73e0e/V0tCydx14a0SCYS/EWCxgwLZ18CZcZ
github.com/holiman/bloomfilter/v2 v2.0.3/go.mod h1:zpoh+gs7qcpqrHr3dB55AMiJwo0iURXE7ZOP9L9hSkA= github.com/holiman/bloomfilter/v2 v2.0.3/go.mod h1:zpoh+gs7qcpqrHr3dB55AMiJwo0iURXE7ZOP9L9hSkA=
github.com/holiman/uint256 v1.3.2 h1:a9EgMPSC1AAaj1SZL5zIQD3WbwTuHrMGOerLjGmM/TA= github.com/holiman/uint256 v1.3.2 h1:a9EgMPSC1AAaj1SZL5zIQD3WbwTuHrMGOerLjGmM/TA=
github.com/holiman/uint256 v1.3.2/go.mod h1:EOMSn4q6Nyt9P6efbI3bueV4e1b3dGlUCXeiRV4ng7E= github.com/holiman/uint256 v1.3.2/go.mod h1:EOMSn4q6Nyt9P6efbI3bueV4e1b3dGlUCXeiRV4ng7E=
github.com/klauspost/compress v1.16.0 h1:iULayQNOReoYUe+1qtKOqw9CwJv3aNQu8ivo7lw1HU4= github.com/klauspost/compress v1.17.8 h1:YcnTYrq7MikUT7k0Yb5eceMmALQPYBW/Xltxn0NAMnU=
github.com/klauspost/compress v1.16.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= github.com/klauspost/compress v1.17.8/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
github.com/klauspost/cpuid/v2 v2.0.4/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.0.4/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.0.9 h1:lgaqFMSdTdQYdZ04uHyN2d/eKdOMyi2YLSvlQIBFYa4= github.com/klauspost/cpuid/v2 v2.0.9 h1:lgaqFMSdTdQYdZ04uHyN2d/eKdOMyi2YLSvlQIBFYa4=
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
@ -123,22 +123,22 @@ go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF
go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs= go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs=
go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI= go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI=
go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA=
golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= golang.org/x/crypto v0.44.0 h1:A97SsFvM3AIwEEmTBiaxPPTYpDC47w720rdiiUvgoAU=
golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= golang.org/x/crypto v0.44.0/go.mod h1:013i+Nw79BMiQiMsOPcVCB5ZIJbYkerPrGnOa00tvmc=
golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df h1:UA2aFVmmsIlefxMk29Dp2juaUSth8Pyn3Tq5Y5mJGME= golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df h1:UA2aFVmmsIlefxMk29Dp2juaUSth8Pyn3Tq5Y5mJGME=
golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc= golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc=
golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw= golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I=
golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk=
golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=

View file

@ -14,7 +14,8 @@
// You should have received a copy of the GNU Lesser General Public License // You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
//go:build !example && !ziren //go:build !example && !ziren && !wasm
// +build !example,!ziren,!wasm
package main package main

View file

@ -57,6 +57,8 @@ const (
importBatchSize = 2500 importBatchSize = 2500
) )
type EraFileFormat int
// ErrImportInterrupted is returned when the user interrupts the import process. // ErrImportInterrupted is returned when the user interrupts the import process.
var ErrImportInterrupted = errors.New("interrupted") var ErrImportInterrupted = errors.New("interrupted")
@ -250,7 +252,7 @@ func readList(filename string) ([]string, error) {
// ImportHistory imports Era1 files containing historical block information, // ImportHistory imports Era1 files containing historical block information,
// starting from genesis. The assumption is held that the provided chain // starting from genesis. The assumption is held that the provided chain
// segment in Era1 file should all be canonical and verified. // segment in Era1 file should all be canonical and verified.
func ImportHistory(chain *core.BlockChain, dir string, network string) error { func ImportHistory(chain *core.BlockChain, dir string, network string, from func(f era.ReadAtSeekCloser) (era.Era, error)) error {
if chain.CurrentSnapBlock().Number.BitLen() != 0 { if chain.CurrentSnapBlock().Number.BitLen() != 0 {
return errors.New("history import only supported when starting from genesis") return errors.New("history import only supported when starting from genesis")
} }
@ -263,42 +265,49 @@ func ImportHistory(chain *core.BlockChain, dir string, network string) error {
return fmt.Errorf("unable to read checksums.txt: %w", err) return fmt.Errorf("unable to read checksums.txt: %w", err)
} }
if len(checksums) != len(entries) { if len(checksums) != len(entries) {
return fmt.Errorf("expected equal number of checksums and entries, have: %d checksums, %d entries", len(checksums), len(entries)) return fmt.Errorf("expected equal number of checksums and entries, have: %d checksums, %d entries",
len(checksums), len(entries))
} }
var ( var (
start = time.Now() start = time.Now()
reported = time.Now() reported = time.Now()
imported = 0 imported = 0
h = sha256.New() h = sha256.New()
buf = bytes.NewBuffer(nil) scratch = bytes.NewBuffer(nil)
) )
for i, filename := range entries {
for i, file := range entries {
err := func() error { err := func() error {
f, err := os.Open(filepath.Join(dir, filename)) path := filepath.Join(dir, file)
// validate against checksum file in directory
f, err := os.Open(path)
if err != nil { if err != nil {
return fmt.Errorf("unable to open era: %w", err) return fmt.Errorf("open %s: %w", path, err)
} }
defer f.Close() defer f.Close()
// Validate checksum.
if _, err := io.Copy(h, f); err != nil { if _, err := io.Copy(h, f); err != nil {
return fmt.Errorf("unable to recalculate checksum: %w", err) return fmt.Errorf("checksum %s: %w", path, err)
}
if have, want := common.BytesToHash(h.Sum(buf.Bytes()[:])).Hex(), checksums[i]; have != want {
return fmt.Errorf("checksum mismatch: have %s, want %s", have, want)
} }
got := common.BytesToHash(h.Sum(scratch.Bytes()[:])).Hex()
want := checksums[i]
h.Reset() h.Reset()
buf.Reset() scratch.Reset()
if got != want {
return fmt.Errorf("%s checksum mismatch: have %s want %s", file, got, want)
}
// Import all block data from Era1. // Import all block data from Era1.
e, err := era.From(f) e, err := from(f)
if err != nil { if err != nil {
return fmt.Errorf("error opening era: %w", err) return fmt.Errorf("error opening era: %w", err)
} }
it, err := era.NewIterator(e) it, err := e.Iterator()
if err != nil { if err != nil {
return fmt.Errorf("error making era reader: %w", err) return fmt.Errorf("error creating iterator: %w", err)
} }
for it.Next() { for it.Next() {
block, err := it.Block() block, err := it.Block()
if err != nil { if err != nil {
@ -311,26 +320,28 @@ func ImportHistory(chain *core.BlockChain, dir string, network string) error {
if err != nil { if err != nil {
return fmt.Errorf("error reading receipts %d: %w", it.Number(), err) return fmt.Errorf("error reading receipts %d: %w", it.Number(), err)
} }
encReceipts := types.EncodeBlockReceiptLists([]types.Receipts{receipts}) enc := types.EncodeBlockReceiptLists([]types.Receipts{receipts})
if _, err := chain.InsertReceiptChain([]*types.Block{block}, encReceipts, math.MaxUint64); err != nil { if _, err := chain.InsertReceiptChain([]*types.Block{block}, enc, math.MaxUint64); err != nil {
return fmt.Errorf("error inserting body %d: %w", it.Number(), err) return fmt.Errorf("error inserting body %d: %w", it.Number(), err)
} }
imported += 1 imported++
// Give the user some feedback that something is happening.
if time.Since(reported) >= 8*time.Second { if time.Since(reported) >= 8*time.Second {
log.Info("Importing Era files", "head", it.Number(), "imported", imported, "elapsed", common.PrettyDuration(time.Since(start))) log.Info("Importing Era files", "head", it.Number(), "imported", imported,
"elapsed", common.PrettyDuration(time.Since(start)))
imported = 0 imported = 0
reported = time.Now() reported = time.Now()
} }
} }
if err := it.Error(); err != nil {
return err
}
return nil return nil
}() }()
if err != nil { if err != nil {
return err return err
} }
} }
return nil return nil
} }
@ -389,7 +400,6 @@ func ExportAppendChain(blockchain *core.BlockChain, fn string, first uint64, las
return err return err
} }
defer fh.Close() defer fh.Close()
var writer io.Writer = fh var writer io.Writer = fh
if strings.HasSuffix(fn, ".gz") { if strings.HasSuffix(fn, ".gz") {
writer = gzip.NewWriter(writer) writer = gzip.NewWriter(writer)
@ -405,7 +415,7 @@ func ExportAppendChain(blockchain *core.BlockChain, fn string, first uint64, las
// ExportHistory exports blockchain history into the specified directory, // ExportHistory exports blockchain history into the specified directory,
// following the Era format. // following the Era format.
func ExportHistory(bc *core.BlockChain, dir string, first, last, step uint64) error { func ExportHistory(bc *core.BlockChain, dir string, first, last uint64, newBuilder func(io.Writer) era.Builder, filename func(network string, epoch int, lastBlockHash common.Hash) string) error {
log.Info("Exporting blockchain history", "dir", dir) log.Info("Exporting blockchain history", "dir", dir)
if head := bc.CurrentBlock().Number.Uint64(); head < last { if head := bc.CurrentBlock().Number.Uint64(); head < last {
log.Warn("Last block beyond head, setting last = head", "head", head, "last", last) log.Warn("Last block beyond head, setting last = head", "head", head, "last", last)
@ -418,76 +428,100 @@ func ExportHistory(bc *core.BlockChain, dir string, first, last, step uint64) er
if err := os.MkdirAll(dir, os.ModePerm); err != nil { if err := os.MkdirAll(dir, os.ModePerm); err != nil {
return fmt.Errorf("error creating output directory: %w", err) return fmt.Errorf("error creating output directory: %w", err)
} }
var ( var (
start = time.Now() start = time.Now()
reported = time.Now() reported = time.Now()
h = sha256.New() h = sha256.New()
buf = bytes.NewBuffer(nil) buf = bytes.NewBuffer(nil)
td = new(big.Int)
checksums []string checksums []string
) )
td := new(big.Int)
for i := uint64(0); i < first; i++ { // Compute initial TD by accumulating difficulty from genesis to first-1.
td.Add(td, bc.GetHeaderByNumber(i).Difficulty) // This is necessary because TD is no longer stored in the database. Only
// compute if a segment of the export is pre-merge.
b := bc.GetBlockByNumber(first)
if b == nil {
return fmt.Errorf("block #%d not found", first)
} }
for i := first; i <= last; i += step { if first > 0 && b.Difficulty().Sign() != 0 {
err := func() error { log.Info("Computing initial total difficulty", "from", 0, "to", first-1)
filename := filepath.Join(dir, era.Filename(network, int(i/step), common.Hash{})) for i := uint64(0); i < first; i++ {
f, err := os.Create(filename) b := bc.GetBlockByNumber(i)
if b == nil {
return fmt.Errorf("block #%d not found while computing initial TD", i)
}
td.Add(td, b.Difficulty())
}
log.Info("Initial total difficulty computed", "td", td)
}
for batch := first; batch <= last; batch += uint64(era.MaxSize) {
idx := int(batch / uint64(era.MaxSize))
tmpPath := filepath.Join(dir, filename(network, idx, common.Hash{}))
if err := func() error {
f, err := os.Create(tmpPath)
if err != nil { if err != nil {
return fmt.Errorf("could not create era file: %w", err) return err
} }
defer f.Close() defer f.Close()
w := era.NewBuilder(f) builder := newBuilder(f)
for j := uint64(0); j < step && j <= last-i; j++ {
var ( for j := uint64(0); j < uint64(era.MaxSize) && batch+j <= last; j++ {
n = i + j n := batch + j
block = bc.GetBlockByNumber(n) block := bc.GetBlockByNumber(n)
)
if block == nil { if block == nil {
return fmt.Errorf("export failed on #%d: not found", n) return fmt.Errorf("block #%d not found", n)
} }
receipts := bc.GetReceiptsByHash(block.Hash()) receipt := bc.GetReceiptsByHash(block.Hash())
if receipts == nil { if receipt == nil {
return fmt.Errorf("export failed on #%d: receipts not found", n) return fmt.Errorf("receipts for #%d missing", n)
} }
td.Add(td, block.Difficulty())
if err := w.Add(block, receipts, new(big.Int).Set(td)); err != nil { // For pre-merge blocks, pass accumulated TD.
// For post-merge blocks (difficulty == 0), pass nil TD.
var blockTD *big.Int
if block.Difficulty().Sign() != 0 {
td.Add(td, block.Difficulty())
blockTD = new(big.Int).Set(td)
}
if err := builder.Add(block, receipt, blockTD); err != nil {
return err return err
} }
} }
root, err := w.Finalize() id, err := builder.Finalize()
if err != nil { if err != nil {
return fmt.Errorf("export failed to finalize %d: %w", step/i, err) return err
} }
// Set correct filename with root.
os.Rename(filename, filepath.Join(dir, era.Filename(network, int(i/step), root)))
// Compute checksum of entire Era1.
if _, err := f.Seek(0, io.SeekStart); err != nil { if _, err := f.Seek(0, io.SeekStart); err != nil {
return err return err
} }
if _, err := io.Copy(h, f); err != nil {
return fmt.Errorf("unable to calculate checksum: %w", err)
}
checksums = append(checksums, common.BytesToHash(h.Sum(buf.Bytes()[:])).Hex())
h.Reset() h.Reset()
buf.Reset() buf.Reset()
return nil if _, err := io.Copy(h, f); err != nil {
}() return err
if err != nil { }
checksums = append(checksums, common.BytesToHash(h.Sum(buf.Bytes()[:])).Hex())
// Close before rename. It's required on Windows.
f.Close()
final := filepath.Join(dir, filename(network, idx, id))
return os.Rename(tmpPath, final)
}(); err != nil {
return err return err
} }
if time.Since(reported) >= 8*time.Second { if time.Since(reported) >= 8*time.Second {
log.Info("Exporting blocks", "exported", i, "elapsed", common.PrettyDuration(time.Since(start))) log.Info("export progress", "exported", batch, "elapsed", common.PrettyDuration(time.Since(start)))
reported = time.Now() reported = time.Now()
} }
} }
os.WriteFile(filepath.Join(dir, "checksums.txt"), []byte(strings.Join(checksums, "\n")), os.ModePerm) _ = os.WriteFile(filepath.Join(dir, "checksums.txt"), []byte(strings.Join(checksums, "\n")), os.ModePerm)
log.Info("Exported blockchain to", "dir", dir)
return nil return nil
} }

View file

@ -295,6 +295,18 @@ var (
Value: ethconfig.Defaults.StateHistory, Value: ethconfig.Defaults.StateHistory,
Category: flags.StateCategory, Category: flags.StateCategory,
} }
TrienodeHistoryFlag = &cli.Int64Flag{
Name: "history.trienode",
Usage: "Number of recent blocks to retain trienode history for, only relevant in state.scheme=path (default/negative = disabled, 0 = entire chain)",
Value: ethconfig.Defaults.TrienodeHistory,
Category: flags.StateCategory,
}
TrienodeHistoryFullValueCheckpointFlag = &cli.UintFlag{
Name: "history.trienode.full-value-checkpoint",
Usage: "The frequency of full-value encoding. Every n-th node is stored in full-value format; all other nodes are stored as diffs relative to their predecessor",
Value: uint(ethconfig.Defaults.NodeFullValueCheckpoint),
Category: flags.StateCategory,
}
TransactionHistoryFlag = &cli.Uint64Flag{ TransactionHistoryFlag = &cli.Uint64Flag{
Name: "history.transactions", Name: "history.transactions",
Usage: "Number of recent blocks to maintain transactions index for (default = about one year, 0 = entire chain)", Usage: "Number of recent blocks to maintain transactions index for (default = about one year, 0 = entire chain)",
@ -636,6 +648,12 @@ var (
Value: ethconfig.Defaults.TxSyncMaxTimeout, Value: ethconfig.Defaults.TxSyncMaxTimeout,
Category: flags.APICategory, Category: flags.APICategory,
} }
RPCGlobalRangeLimitFlag = &cli.Uint64Flag{
Name: "rpc.rangelimit",
Usage: "Maximum block range (end - begin) allowed for range queries (0 = unlimited)",
Value: ethconfig.Defaults.RangeLimit,
Category: flags.APICategory,
}
// Authenticated RPC HTTP settings // Authenticated RPC HTTP settings
AuthListenFlag = &cli.StringFlag{ AuthListenFlag = &cli.StringFlag{
Name: "authrpc.addr", Name: "authrpc.addr",
@ -674,7 +692,7 @@ var (
} }
LogSlowBlockFlag = &cli.DurationFlag{ LogSlowBlockFlag = &cli.DurationFlag{
Name: "debug.logslowblock", Name: "debug.logslowblock",
Usage: "Block execution time threshold beyond which detailed statistics will be logged (0 means disable)", Usage: "Block execution time threshold beyond which detailed statistics will be logged (0 logs all blocks, negative means disable)",
Value: ethconfig.Defaults.SlowBlockThreshold, Value: ethconfig.Defaults.SlowBlockThreshold,
Category: flags.LoggingCategory, Category: flags.LoggingCategory,
} }
@ -1024,6 +1042,55 @@ Please note that --` + MetricsHTTPFlag.Name + ` must be set to start the server.
Value: metrics.DefaultConfig.InfluxDBOrganization, Value: metrics.DefaultConfig.InfluxDBOrganization,
Category: flags.MetricsCategory, Category: flags.MetricsCategory,
} }
// RPC Telemetry
RPCTelemetryFlag = &cli.BoolFlag{
Name: "rpc.telemetry",
Usage: "Enable RPC telemetry",
Category: flags.APICategory,
}
RPCTelemetryEndpointFlag = &cli.StringFlag{
Name: "rpc.telemetry.endpoint",
Usage: "Defines where RPC telemetry is sent (e.g., http://localhost:4318)",
Category: flags.APICategory,
}
RPCTelemetryUserFlag = &cli.StringFlag{
Name: "rpc.telemetry.username",
Usage: "HTTP Basic Auth username for OpenTelemetry",
Category: flags.APICategory,
}
RPCTelemetryPasswordFlag = &cli.StringFlag{
Name: "rpc.telemetry.password",
Usage: "HTTP Basic Auth password for OpenTelemetry",
Category: flags.APICategory,
}
RPCTelemetryInstanceIDFlag = &cli.StringFlag{
Name: "rpc.telemetry.instance-id",
Usage: "OpenTelemetry instance ID",
Category: flags.APICategory,
}
RPCTelemetryTagsFlag = &cli.StringFlag{
Name: "rpc.telemetry.tags",
Usage: "Comma-separated tags (key/values) added as attributes to the OpenTelemetry resource struct",
Category: flags.APICategory,
}
RPCTelemetrySampleRatioFlag = &cli.Float64Flag{
Name: "rpc.telemetry.sample-ratio",
Usage: "Defines the sampling ratio for RPC telemetry (0.0 to 1.0)",
Value: 1.0,
Category: flags.APICategory,
}
// Era flags are a group of flags related to the era archive format.
EraFormatFlag = &cli.StringFlag{
Name: "era.format",
Usage: "Archive format: 'era1' or 'erae'",
}
) )
var ( var (
@ -1414,6 +1481,7 @@ func SetNodeConfig(ctx *cli.Context, cfg *node.Config) {
setNodeUserIdent(ctx, cfg) setNodeUserIdent(ctx, cfg)
SetDataDir(ctx, cfg) SetDataDir(ctx, cfg)
setSmartCard(ctx, cfg) setSmartCard(ctx, cfg)
setOpenTelemetry(ctx, cfg)
if ctx.IsSet(JWTSecretFlag.Name) { if ctx.IsSet(JWTSecretFlag.Name) {
cfg.JWTSecret = ctx.String(JWTSecretFlag.Name) cfg.JWTSecret = ctx.String(JWTSecretFlag.Name)
@ -1481,6 +1549,33 @@ func setSmartCard(ctx *cli.Context, cfg *node.Config) {
cfg.SmartCardDaemonPath = path cfg.SmartCardDaemonPath = path
} }
func setOpenTelemetry(ctx *cli.Context, cfg *node.Config) {
tcfg := &cfg.OpenTelemetry
if ctx.IsSet(RPCTelemetryFlag.Name) {
tcfg.Enabled = ctx.Bool(RPCTelemetryFlag.Name)
}
if ctx.IsSet(RPCTelemetryEndpointFlag.Name) {
tcfg.Endpoint = ctx.String(RPCTelemetryEndpointFlag.Name)
}
if ctx.IsSet(RPCTelemetryUserFlag.Name) {
tcfg.AuthUser = ctx.String(RPCTelemetryUserFlag.Name)
}
if ctx.IsSet(RPCTelemetryPasswordFlag.Name) {
tcfg.AuthPassword = ctx.String(RPCTelemetryPasswordFlag.Name)
}
if ctx.IsSet(RPCTelemetryInstanceIDFlag.Name) {
tcfg.InstanceID = ctx.String(RPCTelemetryInstanceIDFlag.Name)
}
if ctx.IsSet(RPCTelemetryTagsFlag.Name) {
tcfg.Tags = ctx.String(RPCTelemetryTagsFlag.Name)
}
tcfg.SampleRatio = ctx.Float64(RPCTelemetrySampleRatioFlag.Name)
if tcfg.Endpoint != "" && !tcfg.Enabled {
log.Warn(fmt.Sprintf("OpenTelemetry endpoint configured but telemetry is not enabled, use --%s to enable.", RPCTelemetryFlag.Name))
}
}
func SetDataDir(ctx *cli.Context, cfg *node.Config) { func SetDataDir(ctx *cli.Context, cfg *node.Config) {
switch { switch {
case ctx.IsSet(DataDirFlag.Name): case ctx.IsSet(DataDirFlag.Name):
@ -1699,6 +1794,12 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) {
if ctx.IsSet(StateHistoryFlag.Name) { if ctx.IsSet(StateHistoryFlag.Name) {
cfg.StateHistory = ctx.Uint64(StateHistoryFlag.Name) cfg.StateHistory = ctx.Uint64(StateHistoryFlag.Name)
} }
if ctx.IsSet(TrienodeHistoryFlag.Name) {
cfg.TrienodeHistory = ctx.Int64(TrienodeHistoryFlag.Name)
}
if ctx.IsSet(TrienodeHistoryFullValueCheckpointFlag.Name) {
cfg.NodeFullValueCheckpoint = uint32(ctx.Uint(TrienodeHistoryFullValueCheckpointFlag.Name))
}
if ctx.IsSet(StateSchemeFlag.Name) { if ctx.IsSet(StateSchemeFlag.Name) {
cfg.StateScheme = ctx.String(StateSchemeFlag.Name) cfg.StateScheme = ctx.String(StateSchemeFlag.Name)
} }
@ -1753,6 +1854,9 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) {
if ctx.IsSet(RPCTxSyncMaxTimeoutFlag.Name) { if ctx.IsSet(RPCTxSyncMaxTimeoutFlag.Name) {
cfg.TxSyncMaxTimeout = ctx.Duration(RPCTxSyncMaxTimeoutFlag.Name) cfg.TxSyncMaxTimeout = ctx.Duration(RPCTxSyncMaxTimeoutFlag.Name)
} }
if ctx.IsSet(RPCGlobalRangeLimitFlag.Name) {
cfg.RangeLimit = ctx.Uint64(RPCGlobalRangeLimitFlag.Name)
}
if !ctx.Bool(SnapshotFlag.Name) || cfg.SnapshotCache == 0 { if !ctx.Bool(SnapshotFlag.Name) || cfg.SnapshotCache == 0 {
// If snap-sync is requested, this flag is also required // If snap-sync is requested, this flag is also required
if cfg.SyncMode == ethconfig.SnapSync { if cfg.SyncMode == ethconfig.SnapSync {
@ -2097,6 +2201,7 @@ func RegisterFilterAPI(stack *node.Node, backend ethapi.Backend, ethcfg *ethconf
filterSystem := filters.NewFilterSystem(backend, filters.Config{ filterSystem := filters.NewFilterSystem(backend, filters.Config{
LogCacheSize: ethcfg.FilterLogCacheSize, LogCacheSize: ethcfg.FilterLogCacheSize,
LogQueryLimit: ethcfg.LogQueryLimit, LogQueryLimit: ethcfg.LogQueryLimit,
RangeLimit: ethcfg.RangeLimit,
}) })
stack.RegisterAPIs([]rpc.API{{ stack.RegisterAPIs([]rpc.API{{
Namespace: "eth", Namespace: "eth",
@ -2299,15 +2404,17 @@ func MakeChain(ctx *cli.Context, stack *node.Node, readonly bool) (*core.BlockCh
Fatalf("%v", err) Fatalf("%v", err)
} }
options := &core.BlockChainConfig{ options := &core.BlockChainConfig{
TrieCleanLimit: ethconfig.Defaults.TrieCleanCache, TrieCleanLimit: ethconfig.Defaults.TrieCleanCache,
NoPrefetch: ctx.Bool(CacheNoPrefetchFlag.Name), NoPrefetch: ctx.Bool(CacheNoPrefetchFlag.Name),
TrieDirtyLimit: ethconfig.Defaults.TrieDirtyCache, TrieDirtyLimit: ethconfig.Defaults.TrieDirtyCache,
ArchiveMode: ctx.String(GCModeFlag.Name) == "archive", ArchiveMode: ctx.String(GCModeFlag.Name) == "archive",
TrieTimeLimit: ethconfig.Defaults.TrieTimeout, TrieTimeLimit: ethconfig.Defaults.TrieTimeout,
SnapshotLimit: ethconfig.Defaults.SnapshotCache, SnapshotLimit: ethconfig.Defaults.SnapshotCache,
Preimages: ctx.Bool(CachePreimagesFlag.Name), Preimages: ctx.Bool(CachePreimagesFlag.Name),
StateScheme: scheme, StateScheme: scheme,
StateHistory: ctx.Uint64(StateHistoryFlag.Name), StateHistory: ctx.Uint64(StateHistoryFlag.Name),
TrienodeHistory: ctx.Int64(TrienodeHistoryFlag.Name),
NodeFullValueCheckpoint: uint32(ctx.Uint(TrienodeHistoryFullValueCheckpointFlag.Name)),
// Disable transaction indexing/unindexing. // Disable transaction indexing/unindexing.
TxLookupLimit: -1, TxLookupLimit: -1,
@ -2321,8 +2428,12 @@ func MakeChain(ctx *cli.Context, stack *node.Node, readonly bool) (*core.BlockCh
// Enable state size tracking if enabled // Enable state size tracking if enabled
StateSizeTracking: ctx.Bool(StateSizeTrackingFlag.Name), StateSizeTracking: ctx.Bool(StateSizeTrackingFlag.Name),
// Configure the slow block statistic logger // Configure the slow block statistic logger (disabled by default)
SlowBlockThreshold: ctx.Duration(LogSlowBlockFlag.Name), SlowBlockThreshold: ethconfig.Defaults.SlowBlockThreshold,
}
// Only enable slow block logging if the flag was explicitly set
if ctx.IsSet(LogSlowBlockFlag.Name) {
options.SlowBlockThreshold = ctx.Duration(LogSlowBlockFlag.Name)
} }
if options.ArchiveMode && !options.Preimages { if options.ArchiveMode && !options.Preimages {
options.Preimages = true options.Preimages = true

View file

@ -33,6 +33,8 @@ import (
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/internal/era" "github.com/ethereum/go-ethereum/internal/era"
"github.com/ethereum/go-ethereum/internal/era/execdb"
"github.com/ethereum/go-ethereum/internal/era/onedb"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/trie" "github.com/ethereum/go-ethereum/trie"
"github.com/ethereum/go-ethereum/triedb" "github.com/ethereum/go-ethereum/triedb"
@ -44,136 +46,148 @@ var (
) )
func TestHistoryImportAndExport(t *testing.T) { func TestHistoryImportAndExport(t *testing.T) {
var ( for _, tt := range []struct {
key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") name string
address = crypto.PubkeyToAddress(key.PublicKey) builder func(io.Writer) era.Builder
genesis = &core.Genesis{ filename func(network string, epoch int, root common.Hash) string
Config: params.TestChainConfig, from func(f era.ReadAtSeekCloser) (era.Era, error)
Alloc: types.GenesisAlloc{address: {Balance: big.NewInt(1000000000000000000)}}, }{
} {"era1", onedb.NewBuilder, onedb.Filename, onedb.From},
signer = types.LatestSigner(genesis.Config) {"erae", execdb.NewBuilder, execdb.Filename, execdb.From},
) } {
t.Run(tt.name, func(t *testing.T) {
// Generate chain.
db, blocks, _ := core.GenerateChainWithGenesis(genesis, ethash.NewFaker(), int(count), func(i int, g *core.BlockGen) {
if i == 0 {
return
}
tx, err := types.SignNewTx(key, signer, &types.DynamicFeeTx{
ChainID: genesis.Config.ChainID,
Nonce: uint64(i - 1),
GasTipCap: common.Big0,
GasFeeCap: g.PrevBlock(0).BaseFee(),
Gas: 50000,
To: &common.Address{0xaa},
Value: big.NewInt(int64(i)),
Data: nil,
AccessList: nil,
})
if err != nil {
t.Fatalf("error creating tx: %v", err)
}
g.AddTx(tx)
})
// Initialize BlockChain.
chain, err := core.NewBlockChain(db, genesis, ethash.NewFaker(), nil)
if err != nil {
t.Fatalf("unable to initialize chain: %v", err)
}
if _, err := chain.InsertChain(blocks); err != nil {
t.Fatalf("error inserting chain: %v", err)
}
// Make temp directory for era files.
dir := t.TempDir()
// Export history to temp directory.
if err := ExportHistory(chain, dir, 0, count, step); err != nil {
t.Fatalf("error exporting history: %v", err)
}
// Read checksums.
b, err := os.ReadFile(filepath.Join(dir, "checksums.txt"))
if err != nil {
t.Fatalf("failed to read checksums: %v", err)
}
checksums := strings.Split(string(b), "\n")
// Verify each Era.
entries, _ := era.ReadDir(dir, "mainnet")
for i, filename := range entries {
func() {
f, err := os.Open(filepath.Join(dir, filename))
if err != nil {
t.Fatalf("error opening era file: %v", err)
}
var ( var (
h = sha256.New() key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
buf = bytes.NewBuffer(nil) address = crypto.PubkeyToAddress(key.PublicKey)
genesis = &core.Genesis{
Config: params.TestChainConfig,
Alloc: types.GenesisAlloc{address: {Balance: big.NewInt(1000000000000000000)}},
}
signer = types.LatestSigner(genesis.Config)
) )
if _, err := io.Copy(h, f); err != nil {
t.Fatalf("unable to recalculate checksum: %v", err) // Generate chain.
} db, blocks, _ := core.GenerateChainWithGenesis(genesis, ethash.NewFaker(), int(count), func(i int, g *core.BlockGen) {
if got, want := common.BytesToHash(h.Sum(buf.Bytes()[:])).Hex(), checksums[i]; got != want { if i == 0 {
t.Fatalf("checksum %d does not match: got %s, want %s", i, got, want) return
}
e, err := era.From(f)
if err != nil {
t.Fatalf("error opening era: %v", err)
}
defer e.Close()
it, err := era.NewIterator(e)
if err != nil {
t.Fatalf("error making era reader: %v", err)
}
for j := 0; it.Next(); j++ {
n := i*int(step) + j
if it.Error() != nil {
t.Fatalf("error reading block entry %d: %v", n, it.Error())
} }
block, receipts, err := it.BlockAndReceipts() tx, err := types.SignNewTx(key, signer, &types.DynamicFeeTx{
ChainID: genesis.Config.ChainID,
Nonce: uint64(i - 1),
GasTipCap: common.Big0,
GasFeeCap: g.PrevBlock(0).BaseFee(),
Gas: 50000,
To: &common.Address{0xaa},
Value: big.NewInt(int64(i)),
Data: nil,
AccessList: nil,
})
if err != nil { if err != nil {
t.Fatalf("error reading block entry %d: %v", n, err) t.Fatalf("error creating tx: %v", err)
}
want := chain.GetBlockByNumber(uint64(n))
if want, got := uint64(n), block.NumberU64(); want != got {
t.Fatalf("blocks out of order: want %d, got %d", want, got)
}
if want.Hash() != block.Hash() {
t.Fatalf("block hash mismatch %d: want %s, got %s", n, want.Hash().Hex(), block.Hash().Hex())
}
if got := types.DeriveSha(block.Transactions(), trie.NewStackTrie(nil)); got != want.TxHash() {
t.Fatalf("tx hash %d mismatch: want %s, got %s", n, want.TxHash(), got)
}
if got := types.CalcUncleHash(block.Uncles()); got != want.UncleHash() {
t.Fatalf("uncle hash %d mismatch: want %s, got %s", n, want.UncleHash(), got)
}
if got := types.DeriveSha(receipts, trie.NewStackTrie(nil)); got != want.ReceiptHash() {
t.Fatalf("receipt root %d mismatch: want %s, got %s", n, want.ReceiptHash(), got)
} }
g.AddTx(tx)
})
// Initialize BlockChain.
chain, err := core.NewBlockChain(db, genesis, ethash.NewFaker(), nil)
if err != nil {
t.Fatalf("unable to initialize chain: %v", err)
}
if _, err := chain.InsertChain(blocks); err != nil {
t.Fatalf("error inserting chain: %v", err)
} }
}()
}
// Now import Era. // Make temp directory for era files.
db2, err := rawdb.Open(rawdb.NewMemoryDatabase(), rawdb.OpenOptions{}) dir := t.TempDir()
if err != nil {
panic(err)
}
t.Cleanup(func() {
db2.Close()
})
genesis.MustCommit(db2, triedb.NewDatabase(db2, triedb.HashDefaults)) // Export history to temp directory.
imported, err := core.NewBlockChain(db2, genesis, ethash.NewFaker(), nil) if err := ExportHistory(chain, dir, 0, count, tt.builder, tt.filename); err != nil {
if err != nil { t.Fatalf("error exporting history: %v", err)
t.Fatalf("unable to initialize chain: %v", err) }
}
if err := ImportHistory(imported, dir, "mainnet"); err != nil { // Read checksums.
t.Fatalf("failed to import chain: %v", err) b, err := os.ReadFile(filepath.Join(dir, "checksums.txt"))
} if err != nil {
if have, want := imported.CurrentHeader(), chain.CurrentHeader(); have.Hash() != want.Hash() { t.Fatalf("failed to read checksums: %v", err)
t.Fatalf("imported chain does not match expected, have (%d, %s) want (%d, %s)", have.Number, have.Hash(), want.Number, want.Hash()) }
checksums := strings.Split(string(b), "\n")
// Verify each Era.
entries, _ := era.ReadDir(dir, "mainnet")
for i, filename := range entries {
func() {
f, err := os.Open(filepath.Join(dir, filename))
if err != nil {
t.Fatalf("error opening era file: %v", err)
}
var (
h = sha256.New()
buf = bytes.NewBuffer(nil)
)
if _, err := io.Copy(h, f); err != nil {
t.Fatalf("unable to recalculate checksum: %v", err)
}
if got, want := common.BytesToHash(h.Sum(buf.Bytes()[:])).Hex(), checksums[i]; got != want {
t.Fatalf("checksum %d does not match: got %s, want %s", i, got, want)
}
e, err := tt.from(f)
if err != nil {
t.Fatalf("error opening era: %v", err)
}
defer e.Close()
it, err := e.Iterator()
if err != nil {
t.Fatalf("error making era reader: %v", err)
}
for j := 0; it.Next(); j++ {
n := i*int(step) + j
if it.Error() != nil {
t.Fatalf("error reading block entry %d: %v", n, it.Error())
}
block, receipts, err := it.BlockAndReceipts()
if err != nil {
t.Fatalf("error reading block entry %d: %v", n, err)
}
want := chain.GetBlockByNumber(uint64(n))
if want, got := uint64(n), block.NumberU64(); want != got {
t.Fatalf("blocks out of order: want %d, got %d", want, got)
}
if want.Hash() != block.Hash() {
t.Fatalf("block hash mismatch %d: want %s, got %s", n, want.Hash().Hex(), block.Hash().Hex())
}
if got := types.DeriveSha(block.Transactions(), trie.NewStackTrie(nil)); got != want.TxHash() {
t.Fatalf("tx hash %d mismatch: want %s, got %s", n, want.TxHash(), got)
}
if got := types.CalcUncleHash(block.Uncles()); got != want.UncleHash() {
t.Fatalf("uncle hash %d mismatch: want %s, got %s", n, want.UncleHash(), got)
}
if got := types.DeriveSha(receipts, trie.NewStackTrie(nil)); got != want.ReceiptHash() {
t.Fatalf("receipt root %d mismatch: want %s, got %s", n, want.ReceiptHash(), got)
}
}
}()
}
// Now import Era.
db2, err := rawdb.Open(rawdb.NewMemoryDatabase(), rawdb.OpenOptions{})
if err != nil {
panic(err)
}
t.Cleanup(func() {
db2.Close()
})
genesis.MustCommit(db2, triedb.NewDatabase(db2, triedb.HashDefaults))
imported, err := core.NewBlockChain(db2, genesis, ethash.NewFaker(), nil)
if err != nil {
t.Fatalf("unable to initialize chain: %v", err)
}
if err := ImportHistory(imported, dir, "mainnet", tt.from); err != nil {
t.Fatalf("failed to import chain: %v", err)
}
if have, want := imported.CurrentHeader(), chain.CurrentHeader(); have.Hash() != want.Hash() {
t.Fatalf("imported chain does not match expected, have (%d, %s) want (%d, %s)", have.Number, have.Hash(), want.Number, want.Hash())
}
})
} }
} }

View file

@ -30,7 +30,7 @@ import (
"strings" "strings"
"github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/hexutil"
"golang.org/x/crypto/sha3" "github.com/ethereum/go-ethereum/crypto/keccak"
) )
// Lengths of hashes and addresses in bytes. // Lengths of hashes and addresses in bytes.
@ -271,7 +271,7 @@ func (a *Address) checksumHex() []byte {
buf := a.hex() buf := a.hex()
// compute checksum // compute checksum
sha := sha3.NewLegacyKeccak256() sha := keccak.NewLegacyKeccak256()
sha.Write(buf[2:]) sha.Write(buf[2:])
hash := sha.Sum(nil) hash := sha.Sum(nil)
for i := 2; i < len(buf); i++ { for i := 2; i < len(buf); i++ {

View file

@ -37,12 +37,12 @@ import (
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/crypto/keccak"
"github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/trie" "github.com/ethereum/go-ethereum/trie"
"golang.org/x/crypto/sha3"
) )
const ( const (
@ -642,7 +642,7 @@ func (c *Clique) Close() error {
// SealHash returns the hash of a block prior to it being sealed. // SealHash returns the hash of a block prior to it being sealed.
func SealHash(header *types.Header) (hash common.Hash) { func SealHash(header *types.Header) (hash common.Hash) {
hasher := sha3.NewLegacyKeccak256() hasher := keccak.NewLegacyKeccak256()
encodeSigHeader(hasher, header) encodeSigHeader(hasher, header)
hasher.(crypto.KeccakState).Read(hash[:]) hasher.(crypto.KeccakState).Read(hash[:])
return hash return hash

View file

@ -31,11 +31,11 @@ import (
"github.com/ethereum/go-ethereum/core/tracing" "github.com/ethereum/go-ethereum/core/tracing"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/crypto/keccak"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/trie" "github.com/ethereum/go-ethereum/trie"
"github.com/holiman/uint256" "github.com/holiman/uint256"
"golang.org/x/crypto/sha3"
) )
// Ethash proof-of-work protocol constants. // Ethash proof-of-work protocol constants.
@ -527,7 +527,7 @@ func (ethash *Ethash) FinalizeAndAssemble(chain consensus.ChainHeaderReader, hea
// SealHash returns the hash of a block prior to it being sealed. // SealHash returns the hash of a block prior to it being sealed.
func (ethash *Ethash) SealHash(header *types.Header) (hash common.Hash) { func (ethash *Ethash) SealHash(header *types.Header) (hash common.Hash) {
hasher := sha3.NewLegacyKeccak256() hasher := keccak.NewLegacyKeccak256()
enc := []interface{}{ enc := []interface{}{
header.ParentHash, header.ParentHash,

View file

@ -53,9 +53,9 @@ func (bc *BlobConfig) blobPrice(excessBlobGas uint64) *big.Int {
return new(big.Int).Mul(f, big.NewInt(params.BlobTxBlobGasPerBlob)) return new(big.Int).Mul(f, big.NewInt(params.BlobTxBlobGasPerBlob))
} }
func latestBlobConfig(cfg *params.ChainConfig, time uint64) *BlobConfig { func latestBlobConfig(cfg *params.ChainConfig, time uint64) (BlobConfig, error) {
if cfg.BlobScheduleConfig == nil { if cfg.BlobScheduleConfig == nil {
return nil return BlobConfig{}, errors.New("no blob config")
} }
var ( var (
london = cfg.LondonBlock london = cfg.LondonBlock
@ -80,14 +80,14 @@ func latestBlobConfig(cfg *params.ChainConfig, time uint64) *BlobConfig {
case cfg.IsCancun(london, time) && s.Cancun != nil: case cfg.IsCancun(london, time) && s.Cancun != nil:
bc = s.Cancun bc = s.Cancun
default: default:
return nil return BlobConfig{}, errors.New("no blob config")
} }
return &BlobConfig{ return BlobConfig{
Target: bc.Target, Target: bc.Target,
Max: bc.Max, Max: bc.Max,
UpdateFraction: bc.UpdateFraction, UpdateFraction: bc.UpdateFraction,
} }, nil
} }
// VerifyEIP4844Header verifies the presence of the excessBlobGas field and that // VerifyEIP4844Header verifies the presence of the excessBlobGas field and that
@ -98,8 +98,8 @@ func VerifyEIP4844Header(config *params.ChainConfig, parent, header *types.Heade
panic("bad header pair") panic("bad header pair")
} }
bcfg := latestBlobConfig(config, header.Time) bcfg, err := latestBlobConfig(config, header.Time)
if bcfg == nil { if err != nil {
panic("called before EIP-4844 is active") panic("called before EIP-4844 is active")
} }
@ -130,11 +130,14 @@ func VerifyEIP4844Header(config *params.ChainConfig, parent, header *types.Heade
// blobs on top of the excess blob gas. // blobs on top of the excess blob gas.
func CalcExcessBlobGas(config *params.ChainConfig, parent *types.Header, headTimestamp uint64) uint64 { func CalcExcessBlobGas(config *params.ChainConfig, parent *types.Header, headTimestamp uint64) uint64 {
isOsaka := config.IsOsaka(config.LondonBlock, headTimestamp) isOsaka := config.IsOsaka(config.LondonBlock, headTimestamp)
bcfg := latestBlobConfig(config, headTimestamp) bcfg, err := latestBlobConfig(config, headTimestamp)
if err != nil {
panic("calculating excess blob gas on nil blob config")
}
return calcExcessBlobGas(isOsaka, bcfg, parent) return calcExcessBlobGas(isOsaka, bcfg, parent)
} }
func calcExcessBlobGas(isOsaka bool, bcfg *BlobConfig, parent *types.Header) uint64 { func calcExcessBlobGas(isOsaka bool, bcfg BlobConfig, parent *types.Header) uint64 {
var parentExcessBlobGas, parentBlobGasUsed uint64 var parentExcessBlobGas, parentBlobGasUsed uint64
if parent.ExcessBlobGas != nil { if parent.ExcessBlobGas != nil {
parentExcessBlobGas = *parent.ExcessBlobGas parentExcessBlobGas = *parent.ExcessBlobGas
@ -169,8 +172,8 @@ func calcExcessBlobGas(isOsaka bool, bcfg *BlobConfig, parent *types.Header) uin
// CalcBlobFee calculates the blobfee from the header's excess blob gas field. // CalcBlobFee calculates the blobfee from the header's excess blob gas field.
func CalcBlobFee(config *params.ChainConfig, header *types.Header) *big.Int { func CalcBlobFee(config *params.ChainConfig, header *types.Header) *big.Int {
blobConfig := latestBlobConfig(config, header.Time) blobConfig, err := latestBlobConfig(config, header.Time)
if blobConfig == nil { if err != nil {
panic("calculating blob fee on unsupported fork") panic("calculating blob fee on unsupported fork")
} }
return blobConfig.blobBaseFee(*header.ExcessBlobGas) return blobConfig.blobBaseFee(*header.ExcessBlobGas)
@ -178,8 +181,8 @@ func CalcBlobFee(config *params.ChainConfig, header *types.Header) *big.Int {
// MaxBlobsPerBlock returns the max blobs per block for a block at the given timestamp. // MaxBlobsPerBlock returns the max blobs per block for a block at the given timestamp.
func MaxBlobsPerBlock(cfg *params.ChainConfig, time uint64) int { func MaxBlobsPerBlock(cfg *params.ChainConfig, time uint64) int {
blobConfig := latestBlobConfig(cfg, time) blobConfig, err := latestBlobConfig(cfg, time)
if blobConfig == nil { if err != nil {
return 0 return 0
} }
return blobConfig.Max return blobConfig.Max
@ -193,8 +196,8 @@ func MaxBlobGasPerBlock(cfg *params.ChainConfig, time uint64) uint64 {
// LatestMaxBlobsPerBlock returns the latest max blobs per block defined by the // LatestMaxBlobsPerBlock returns the latest max blobs per block defined by the
// configuration, regardless of the currently active fork. // configuration, regardless of the currently active fork.
func LatestMaxBlobsPerBlock(cfg *params.ChainConfig) int { func LatestMaxBlobsPerBlock(cfg *params.ChainConfig) int {
bcfg := latestBlobConfig(cfg, math.MaxUint64) bcfg, err := latestBlobConfig(cfg, math.MaxUint64)
if bcfg == nil { if err != nil {
return 0 return 0
} }
return bcfg.Max return bcfg.Max
@ -202,8 +205,8 @@ func LatestMaxBlobsPerBlock(cfg *params.ChainConfig) int {
// TargetBlobsPerBlock returns the target blobs per block for a block at the given timestamp. // TargetBlobsPerBlock returns the target blobs per block for a block at the given timestamp.
func TargetBlobsPerBlock(cfg *params.ChainConfig, time uint64) int { func TargetBlobsPerBlock(cfg *params.ChainConfig, time uint64) int {
blobConfig := latestBlobConfig(cfg, time) blobConfig, err := latestBlobConfig(cfg, time)
if blobConfig == nil { if err != nil {
return 0 return 0
} }
return blobConfig.Target return blobConfig.Target

View file

@ -78,6 +78,7 @@ var (
storageUpdateTimer = metrics.NewRegisteredResettingTimer("chain/storage/updates", nil) storageUpdateTimer = metrics.NewRegisteredResettingTimer("chain/storage/updates", nil)
storageCommitTimer = metrics.NewRegisteredResettingTimer("chain/storage/commits", nil) storageCommitTimer = metrics.NewRegisteredResettingTimer("chain/storage/commits", nil)
codeReadTimer = metrics.NewRegisteredResettingTimer("chain/code/reads", nil) codeReadTimer = metrics.NewRegisteredResettingTimer("chain/code/reads", nil)
codeReadBytesTimer = metrics.NewRegisteredResettingTimer("chain/code/readbytes", nil)
accountCacheHitMeter = metrics.NewRegisteredMeter("chain/account/reads/cache/process/hit", nil) accountCacheHitMeter = metrics.NewRegisteredMeter("chain/account/reads/cache/process/hit", nil)
accountCacheMissMeter = metrics.NewRegisteredMeter("chain/account/reads/cache/process/miss", nil) accountCacheMissMeter = metrics.NewRegisteredMeter("chain/account/reads/cache/process/miss", nil)
@ -179,6 +180,17 @@ type BlockChainConfig struct {
// If set to 0, all state histories across the entire chain will be retained; // If set to 0, all state histories across the entire chain will be retained;
StateHistory uint64 StateHistory uint64
// Number of blocks from the chain head for which trienode histories are retained.
// If set to 0, all trienode histories across the entire chain will be retained;
// If set to -1, no trienode history will be retained;
TrienodeHistory int64
// The frequency of full-value encoding. For example, a value of 16 means
// that, on average, for a given trie node across its 16 consecutive historical
// versions, only one version is stored in full format, while the others
// are stored in diff mode for storage compression.
NodeFullValueCheckpoint uint32
// State snapshot related options // State snapshot related options
SnapshotLimit int // Memory allowance (MB) to use for caching snapshot entries in memory SnapshotLimit int // Memory allowance (MB) to use for caching snapshot entries in memory
SnapshotNoBuild bool // Whether the background generation is allowed SnapshotNoBuild bool // Whether the background generation is allowed
@ -204,7 +216,8 @@ type BlockChainConfig struct {
StateSizeTracking bool StateSizeTracking bool
// SlowBlockThreshold is the block execution time threshold beyond which // SlowBlockThreshold is the block execution time threshold beyond which
// detailed statistics will be logged. // detailed statistics will be logged. Negative value means disabled (default),
// zero logs all blocks, positive value filters blocks by execution time.
SlowBlockThreshold time.Duration SlowBlockThreshold time.Duration
} }
@ -256,17 +269,22 @@ func (cfg *BlockChainConfig) triedbConfig(isVerkle bool) *triedb.Config {
} }
if cfg.StateScheme == rawdb.PathScheme { if cfg.StateScheme == rawdb.PathScheme {
config.PathDB = &pathdb.Config{ config.PathDB = &pathdb.Config{
StateHistory: cfg.StateHistory, TrieCleanSize: cfg.TrieCleanLimit * 1024 * 1024,
EnableStateIndexing: cfg.ArchiveMode, StateCleanSize: cfg.SnapshotLimit * 1024 * 1024,
TrieCleanSize: cfg.TrieCleanLimit * 1024 * 1024,
StateCleanSize: cfg.SnapshotLimit * 1024 * 1024,
JournalDirectory: cfg.TrieJournalDirectory,
// TODO(rjl493456442): The write buffer represents the memory limit used // TODO(rjl493456442): The write buffer represents the memory limit used
// for flushing both trie data and state data to disk. The config name // for flushing both trie data and state data to disk. The config name
// should be updated to eliminate the confusion. // should be updated to eliminate the confusion.
WriteBufferSize: cfg.TrieDirtyLimit * 1024 * 1024, WriteBufferSize: cfg.TrieDirtyLimit * 1024 * 1024,
NoAsyncFlush: cfg.TrieNoAsyncFlush, JournalDirectory: cfg.TrieJournalDirectory,
// Historical state configurations
StateHistory: cfg.StateHistory,
TrienodeHistory: cfg.TrienodeHistory,
EnableStateIndexing: cfg.ArchiveMode,
FullValueCheckpoint: cfg.NodeFullValueCheckpoint,
// Testing configurations
NoAsyncFlush: cfg.TrieNoAsyncFlush,
} }
} }
return config return config
@ -2091,24 +2109,11 @@ func (bc *BlockChain) ProcessBlock(ctx context.Context, parentRoot common.Hash,
} }
// Upload the statistics of reader at the end // Upload the statistics of reader at the end
defer func() { defer func() {
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 { if result != nil {
result.stats.StatePrefetchCacheStats = pStat result.stats.StatePrefetchCacheStats = prefetch.GetStats()
result.stats.StateReadCacheStats = rStat result.stats.StateReadCacheStats = process.GetStats()
} }
}() }()
go func(start time.Time, throwaway *state.StateDB, block *types.Block) { go func(start time.Time, throwaway *state.StateDB, block *types.Block) {
// Disable tracing for prefetcher executions. // Disable tracing for prefetcher executions.
vmCfg := bc.cfg.VmConfig vmCfg := bc.cfg.VmConfig
@ -2228,7 +2233,11 @@ func (bc *BlockChain) ProcessBlock(ctx context.Context, parentRoot common.Hash,
stats.StorageLoaded = statedb.StorageLoaded stats.StorageLoaded = statedb.StorageLoaded
stats.StorageUpdated = int(statedb.StorageUpdated.Load()) stats.StorageUpdated = int(statedb.StorageUpdated.Load())
stats.StorageDeleted = int(statedb.StorageDeleted.Load()) stats.StorageDeleted = int(statedb.StorageDeleted.Load())
stats.CodeLoaded = statedb.CodeLoaded stats.CodeLoaded = statedb.CodeLoaded
stats.CodeLoadBytes = statedb.CodeLoadBytes
stats.CodeUpdated = statedb.CodeUpdated
stats.CodeUpdateBytes = statedb.CodeUpdateBytes
stats.Execution = ptime - (statedb.AccountReads + statedb.StorageReads + statedb.CodeReads) // The time spent on EVM processing 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.Validation = vtime - (statedb.AccountHashes + statedb.AccountUpdates + statedb.StorageUpdates) // The time spent on block validation

View file

@ -17,8 +17,7 @@
package core package core
import ( import (
"fmt" "encoding/json"
"strings"
"time" "time"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
@ -39,13 +38,16 @@ type ExecuteStats struct {
StorageCommits time.Duration // Time spent on the storage trie commit StorageCommits time.Duration // Time spent on the storage trie commit
CodeReads time.Duration // Time spent on the contract code read CodeReads time.Duration // Time spent on the contract code read
AccountLoaded int // Number of accounts loaded AccountLoaded int // Number of accounts loaded
AccountUpdated int // Number of accounts updated AccountUpdated int // Number of accounts updated
AccountDeleted int // Number of accounts deleted AccountDeleted int // Number of accounts deleted
StorageLoaded int // Number of storage slots loaded StorageLoaded int // Number of storage slots loaded
StorageUpdated int // Number of storage slots updated StorageUpdated int // Number of storage slots updated
StorageDeleted int // Number of storage slots deleted StorageDeleted int // Number of storage slots deleted
CodeLoaded int // Number of contract code loaded CodeLoaded int // Number of contract code loaded
CodeLoadBytes int // Number of bytes read from contract code
CodeUpdated int // Number of contract code written (CREATE/CREATE2 + EIP-7702)
CodeUpdateBytes int // Total bytes of code written
Execution time.Duration // Time spent on the EVM execution Execution time.Duration // Time spent on the EVM execution
Validation time.Duration // Time spent on the block validation Validation time.Duration // Time spent on the block validation
@ -74,6 +76,7 @@ func (s *ExecuteStats) reportMetrics() {
if s.CodeLoaded != 0 { if s.CodeLoaded != 0 {
codeReadTimer.Update(s.CodeReads) codeReadTimer.Update(s.CodeReads)
codeReadSingleTimer.Update(s.CodeReads / time.Duration(s.CodeLoaded)) codeReadSingleTimer.Update(s.CodeReads / time.Duration(s.CodeLoaded))
codeReadBytesTimer.Update(time.Duration(s.CodeLoadBytes))
} }
accountUpdateTimer.Update(s.AccountUpdates) // Account updates are complete(in validation) accountUpdateTimer.Update(s.AccountUpdates) // Account updates are complete(in validation)
storageUpdateTimer.Update(s.StorageUpdates) // Storage updates are complete(in validation) storageUpdateTimer.Update(s.StorageUpdates) // Storage updates are complete(in validation)
@ -102,64 +105,161 @@ func (s *ExecuteStats) reportMetrics() {
storageCacheMissMeter.Mark(s.StateReadCacheStats.StorageCacheMiss) storageCacheMissMeter.Mark(s.StateReadCacheStats.StorageCacheMiss)
} }
// logSlow prints the detailed execution statistics if the block is regarded as slow. // slowBlockLog represents the JSON structure for slow block logging.
func (s *ExecuteStats) logSlow(block *types.Block, slowBlockThreshold time.Duration) { // This format is designed for cross-client compatibility with other
if slowBlockThreshold == 0 { // Ethereum execution clients (reth, Besu, Nethermind).
return type slowBlockLog struct {
} Level string `json:"level"`
if s.TotalTime < slowBlockThreshold { Msg string `json:"msg"`
return Block slowBlockInfo `json:"block"`
} Timing slowBlockTime `json:"timing"`
msg := fmt.Sprintf(` Throughput slowBlockThru `json:"throughput"`
########## SLOW BLOCK ######### StateReads slowBlockReads `json:"state_reads"`
Block: %v (%#x) txs: %d, mgasps: %.2f, elapsed: %v StateWrites slowBlockWrites `json:"state_writes"`
Cache slowBlockCache `json:"cache"`
EVM execution: %v }
Validation: %v type slowBlockInfo struct {
Account hash: %v Number uint64 `json:"number"`
Storage hash: %v Hash common.Hash `json:"hash"`
GasUsed uint64 `json:"gas_used"`
State read: %v TxCount int `json:"tx_count"`
Account read: %v(%d) }
Storage read: %v(%d)
Code read: %v(%d) type slowBlockTime struct {
ExecutionMs float64 `json:"execution_ms"`
State write: %v StateReadMs float64 `json:"state_read_ms"`
Trie commit: %v StateHashMs float64 `json:"state_hash_ms"`
State write: %v CommitMs float64 `json:"commit_ms"`
Block write: %v TotalMs float64 `json:"total_ms"`
}
%s
############################## type slowBlockThru struct {
`, block.Number(), block.Hash(), len(block.Transactions()), s.MgasPerSecond, common.PrettyDuration(s.TotalTime), MgasPerSec float64 `json:"mgas_per_sec"`
// EVM execution }
common.PrettyDuration(s.Execution),
type slowBlockReads struct {
// Block validation Accounts int `json:"accounts"`
common.PrettyDuration(s.Validation+s.CrossValidation+s.AccountHashes+s.AccountUpdates+s.StorageUpdates), StorageSlots int `json:"storage_slots"`
common.PrettyDuration(s.AccountHashes+s.AccountUpdates), Code int `json:"code"`
common.PrettyDuration(s.StorageUpdates), CodeBytes int `json:"code_bytes"`
}
// State read
common.PrettyDuration(s.AccountReads+s.StorageReads+s.CodeReads), type slowBlockWrites struct {
common.PrettyDuration(s.AccountReads), s.AccountLoaded, Accounts int `json:"accounts"`
common.PrettyDuration(s.StorageReads), s.StorageLoaded, AccountsDeleted int `json:"accounts_deleted"`
common.PrettyDuration(s.CodeReads), s.CodeLoaded, StorageSlots int `json:"storage_slots"`
StorageSlotsDeleted int `json:"storage_slots_deleted"`
// State write Code int `json:"code"`
common.PrettyDuration(max(s.AccountCommits, s.StorageCommits)+s.TrieDBCommit+s.SnapshotCommit+s.BlockWrite), CodeBytes int `json:"code_bytes"`
common.PrettyDuration(max(s.AccountCommits, s.StorageCommits)), }
common.PrettyDuration(s.TrieDBCommit+s.SnapshotCommit),
common.PrettyDuration(s.BlockWrite), // slowBlockCache represents cache hit/miss statistics for cross-client analysis.
type slowBlockCache struct {
// cache statistics Account slowBlockCacheEntry `json:"account"`
s.StateReadCacheStats, Storage slowBlockCacheEntry `json:"storage"`
) Code slowBlockCodeCacheEntry `json:"code"`
for _, line := range strings.Split(msg, "\n") { }
if line == "" {
continue // slowBlockCacheEntry represents cache statistics for account/storage caches.
} type slowBlockCacheEntry struct {
log.Info(line) Hits int64 `json:"hits"`
} Misses int64 `json:"misses"`
HitRate float64 `json:"hit_rate"`
}
// slowBlockCodeCacheEntry represents cache statistics for code cache with byte-level granularity.
type slowBlockCodeCacheEntry struct {
Hits int64 `json:"hits"`
Misses int64 `json:"misses"`
HitRate float64 `json:"hit_rate"`
HitBytes int64 `json:"hit_bytes"`
MissBytes int64 `json:"miss_bytes"`
}
// calculateHitRate computes the cache hit rate as a percentage (0-100).
func calculateHitRate(hits, misses int64) float64 {
if total := hits + misses; total > 0 {
return float64(hits) / float64(total) * 100.0
}
return 0.0
}
// durationToMs converts a time.Duration to milliseconds as a float64
// with sub-millisecond precision for accurate cross-client metrics.
func durationToMs(d time.Duration) float64 {
return float64(d.Nanoseconds()) / 1e6
}
// logSlow prints the detailed execution statistics in JSON format if the block
// is regarded as slow. The JSON format is designed for cross-client compatibility
// with other Ethereum execution clients.
func (s *ExecuteStats) logSlow(block *types.Block, slowBlockThreshold time.Duration) {
// Negative threshold means disabled (default when flag not set)
if slowBlockThreshold < 0 {
return
}
// Threshold of 0 logs all blocks; positive threshold filters
if slowBlockThreshold > 0 && s.TotalTime < slowBlockThreshold {
return
}
logEntry := slowBlockLog{
Level: "warn",
Msg: "Slow block",
Block: slowBlockInfo{
Number: block.NumberU64(),
Hash: block.Hash(),
GasUsed: block.GasUsed(),
TxCount: len(block.Transactions()),
},
Timing: slowBlockTime{
ExecutionMs: durationToMs(s.Execution),
StateReadMs: durationToMs(s.AccountReads + s.StorageReads + s.CodeReads),
StateHashMs: durationToMs(s.AccountHashes + s.AccountUpdates + s.StorageUpdates),
CommitMs: durationToMs(max(s.AccountCommits, s.StorageCommits) + s.TrieDBCommit + s.SnapshotCommit + s.BlockWrite),
TotalMs: durationToMs(s.TotalTime),
},
Throughput: slowBlockThru{
MgasPerSec: s.MgasPerSecond,
},
StateReads: slowBlockReads{
Accounts: s.AccountLoaded,
StorageSlots: s.StorageLoaded,
Code: s.CodeLoaded,
CodeBytes: s.CodeLoadBytes,
},
StateWrites: slowBlockWrites{
Accounts: s.AccountUpdated,
AccountsDeleted: s.AccountDeleted,
StorageSlots: s.StorageUpdated,
StorageSlotsDeleted: s.StorageDeleted,
Code: s.CodeUpdated,
CodeBytes: s.CodeUpdateBytes,
},
Cache: slowBlockCache{
Account: slowBlockCacheEntry{
Hits: s.StateReadCacheStats.AccountCacheHit,
Misses: s.StateReadCacheStats.AccountCacheMiss,
HitRate: calculateHitRate(s.StateReadCacheStats.AccountCacheHit, s.StateReadCacheStats.AccountCacheMiss),
},
Storage: slowBlockCacheEntry{
Hits: s.StateReadCacheStats.StorageCacheHit,
Misses: s.StateReadCacheStats.StorageCacheMiss,
HitRate: calculateHitRate(s.StateReadCacheStats.StorageCacheHit, s.StateReadCacheStats.StorageCacheMiss),
},
Code: slowBlockCodeCacheEntry{
Hits: s.StateReadCacheStats.CodeStats.CacheHit,
Misses: s.StateReadCacheStats.CodeStats.CacheMiss,
HitRate: calculateHitRate(s.StateReadCacheStats.CodeStats.CacheHit, s.StateReadCacheStats.CodeStats.CacheMiss),
HitBytes: s.StateReadCacheStats.CodeStats.CacheHitBytes,
MissBytes: s.StateReadCacheStats.CodeStats.CacheMissBytes,
},
},
}
jsonBytes, err := json.Marshal(logEntry)
if err != nil {
log.Error("Failed to marshal slow block log", "error", err)
return
}
log.Warn(string(jsonBytes))
} }

View file

@ -80,12 +80,9 @@ func NewEVMBlockContext(header *types.Header, chain ChainContext, author *common
func NewEVMTxContext(msg *Message) vm.TxContext { func NewEVMTxContext(msg *Message) vm.TxContext {
ctx := vm.TxContext{ ctx := vm.TxContext{
Origin: msg.From, Origin: msg.From,
GasPrice: new(big.Int).Set(msg.GasPrice), GasPrice: uint256.MustFromBig(msg.GasPrice),
BlobHashes: msg.BlobHashes, BlobHashes: msg.BlobHashes,
} }
if msg.BlobGasFeeCap != nil {
ctx.BlobFeeCap = new(big.Int).Set(msg.BlobGasFeeCap)
}
return ctx return ctx
} }

View file

@ -308,7 +308,7 @@ func TestVerkleGenesisCommit(t *testing.T) {
}, },
} }
expected := common.FromHex("19056b480530799a4fdaa9fd9407043b965a3a5c37b4d2a1a9a4f3395a327561") expected := common.FromHex("b94812c1674dcf4f2bc98f4503d15f4cc674265135bcf3be6e4417b60881042a")
got := genesis.ToBlock().Root().Bytes() got := genesis.ToBlock().Root().Bytes()
if !bytes.Equal(got, expected) { if !bytes.Equal(got, expected) {
t.Fatalf("invalid genesis state root, expected %x, got %x", expected, got) t.Fatalf("invalid genesis state root, expected %x, got %x", expected, got)

View file

@ -424,7 +424,13 @@ func WriteBodyRLP(db ethdb.KeyValueWriter, hash common.Hash, number uint64, rlp
// HasBody verifies the existence of a block body corresponding to the hash. // HasBody verifies the existence of a block body corresponding to the hash.
func HasBody(db ethdb.Reader, hash common.Hash, number uint64) bool { func HasBody(db ethdb.Reader, hash common.Hash, number uint64) bool {
if isCanon(db, number, hash) { if isCanon(db, number, hash) {
return true // Block is in ancient store, but bodies can be pruned.
// Check if the block number is above the pruning tail.
tail, _ := db.Tail()
if number >= tail {
return true
}
return false
} }
if has, err := db.Has(blockBodyKey(number, hash)); !has || err != nil { if has, err := db.Has(blockBodyKey(number, hash)); !has || err != nil {
return false return false
@ -466,7 +472,13 @@ func DeleteBody(db ethdb.KeyValueWriter, hash common.Hash, number uint64) {
// to a block. // to a block.
func HasReceipts(db ethdb.Reader, hash common.Hash, number uint64) bool { func HasReceipts(db ethdb.Reader, hash common.Hash, number uint64) bool {
if isCanon(db, number, hash) { if isCanon(db, number, hash) {
return true // Block is in ancient store, but receipts can be pruned.
// Check if the block number is above the pruning tail.
tail, _ := db.Tail()
if number >= tail {
return true
}
return false
} }
if has, err := db.Has(blockReceiptsKey(number, hash)); !has || err != nil { if has, err := db.Has(blockReceiptsKey(number, hash)); !has || err != nil {
return false return false

View file

@ -28,9 +28,9 @@ import (
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/crypto/keccak"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rlp"
"golang.org/x/crypto/sha3"
) )
// Tests block header storage and retrieval operations. // Tests block header storage and retrieval operations.
@ -52,10 +52,7 @@ func TestHeaderStorage(t *testing.T) {
if entry := ReadHeaderRLP(db, header.Hash(), header.Number.Uint64()); entry == nil { if entry := ReadHeaderRLP(db, header.Hash(), header.Number.Uint64()); entry == nil {
t.Fatalf("Stored header RLP not found") t.Fatalf("Stored header RLP not found")
} else { } else {
hasher := sha3.NewLegacyKeccak256() if hash := crypto.Keccak256Hash(entry); hash != header.Hash() {
hasher.Write(entry)
if hash := common.BytesToHash(hasher.Sum(nil)); hash != header.Hash() {
t.Fatalf("Retrieved RLP header mismatch: have %v, want %v", entry, header) t.Fatalf("Retrieved RLP header mismatch: have %v, want %v", entry, header)
} }
} }
@ -72,8 +69,7 @@ func TestBodyStorage(t *testing.T) {
// Create a test body to move around the database and make sure it's really new // Create a test body to move around the database and make sure it's really new
body := &types.Body{Uncles: []*types.Header{{Extra: []byte("test header")}}} body := &types.Body{Uncles: []*types.Header{{Extra: []byte("test header")}}}
hasher := keccak.NewLegacyKeccak256()
hasher := sha3.NewLegacyKeccak256()
rlp.Encode(hasher, body) rlp.Encode(hasher, body)
hash := common.BytesToHash(hasher.Sum(nil)) hash := common.BytesToHash(hasher.Sum(nil))
@ -90,10 +86,7 @@ func TestBodyStorage(t *testing.T) {
if entry := ReadBodyRLP(db, hash, 0); entry == nil { if entry := ReadBodyRLP(db, hash, 0); entry == nil {
t.Fatalf("Stored body RLP not found") t.Fatalf("Stored body RLP not found")
} else { } else {
hasher := sha3.NewLegacyKeccak256() if calc := crypto.Keccak256Hash(entry); calc != hash {
hasher.Write(entry)
if calc := common.BytesToHash(hasher.Sum(nil)); calc != hash {
t.Fatalf("Retrieved RLP body mismatch: have %v, want %v", entry, body) t.Fatalf("Retrieved RLP body mismatch: have %v, want %v", entry, body)
} }
} }

View file

@ -147,9 +147,6 @@ func findTxInBlockBody(blockbody rlp.RawValue, target common.Hash) (*types.Trans
} }
txIndex := uint64(0) txIndex := uint64(0)
for iter.Next() { for iter.Next() {
if iter.Err() != nil {
return nil, 0, iter.Err()
}
// The preimage for the hash calculation of legacy transactions // The preimage for the hash calculation of legacy transactions
// is just their RLP encoding. For typed (EIP-2718) transactions, // is just their RLP encoding. For typed (EIP-2718) transactions,
// which are encoded as byte arrays, the preimage is the content of // which are encoded as byte arrays, the preimage is the content of
@ -171,6 +168,9 @@ func findTxInBlockBody(blockbody rlp.RawValue, target common.Hash) (*types.Trans
} }
txIndex++ txIndex++
} }
if iter.Err() != nil {
return nil, 0, iter.Err()
}
return nil, 0, errors.New("transaction not found") return nil, 0, errors.New("transaction not found")
} }

View file

@ -166,6 +166,7 @@ func InspectFreezerTable(ancient string, freezerName string, tableName string, s
if err != nil { if err != nil {
return err return err
} }
defer table.Close()
table.dumpIndexStdout(start, end) table.dumpIndexStdout(start, end)
return nil return nil
} }

View file

@ -153,9 +153,9 @@ func iterateTransactions(db ethdb.Database, from uint64, to uint64, reverse bool
err: err, err: err,
} }
} else { } else {
var hashes []common.Hash hashes := make([]common.Hash, len(body.Transactions))
for _, tx := range body.Transactions { for i, tx := range body.Transactions {
hashes = append(hashes, tx.Hash()) hashes[i] = tx.Hash()
} }
result = &blockTxHashes{ result = &blockTxHashes{
hashes: hashes, hashes: hashes,
@ -357,9 +357,9 @@ func unindexTransactions(db ethdb.Database, from uint64, to uint64, interrupt ch
} }
select { select {
case <-interrupt: case <-interrupt:
logger("Transaction unindexing interrupted", "blocks", blocks, "txs", txs, "tail", to, "elapsed", common.PrettyDuration(time.Since(start))) logger("Transaction unindexing interrupted", "blocks", blocks, "txs", txs, "tail", nextNum, "elapsed", common.PrettyDuration(time.Since(start)))
default: default:
logger("Unindexed transactions", "blocks", blocks, "txs", txs, "tail", to, "elapsed", common.PrettyDuration(time.Since(start))) logger("Unindexed transactions", "blocks", blocks, "txs", txs, "tail", nextNum, "elapsed", common.PrettyDuration(time.Since(start)))
} }
} }

View file

@ -429,7 +429,8 @@ func InspectDatabase(db ethdb.Database, keyPrefix, keyStart []byte) error {
filterMapBlockLV stat filterMapBlockLV stat
// Path-mode archive data // Path-mode archive data
stateIndex stat stateIndex stat
trienodeIndex stat
// Verkle statistics // Verkle statistics
verkleTries stat verkleTries stat
@ -524,8 +525,19 @@ func InspectDatabase(db ethdb.Database, keyPrefix, keyStart []byte) error {
bloomBits.add(size) bloomBits.add(size)
// Path-based historic state indexes // Path-based historic state indexes
case bytes.HasPrefix(key, StateHistoryIndexPrefix) && len(key) >= len(StateHistoryIndexPrefix)+common.HashLength: case bytes.HasPrefix(key, StateHistoryAccountMetadataPrefix) && len(key) == len(StateHistoryAccountMetadataPrefix)+common.HashLength:
stateIndex.add(size) stateIndex.add(size)
case bytes.HasPrefix(key, StateHistoryStorageMetadataPrefix) && len(key) == len(StateHistoryStorageMetadataPrefix)+2*common.HashLength:
stateIndex.add(size)
case bytes.HasPrefix(key, StateHistoryAccountBlockPrefix) && len(key) == len(StateHistoryAccountBlockPrefix)+common.HashLength+4:
stateIndex.add(size)
case bytes.HasPrefix(key, StateHistoryStorageBlockPrefix) && len(key) == len(StateHistoryStorageBlockPrefix)+2*common.HashLength+4:
stateIndex.add(size)
case bytes.HasPrefix(key, TrienodeHistoryMetadataPrefix) && len(key) >= len(TrienodeHistoryMetadataPrefix)+common.HashLength:
trienodeIndex.add(size)
case bytes.HasPrefix(key, TrienodeHistoryBlockPrefix) && len(key) >= len(TrienodeHistoryBlockPrefix)+common.HashLength+4:
trienodeIndex.add(size)
// Verkle trie data is detected, determine the sub-category // Verkle trie data is detected, determine the sub-category
case bytes.HasPrefix(key, VerklePrefix): case bytes.HasPrefix(key, VerklePrefix):
@ -622,12 +634,13 @@ func InspectDatabase(db ethdb.Database, keyPrefix, keyStart []byte) error {
{"Key-Value store", "Path trie state lookups", stateLookups.sizeString(), stateLookups.countString()}, {"Key-Value store", "Path trie state lookups", stateLookups.sizeString(), stateLookups.countString()},
{"Key-Value store", "Path trie account nodes", accountTries.sizeString(), accountTries.countString()}, {"Key-Value store", "Path trie account nodes", accountTries.sizeString(), accountTries.countString()},
{"Key-Value store", "Path trie storage nodes", storageTries.sizeString(), storageTries.countString()}, {"Key-Value store", "Path trie storage nodes", storageTries.sizeString(), storageTries.countString()},
{"Key-Value store", "Path state history indexes", stateIndex.sizeString(), stateIndex.countString()},
{"Key-Value store", "Verkle trie nodes", verkleTries.sizeString(), verkleTries.countString()}, {"Key-Value store", "Verkle trie nodes", verkleTries.sizeString(), verkleTries.countString()},
{"Key-Value store", "Verkle trie state lookups", verkleStateLookups.sizeString(), verkleStateLookups.countString()}, {"Key-Value store", "Verkle trie state lookups", verkleStateLookups.sizeString(), verkleStateLookups.countString()},
{"Key-Value store", "Trie preimages", preimages.sizeString(), preimages.countString()}, {"Key-Value store", "Trie preimages", preimages.sizeString(), preimages.countString()},
{"Key-Value store", "Account snapshot", accountSnaps.sizeString(), accountSnaps.countString()}, {"Key-Value store", "Account snapshot", accountSnaps.sizeString(), accountSnaps.countString()},
{"Key-Value store", "Storage snapshot", storageSnaps.sizeString(), storageSnaps.countString()}, {"Key-Value store", "Storage snapshot", storageSnaps.sizeString(), storageSnaps.countString()},
{"Key-Value store", "Historical state index", stateIndex.sizeString(), stateIndex.countString()},
{"Key-Value store", "Historical trie index", trienodeIndex.sizeString(), trienodeIndex.countString()},
{"Key-Value store", "Beacon sync headers", beaconHeaders.sizeString(), beaconHeaders.countString()}, {"Key-Value store", "Beacon sync headers", beaconHeaders.sizeString(), beaconHeaders.countString()},
{"Key-Value store", "Clique snapshots", cliqueSnaps.sizeString(), cliqueSnaps.countString()}, {"Key-Value store", "Clique snapshots", cliqueSnaps.sizeString(), cliqueSnaps.countString()},
{"Key-Value store", "Singleton metadata", metadata.sizeString(), metadata.countString()}, {"Key-Value store", "Singleton metadata", metadata.sizeString(), metadata.countString()},
@ -672,7 +685,7 @@ var knownMetadataKeys = [][]byte{
snapshotGeneratorKey, snapshotRecoveryKey, txIndexTailKey, fastTxLookupLimitKey, snapshotGeneratorKey, snapshotRecoveryKey, txIndexTailKey, fastTxLookupLimitKey,
uncleanShutdownKey, badBlockKey, transitionStatusKey, skeletonSyncStatusKey, uncleanShutdownKey, badBlockKey, transitionStatusKey, skeletonSyncStatusKey,
persistentStateIDKey, trieJournalKey, snapshotSyncStatusKey, snapSyncStatusFlagKey, persistentStateIDKey, trieJournalKey, snapshotSyncStatusKey, snapSyncStatusFlagKey,
filterMapsRangeKey, headStateHistoryIndexKey, VerkleTransitionStatePrefix, filterMapsRangeKey, headStateHistoryIndexKey, headTrienodeHistoryIndexKey, VerkleTransitionStatePrefix,
} }
// printChainMetadata prints out chain metadata to stderr. // printChainMetadata prints out chain metadata to stderr.

View file

@ -27,6 +27,7 @@ import (
"github.com/ethereum/go-ethereum/common/lru" "github.com/ethereum/go-ethereum/common/lru"
"github.com/ethereum/go-ethereum/internal/era" "github.com/ethereum/go-ethereum/internal/era"
"github.com/ethereum/go-ethereum/internal/era/onedb"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rlp"
) )
@ -51,7 +52,7 @@ type Store struct {
type fileCacheEntry struct { type fileCacheEntry struct {
refcount int // reference count. This is protected by Store.mu! refcount int // reference count. This is protected by Store.mu!
opened chan struct{} // signals opening of file has completed opened chan struct{} // signals opening of file has completed
file *era.Era // the file file *onedb.Era // the file
err error // error from opening the file err error // error from opening the file
} }
@ -102,7 +103,7 @@ func (db *Store) Close() {
// GetRawBody returns the raw body for a given block number. // GetRawBody returns the raw body for a given block number.
func (db *Store) GetRawBody(number uint64) ([]byte, error) { func (db *Store) GetRawBody(number uint64) ([]byte, error) {
epoch := number / uint64(era.MaxEra1Size) epoch := number / uint64(era.MaxSize)
entry := db.getEraByEpoch(epoch) entry := db.getEraByEpoch(epoch)
if entry.err != nil { if entry.err != nil {
if errors.Is(entry.err, fs.ErrNotExist) { if errors.Is(entry.err, fs.ErrNotExist) {
@ -117,7 +118,7 @@ func (db *Store) GetRawBody(number uint64) ([]byte, error) {
// GetRawReceipts returns the raw receipts for a given block number. // GetRawReceipts returns the raw receipts for a given block number.
func (db *Store) GetRawReceipts(number uint64) ([]byte, error) { func (db *Store) GetRawReceipts(number uint64) ([]byte, error) {
epoch := number / uint64(era.MaxEra1Size) epoch := number / uint64(era.MaxSize)
entry := db.getEraByEpoch(epoch) entry := db.getEraByEpoch(epoch)
if entry.err != nil { if entry.err != nil {
if errors.Is(entry.err, fs.ErrNotExist) { if errors.Is(entry.err, fs.ErrNotExist) {
@ -249,7 +250,7 @@ func (db *Store) getCacheEntry(epoch uint64) (stat fileCacheStatus, entry *fileC
} }
// fileOpened is called after an era file has been successfully opened. // fileOpened is called after an era file has been successfully opened.
func (db *Store) fileOpened(epoch uint64, entry *fileCacheEntry, file *era.Era) { func (db *Store) fileOpened(epoch uint64, entry *fileCacheEntry, file *onedb.Era) {
db.mu.Lock() db.mu.Lock()
defer db.mu.Unlock() defer db.mu.Unlock()
@ -282,7 +283,7 @@ func (db *Store) fileFailedToOpen(epoch uint64, entry *fileCacheEntry, err error
entry.err = err entry.err = err
} }
func (db *Store) openEraFile(epoch uint64) (*era.Era, error) { func (db *Store) openEraFile(epoch uint64) (*onedb.Era, error) {
// File name scheme is <network>-<epoch>-<root>. // File name scheme is <network>-<epoch>-<root>.
glob := fmt.Sprintf("*-%05d-*.era1", epoch) glob := fmt.Sprintf("*-%05d-*.era1", epoch)
matches, err := filepath.Glob(filepath.Join(db.datadir, glob)) matches, err := filepath.Glob(filepath.Join(db.datadir, glob))
@ -297,17 +298,17 @@ func (db *Store) openEraFile(epoch uint64) (*era.Era, error) {
} }
filename := matches[0] filename := matches[0]
e, err := era.Open(filename) e, err := onedb.Open(filename)
if err != nil { if err != nil {
return nil, err return nil, err
} }
// Sanity-check start block. // Sanity-check start block.
if e.Start()%uint64(era.MaxEra1Size) != 0 { if e.Start()%uint64(era.MaxSize) != 0 {
e.Close() e.Close()
return nil, fmt.Errorf("pre-merge era1 file has invalid boundary. %d %% %d != 0", e.Start(), era.MaxEra1Size) return nil, fmt.Errorf("pre-merge era1 file has invalid boundary. %d %% %d != 0", e.Start(), era.MaxSize)
} }
log.Debug("Opened era1 file", "epoch", epoch) log.Debug("Opened era1 file", "epoch", epoch)
return e, nil return e.(*onedb.Era), nil
} }
// doneWithFile signals that the caller has finished using a file. // doneWithFile signals that the caller has finished using a file.

View file

@ -221,13 +221,12 @@ func cleanup(path string) error {
if err != nil { if err != nil {
return err return err
} }
defer dir.Close()
names, err := dir.Readdirnames(0) names, err := dir.Readdirnames(0)
if err != nil { if err != nil {
return err return err
} }
if cerr := dir.Close(); cerr != nil {
return cerr
}
for _, name := range names { for _, name := range names {
if name == filepath.Base(path)+tmpSuffix { if name == filepath.Base(path)+tmpSuffix {
log.Info("Removed leftover freezer directory", "name", name) log.Info("Removed leftover freezer directory", "name", name)

View file

@ -25,9 +25,9 @@ import (
"github.com/ethereum/go-ethereum/consensus/ethash" "github.com/ethereum/go-ethereum/consensus/ethash"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/crypto/keccak"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rlp"
"golang.org/x/crypto/sha3"
) )
func getBlock(transactions int, uncles int, dataSize int) *types.Block { func getBlock(transactions int, uncles int, dataSize int) *types.Block {
@ -147,7 +147,7 @@ func BenchmarkHashing(b *testing.B) {
blockRlp, _ = rlp.EncodeToBytes(block) blockRlp, _ = rlp.EncodeToBytes(block)
} }
var got common.Hash var got common.Hash
var hasher = sha3.NewLegacyKeccak256() var hasher = keccak.NewLegacyKeccak256()
b.Run("iteratorhashing", func(b *testing.B) { b.Run("iteratorhashing", func(b *testing.B) {
for b.Loop() { for b.Loop() {
var hash common.Hash var hash common.Hash

View file

@ -17,40 +17,39 @@
package state package state
import ( import (
"errors" "fmt"
"sync"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/lru" "github.com/ethereum/go-ethereum/common/lru"
"github.com/ethereum/go-ethereum/core/state/snapshot" "github.com/ethereum/go-ethereum/core/state/snapshot"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/trie"
"github.com/ethereum/go-ethereum/triedb" "github.com/ethereum/go-ethereum/triedb"
"github.com/ethereum/go-ethereum/triedb/database"
"github.com/ethereum/go-ethereum/triedb/pathdb" "github.com/ethereum/go-ethereum/triedb/pathdb"
) )
// historicReader wraps a historical state reader defined in path database, // historicStateReader implements StateReader, wrapping a historical state reader
// providing historic state serving over the path scheme. // defined in path database and provide historic state serving over the path scheme.
// type historicStateReader struct {
// TODO(rjl493456442): historicReader is not thread-safe and does not fully
// comply with the StateReader interface requirements, needs to be fixed.
// Currently, it is only used in a non-concurrent context, so it is safe for now.
type historicReader struct {
reader *pathdb.HistoricalStateReader reader *pathdb.HistoricalStateReader
lock sync.Mutex // Lock for protecting concurrent read
} }
// newHistoricReader constructs a reader for historic state serving. // newHistoricStateReader constructs a reader for historical state serving.
func newHistoricReader(r *pathdb.HistoricalStateReader) *historicReader { func newHistoricStateReader(r *pathdb.HistoricalStateReader) *historicStateReader {
return &historicReader{reader: r} return &historicStateReader{reader: r}
} }
// Account implements StateReader, retrieving the account specified by the address. // Account implements StateReader, retrieving the account specified by the address.
// func (r *historicStateReader) Account(addr common.Address) (*types.StateAccount, error) {
// An error will be returned if the associated snapshot is already stale or r.lock.Lock()
// the requested account is not yet covered by the snapshot. defer r.lock.Unlock()
//
// The returned account might be nil if it's not existent.
func (r *historicReader) Account(addr common.Address) (*types.StateAccount, error) {
account, err := r.reader.Account(addr) account, err := r.reader.Account(addr)
if err != nil { if err != nil {
return nil, err return nil, err
@ -80,7 +79,10 @@ func (r *historicReader) Account(addr common.Address) (*types.StateAccount, erro
// the requested storage slot is not yet covered by the snapshot. // the requested storage slot is not yet covered by the snapshot.
// //
// The returned storage slot might be empty if it's not existent. // The returned storage slot might be empty if it's not existent.
func (r *historicReader) Storage(addr common.Address, key common.Hash) (common.Hash, error) { func (r *historicStateReader) Storage(addr common.Address, key common.Hash) (common.Hash, error) {
r.lock.Lock()
defer r.lock.Unlock()
blob, err := r.reader.Storage(addr, key) blob, err := r.reader.Storage(addr, key)
if err != nil { if err != nil {
return common.Hash{}, err return common.Hash{}, err
@ -97,6 +99,125 @@ func (r *historicReader) Storage(addr common.Address, key common.Hash) (common.H
return slot, nil return slot, nil
} }
// historicTrieOpener is a wrapper of pathdb.HistoricalNodeReader, implementing
// the database.NodeDatabase by adding NodeReader function.
type historicTrieOpener struct {
root common.Hash
reader *pathdb.HistoricalNodeReader
}
// newHistoricTrieOpener constructs the historical trie opener.
func newHistoricTrieOpener(root common.Hash, reader *pathdb.HistoricalNodeReader) *historicTrieOpener {
return &historicTrieOpener{
root: root,
reader: reader,
}
}
// NodeReader implements database.NodeDatabase, returning a node reader of a
// specified state.
func (o *historicTrieOpener) NodeReader(root common.Hash) (database.NodeReader, error) {
if root != o.root {
return nil, fmt.Errorf("state %x is not available", root)
}
return o.reader, nil
}
// historicalTrieReader wraps a historical node reader defined in path database,
// providing historical node serving over the path scheme.
type historicalTrieReader struct {
root common.Hash
opener *historicTrieOpener
tr Trie
subRoots map[common.Address]common.Hash // Set of storage roots, cached when the account is resolved
subTries map[common.Address]Trie // Group of storage tries, cached when it's resolved
lock sync.Mutex // Lock for protecting concurrent read
}
// newHistoricalTrieReader constructs a reader for historical trie node serving.
func newHistoricalTrieReader(root common.Hash, r *pathdb.HistoricalNodeReader) (*historicalTrieReader, error) {
opener := newHistoricTrieOpener(root, r)
tr, err := trie.NewStateTrie(trie.StateTrieID(root), opener)
if err != nil {
return nil, err
}
return &historicalTrieReader{
root: root,
opener: opener,
tr: tr,
subRoots: make(map[common.Address]common.Hash),
subTries: make(map[common.Address]Trie),
}, nil
}
// account is the inner version of Account and assumes the r.lock is already held.
func (r *historicalTrieReader) account(addr common.Address) (*types.StateAccount, error) {
account, err := r.tr.GetAccount(addr)
if err != nil {
return nil, err
}
if account == nil {
r.subRoots[addr] = types.EmptyRootHash
} else {
r.subRoots[addr] = account.Root
}
return account, nil
}
// Account implements StateReader, retrieving the account specified by the address.
//
// An error will be returned if the associated snapshot is already stale or
// the requested account is not yet covered by the snapshot.
//
// The returned account might be nil if it's not existent.
func (r *historicalTrieReader) Account(addr common.Address) (*types.StateAccount, error) {
r.lock.Lock()
defer r.lock.Unlock()
return r.account(addr)
}
// Storage implements StateReader, retrieving the storage slot specified by the
// address and slot key.
//
// An error will be returned if the associated snapshot is already stale or
// the requested storage slot is not yet covered by the snapshot.
//
// The returned storage slot might be empty if it's not existent.
func (r *historicalTrieReader) Storage(addr common.Address, key common.Hash) (common.Hash, error) {
r.lock.Lock()
defer r.lock.Unlock()
tr, found := r.subTries[addr]
if !found {
root, ok := r.subRoots[addr]
// The storage slot is accessed without account caching. It's unexpected
// behavior but try to resolve the account first anyway.
if !ok {
_, err := r.account(addr)
if err != nil {
return common.Hash{}, err
}
root = r.subRoots[addr]
}
var err error
tr, err = trie.NewStateTrie(trie.StorageTrieID(r.root, crypto.Keccak256Hash(addr.Bytes()), root), r.opener)
if err != nil {
return common.Hash{}, err
}
r.subTries[addr] = tr
}
ret, err := tr.GetStorage(addr, key.Bytes())
if err != nil {
return common.Hash{}, err
}
var value common.Hash
value.SetBytes(ret)
return value, nil
}
// HistoricDB is the implementation of Database interface, with the ability to // HistoricDB is the implementation of Database interface, with the ability to
// access historical state. // access historical state.
type HistoricDB struct { type HistoricDB struct {
@ -118,22 +239,54 @@ func NewHistoricDatabase(disk ethdb.KeyValueStore, triedb *triedb.Database) *His
// Reader implements Database interface, returning a reader of the specific state. // Reader implements Database interface, returning a reader of the specific state.
func (db *HistoricDB) Reader(stateRoot common.Hash) (Reader, error) { func (db *HistoricDB) Reader(stateRoot common.Hash) (Reader, error) {
hr, err := db.triedb.HistoricReader(stateRoot) var readers []StateReader
sr, err := db.triedb.HistoricStateReader(stateRoot)
if err == nil {
readers = append(readers, newHistoricStateReader(sr))
}
nr, err := db.triedb.HistoricNodeReader(stateRoot)
if err == nil {
tr, err := newHistoricalTrieReader(stateRoot, nr)
if err == nil {
readers = append(readers, tr)
}
}
if len(readers) == 0 {
return nil, fmt.Errorf("historical state %x is not available", stateRoot)
}
combined, err := newMultiStateReader(readers...)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return newReader(newCachingCodeReader(db.disk, db.codeCache, db.codeSizeCache), newHistoricReader(hr)), nil return newReader(newCachingCodeReader(db.disk, db.codeCache, db.codeSizeCache), combined), nil
} }
// OpenTrie opens the main account trie. It's not supported by historic database. // OpenTrie opens the main account trie. It's not supported by historic database.
func (db *HistoricDB) OpenTrie(root common.Hash) (Trie, error) { func (db *HistoricDB) OpenTrie(root common.Hash) (Trie, error) {
return nil, errors.New("not implemented") nr, err := db.triedb.HistoricNodeReader(root)
if err != nil {
return nil, err
}
tr, err := trie.NewStateTrie(trie.StateTrieID(root), newHistoricTrieOpener(root, nr))
if err != nil {
return nil, err
}
return tr, nil
} }
// OpenStorageTrie opens the storage trie of an account. It's not supported by // OpenStorageTrie opens the storage trie of an account. It's not supported by
// historic database. // historic database.
func (db *HistoricDB) OpenStorageTrie(stateRoot common.Hash, address common.Address, root common.Hash, trie Trie) (Trie, error) { func (db *HistoricDB) OpenStorageTrie(stateRoot common.Hash, address common.Address, root common.Hash, _ Trie) (Trie, error) {
return nil, errors.New("not implemented") nr, err := db.triedb.HistoricNodeReader(stateRoot)
if err != nil {
return nil, err
}
id := trie.StorageTrieID(stateRoot, crypto.Keccak256Hash(address.Bytes()), root)
tr, err := trie.NewStateTrie(id, newHistoricTrieOpener(stateRoot, nr))
if err != nil {
return nil, err
}
return tr, nil
} }
// TrieDB returns the underlying trie database for managing trie nodes. // TrieDB returns the underlying trie database for managing trie nodes.

View file

@ -39,7 +39,8 @@ type journalEntry interface {
revert(*StateDB) revert(*StateDB)
// dirtied returns the Ethereum address modified by this journal entry. // dirtied returns the Ethereum address modified by this journal entry.
dirtied() *common.Address // indicates false if no address was changed.
dirtied() (common.Address, bool)
// copy returns a deep-copied journal entry. // copy returns a deep-copied journal entry.
copy() journalEntry copy() journalEntry
@ -100,8 +101,8 @@ func (j *journal) revertToSnapshot(revid int, s *StateDB) {
// append inserts a new modification entry to the end of the change journal. // append inserts a new modification entry to the end of the change journal.
func (j *journal) append(entry journalEntry) { func (j *journal) append(entry journalEntry) {
j.entries = append(j.entries, entry) j.entries = append(j.entries, entry)
if addr := entry.dirtied(); addr != nil { if addr, dirty := entry.dirtied(); dirty {
j.dirties[*addr]++ j.dirties[addr]++
} }
} }
@ -113,9 +114,9 @@ func (j *journal) revert(statedb *StateDB, snapshot int) {
j.entries[i].revert(statedb) j.entries[i].revert(statedb)
// Drop any dirty tracking induced by the change // Drop any dirty tracking induced by the change
if addr := j.entries[i].dirtied(); addr != nil { if addr, dirty := j.entries[i].dirtied(); dirty {
if j.dirties[*addr]--; j.dirties[*addr] == 0 { if j.dirties[addr]--; j.dirties[addr] == 0 {
delete(j.dirties, *addr) delete(j.dirties, addr)
} }
} }
} }
@ -294,8 +295,8 @@ func (ch createObjectChange) revert(s *StateDB) {
delete(s.stateObjects, ch.account) delete(s.stateObjects, ch.account)
} }
func (ch createObjectChange) dirtied() *common.Address { func (ch createObjectChange) dirtied() (common.Address, bool) {
return &ch.account return ch.account, true
} }
func (ch createObjectChange) copy() journalEntry { func (ch createObjectChange) copy() journalEntry {
@ -308,8 +309,8 @@ func (ch createContractChange) revert(s *StateDB) {
s.getStateObject(ch.account).newContract = false s.getStateObject(ch.account).newContract = false
} }
func (ch createContractChange) dirtied() *common.Address { func (ch createContractChange) dirtied() (common.Address, bool) {
return nil return common.Address{}, false
} }
func (ch createContractChange) copy() journalEntry { func (ch createContractChange) copy() journalEntry {
@ -325,8 +326,8 @@ func (ch selfDestructChange) revert(s *StateDB) {
} }
} }
func (ch selfDestructChange) dirtied() *common.Address { func (ch selfDestructChange) dirtied() (common.Address, bool) {
return &ch.account return ch.account, true
} }
func (ch selfDestructChange) copy() journalEntry { func (ch selfDestructChange) copy() journalEntry {
@ -340,8 +341,8 @@ var ripemd = common.HexToAddress("0000000000000000000000000000000000000003")
func (ch touchChange) revert(s *StateDB) { func (ch touchChange) revert(s *StateDB) {
} }
func (ch touchChange) dirtied() *common.Address { func (ch touchChange) dirtied() (common.Address, bool) {
return &ch.account return ch.account, true
} }
func (ch touchChange) copy() journalEntry { func (ch touchChange) copy() journalEntry {
@ -354,8 +355,8 @@ func (ch balanceChange) revert(s *StateDB) {
s.getStateObject(ch.account).setBalance(ch.prev) s.getStateObject(ch.account).setBalance(ch.prev)
} }
func (ch balanceChange) dirtied() *common.Address { func (ch balanceChange) dirtied() (common.Address, bool) {
return &ch.account return ch.account, true
} }
func (ch balanceChange) copy() journalEntry { func (ch balanceChange) copy() journalEntry {
@ -369,8 +370,8 @@ func (ch nonceChange) revert(s *StateDB) {
s.getStateObject(ch.account).setNonce(ch.prev) s.getStateObject(ch.account).setNonce(ch.prev)
} }
func (ch nonceChange) dirtied() *common.Address { func (ch nonceChange) dirtied() (common.Address, bool) {
return &ch.account return ch.account, true
} }
func (ch nonceChange) copy() journalEntry { func (ch nonceChange) copy() journalEntry {
@ -384,8 +385,8 @@ func (ch codeChange) revert(s *StateDB) {
s.getStateObject(ch.account).setCode(crypto.Keccak256Hash(ch.prevCode), ch.prevCode) s.getStateObject(ch.account).setCode(crypto.Keccak256Hash(ch.prevCode), ch.prevCode)
} }
func (ch codeChange) dirtied() *common.Address { func (ch codeChange) dirtied() (common.Address, bool) {
return &ch.account return ch.account, true
} }
func (ch codeChange) copy() journalEntry { func (ch codeChange) copy() journalEntry {
@ -399,8 +400,8 @@ func (ch storageChange) revert(s *StateDB) {
s.getStateObject(ch.account).setState(ch.key, ch.prevvalue, ch.origvalue) s.getStateObject(ch.account).setState(ch.key, ch.prevvalue, ch.origvalue)
} }
func (ch storageChange) dirtied() *common.Address { func (ch storageChange) dirtied() (common.Address, bool) {
return &ch.account return ch.account, true
} }
func (ch storageChange) copy() journalEntry { func (ch storageChange) copy() journalEntry {
@ -416,8 +417,8 @@ func (ch transientStorageChange) revert(s *StateDB) {
s.setTransientState(ch.account, ch.key, ch.prevalue) s.setTransientState(ch.account, ch.key, ch.prevalue)
} }
func (ch transientStorageChange) dirtied() *common.Address { func (ch transientStorageChange) dirtied() (common.Address, bool) {
return nil return common.Address{}, false
} }
func (ch transientStorageChange) copy() journalEntry { func (ch transientStorageChange) copy() journalEntry {
@ -432,8 +433,8 @@ func (ch refundChange) revert(s *StateDB) {
s.refund = ch.prev s.refund = ch.prev
} }
func (ch refundChange) dirtied() *common.Address { func (ch refundChange) dirtied() (common.Address, bool) {
return nil return common.Address{}, false
} }
func (ch refundChange) copy() journalEntry { func (ch refundChange) copy() journalEntry {
@ -452,8 +453,8 @@ func (ch addLogChange) revert(s *StateDB) {
s.logSize-- s.logSize--
} }
func (ch addLogChange) dirtied() *common.Address { func (ch addLogChange) dirtied() (common.Address, bool) {
return nil return common.Address{}, false
} }
func (ch addLogChange) copy() journalEntry { func (ch addLogChange) copy() journalEntry {
@ -475,8 +476,8 @@ func (ch accessListAddAccountChange) revert(s *StateDB) {
s.accessList.DeleteAddress(ch.address) s.accessList.DeleteAddress(ch.address)
} }
func (ch accessListAddAccountChange) dirtied() *common.Address { func (ch accessListAddAccountChange) dirtied() (common.Address, bool) {
return nil return common.Address{}, false
} }
func (ch accessListAddAccountChange) copy() journalEntry { func (ch accessListAddAccountChange) copy() journalEntry {
@ -489,8 +490,8 @@ func (ch accessListAddSlotChange) revert(s *StateDB) {
s.accessList.DeleteSlot(ch.address, ch.slot) s.accessList.DeleteSlot(ch.address, ch.slot)
} }
func (ch accessListAddSlotChange) dirtied() *common.Address { func (ch accessListAddSlotChange) dirtied() (common.Address, bool) {
return nil return common.Address{}, false
} }
func (ch accessListAddSlotChange) copy() journalEntry { func (ch accessListAddSlotChange) copy() journalEntry {

View file

@ -58,11 +58,28 @@ type ContractCodeReader interface {
CodeSize(addr common.Address, codeHash common.Hash) (int, error) CodeSize(addr common.Address, codeHash common.Hash) (int, error)
} }
// ContractCodeReaderStats aggregates statistics for the contract code reader.
type ContractCodeReaderStats struct {
CacheHit int64 // Number of cache hits
CacheMiss int64 // Number of cache misses
CacheHitBytes int64 // Total bytes served from cache
CacheMissBytes int64 // Total bytes read on cache misses
}
// HitRate returns the cache hit rate.
func (s ContractCodeReaderStats) HitRate() float64 {
if s.CacheHit == 0 {
return 0
}
return float64(s.CacheHit) / float64(s.CacheHit+s.CacheMiss)
}
// ContractCodeReaderWithStats extends ContractCodeReader by adding GetStats to // ContractCodeReaderWithStats extends ContractCodeReader by adding GetStats to
// expose statistics of code reader. // expose statistics of code reader.
type ContractCodeReaderWithStats interface { type ContractCodeReaderWithStats interface {
ContractCodeReader ContractCodeReader
GetStats() (int64, int64)
GetStats() ContractCodeReaderStats
} }
// StateReader defines the interface for accessing accounts and storage slots // StateReader defines the interface for accessing accounts and storage slots
@ -99,13 +116,11 @@ type Reader interface {
// ReaderStats wraps the statistics of reader. // ReaderStats wraps the statistics of reader.
type ReaderStats struct { type ReaderStats struct {
// Cache stats
AccountCacheHit int64 AccountCacheHit int64
AccountCacheMiss int64 AccountCacheMiss int64
StorageCacheHit int64 StorageCacheHit int64
StorageCacheMiss int64 StorageCacheMiss int64
ContractCodeHit int64 CodeStats ContractCodeReaderStats
ContractCodeMiss int64
} }
// String implements fmt.Stringer, returning string format statistics. // String implements fmt.Stringer, returning string format statistics.
@ -113,7 +128,6 @@ func (s ReaderStats) String() string {
var ( var (
accountCacheHitRate float64 accountCacheHitRate float64
storageCacheHitRate float64 storageCacheHitRate float64
contractCodeHitRate float64
) )
if s.AccountCacheHit > 0 { if s.AccountCacheHit > 0 {
accountCacheHitRate = float64(s.AccountCacheHit) / float64(s.AccountCacheHit+s.AccountCacheMiss) * 100 accountCacheHitRate = float64(s.AccountCacheHit) / float64(s.AccountCacheHit+s.AccountCacheMiss) * 100
@ -121,13 +135,10 @@ func (s ReaderStats) String() string {
if s.StorageCacheHit > 0 { if s.StorageCacheHit > 0 {
storageCacheHitRate = float64(s.StorageCacheHit) / float64(s.StorageCacheHit+s.StorageCacheMiss) * 100 storageCacheHitRate = float64(s.StorageCacheHit) / float64(s.StorageCacheHit+s.StorageCacheMiss) * 100
} }
if s.ContractCodeHit > 0 {
contractCodeHitRate = float64(s.ContractCodeHit) / float64(s.ContractCodeHit+s.ContractCodeMiss) * 100
}
msg := fmt.Sprintf("Reader statistics\n") 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("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) msg += fmt.Sprintf("storage: hit: %d, miss: %d, rate: %.2f\n", s.StorageCacheHit, s.StorageCacheMiss, storageCacheHitRate)
msg += fmt.Sprintf("code: hit: %d, miss: %d, rate: %.2f\n", s.ContractCodeHit, s.ContractCodeMiss, contractCodeHitRate) msg += fmt.Sprintf("code: hit: %d(%v), miss: %d(%v), rate: %.2f\n", s.CodeStats.CacheHit, common.StorageSize(s.CodeStats.CacheHitBytes), s.CodeStats.CacheMiss, common.StorageSize(s.CodeStats.CacheMissBytes), s.CodeStats.HitRate())
return msg return msg
} }
@ -150,8 +161,10 @@ type cachingCodeReader struct {
codeSizeCache *lru.Cache[common.Hash, int] codeSizeCache *lru.Cache[common.Hash, int]
// Cache statistics // Cache statistics
hit atomic.Int64 // Number of code lookups found in the cache. hit atomic.Int64 // Number of code lookups found in the cache
miss atomic.Int64 // Number of code lookups not found in the cache. miss atomic.Int64 // Number of code lookups not found in the cache
hitBytes atomic.Int64 // Total number of bytes read from cache
missBytes atomic.Int64 // Total number of bytes read from database
} }
// newCachingCodeReader constructs the code reader. // newCachingCodeReader constructs the code reader.
@ -169,6 +182,7 @@ func (r *cachingCodeReader) Code(addr common.Address, codeHash common.Hash) ([]b
code, _ := r.codeCache.Get(codeHash) code, _ := r.codeCache.Get(codeHash)
if len(code) > 0 { if len(code) > 0 {
r.hit.Add(1) r.hit.Add(1)
r.hitBytes.Add(int64(len(code)))
return code, nil return code, nil
} }
r.miss.Add(1) r.miss.Add(1)
@ -177,6 +191,7 @@ func (r *cachingCodeReader) Code(addr common.Address, codeHash common.Hash) ([]b
if len(code) > 0 { if len(code) > 0 {
r.codeCache.Add(codeHash, code) r.codeCache.Add(codeHash, code)
r.codeSizeCache.Add(codeHash, len(code)) r.codeSizeCache.Add(codeHash, len(code))
r.missBytes.Add(int64(len(code)))
} }
return code, nil return code, nil
} }
@ -202,9 +217,14 @@ func (r *cachingCodeReader) Has(addr common.Address, codeHash common.Hash) bool
return len(code) > 0 return len(code) > 0
} }
// GetStats returns the cache statistics fo the code reader. // GetStats returns the statistics of the code reader.
func (r *cachingCodeReader) GetStats() (int64, int64) { func (r *cachingCodeReader) GetStats() ContractCodeReaderStats {
return r.hit.Load(), r.miss.Load() return ContractCodeReaderStats{
CacheHit: r.hit.Load(),
CacheMiss: r.miss.Load(),
CacheHitBytes: r.hitBytes.Load(),
CacheMissBytes: r.missBytes.Load(),
}
} }
// flatReader wraps a database state reader and is safe for concurrent access. // flatReader wraps a database state reader and is safe for concurrent access.
@ -654,13 +674,11 @@ func (r *readerWithStats) Storage(addr common.Address, slot common.Hash) (common
// GetStats implements ReaderWithStats, returning the statistics of state reader. // GetStats implements ReaderWithStats, returning the statistics of state reader.
func (r *readerWithStats) GetStats() ReaderStats { func (r *readerWithStats) GetStats() ReaderStats {
codeHit, codeMiss := r.ContractCodeReaderWithStats.GetStats()
return ReaderStats{ return ReaderStats{
AccountCacheHit: r.accountCacheHit.Load(), AccountCacheHit: r.accountCacheHit.Load(),
AccountCacheMiss: r.accountCacheMiss.Load(), AccountCacheMiss: r.accountCacheMiss.Load(),
StorageCacheHit: r.storageCacheHit.Load(), StorageCacheHit: r.storageCacheHit.Load(),
StorageCacheMiss: r.storageCacheMiss.Load(), StorageCacheMiss: r.storageCacheMiss.Load(),
ContractCodeHit: codeHit, CodeStats: r.ContractCodeReaderWithStats.GetStats(),
ContractCodeMiss: codeMiss,
} }
} }

View file

@ -25,6 +25,7 @@ import (
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rlp"
@ -34,16 +35,10 @@ import (
"github.com/ethereum/go-ethereum/triedb/hashdb" "github.com/ethereum/go-ethereum/triedb/hashdb"
"github.com/ethereum/go-ethereum/triedb/pathdb" "github.com/ethereum/go-ethereum/triedb/pathdb"
"github.com/holiman/uint256" "github.com/holiman/uint256"
"golang.org/x/crypto/sha3"
) )
func hashData(input []byte) common.Hash { func hashData(input []byte) common.Hash {
var hasher = sha3.NewLegacyKeccak256() return crypto.Keccak256Hash(input)
var hash common.Hash
hasher.Reset()
hasher.Write(input)
hasher.Sum(hash[:0])
return hash
} }
// Tests that snapshot generation from an empty database. // Tests that snapshot generation from an empty database.

View file

@ -48,11 +48,11 @@ func (s Storage) Copy() Storage {
// - Account values as well as storages can be accessed and modified through the object. // - Account values as well as storages can be accessed and modified through the object.
// - Finally, call commit to return the changes of storage trie and update account data. // - Finally, call commit to return the changes of storage trie and update account data.
type stateObject struct { type stateObject struct {
db *StateDB db *StateDB
address common.Address // address of ethereum account address common.Address // address of ethereum account
addrHash common.Hash // hash of ethereum address of the account addressHash *common.Hash // hash of ethereum address of the account
origin *types.StateAccount // Account original data without any change applied, nil means it was not existent origin *types.StateAccount // Account original data without any change applied, nil means it was not existent
data types.StateAccount // Account data with all mutations applied in the scope of block data types.StateAccount // Account data with all mutations applied in the scope of block
// Write caches. // Write caches.
trie Trie // storage trie, which becomes non-nil on first access trie Trie // storage trie, which becomes non-nil on first access
@ -102,7 +102,6 @@ func newObject(db *StateDB, address common.Address, acct *types.StateAccount) *s
return &stateObject{ return &stateObject{
db: db, db: db,
address: address, address: address,
addrHash: crypto.Keccak256Hash(address[:]),
origin: origin, origin: origin,
data: *acct, data: *acct,
originStorage: make(Storage), originStorage: make(Storage),
@ -112,6 +111,14 @@ func newObject(db *StateDB, address common.Address, acct *types.StateAccount) *s
} }
} }
func (s *stateObject) addrHash() common.Hash {
if s.addressHash == nil {
h := crypto.Keccak256Hash(s.address[:])
s.addressHash = &h
}
return *s.addressHash
}
func (s *stateObject) markSelfdestructed() { func (s *stateObject) markSelfdestructed() {
s.selfDestructed = true s.selfDestructed = true
} }
@ -151,7 +158,7 @@ func (s *stateObject) getPrefetchedTrie() Trie {
return nil return nil
} }
// Attempt to retrieve the trie from the prefetcher // Attempt to retrieve the trie from the prefetcher
return s.db.prefetcher.trie(s.addrHash, s.data.Root) return s.db.prefetcher.trie(s.addrHash(), s.data.Root)
} }
// GetState retrieves a value associated with the given storage key. // GetState retrieves a value associated with the given storage key.
@ -203,7 +210,7 @@ func (s *stateObject) GetCommittedState(key common.Hash) common.Hash {
// Schedule the resolved storage slots for prefetching if it's enabled. // Schedule the resolved storage slots for prefetching if it's enabled.
if s.db.prefetcher != nil && s.data.Root != types.EmptyRootHash { if s.db.prefetcher != nil && s.data.Root != types.EmptyRootHash {
if err = s.db.prefetcher.prefetch(s.addrHash, s.origin.Root, s.address, nil, []common.Hash{key}, true); err != nil { if err = s.db.prefetcher.prefetch(s.addrHash(), s.origin.Root, s.address, nil, []common.Hash{key}, true); err != nil {
log.Error("Failed to prefetch storage slot", "addr", s.address, "key", key, "err", err) log.Error("Failed to prefetch storage slot", "addr", s.address, "key", key, "err", err)
} }
} }
@ -264,7 +271,7 @@ func (s *stateObject) finalise() {
s.pendingStorage[key] = value s.pendingStorage[key] = value
} }
if s.db.prefetcher != nil && len(slotsToPrefetch) > 0 && s.data.Root != types.EmptyRootHash { if s.db.prefetcher != nil && len(slotsToPrefetch) > 0 && s.data.Root != types.EmptyRootHash {
if err := s.db.prefetcher.prefetch(s.addrHash, s.data.Root, s.address, nil, slotsToPrefetch, false); err != nil { if err := s.db.prefetcher.prefetch(s.addrHash(), s.data.Root, s.address, nil, slotsToPrefetch, false); err != nil {
log.Error("Failed to prefetch slots", "addr", s.address, "slots", len(slotsToPrefetch), "err", err) log.Error("Failed to prefetch slots", "addr", s.address, "slots", len(slotsToPrefetch), "err", err)
} }
} }
@ -359,7 +366,7 @@ func (s *stateObject) updateTrie() (Trie, error) {
s.db.StorageDeleted.Add(1) s.db.StorageDeleted.Add(1)
} }
if s.db.prefetcher != nil { if s.db.prefetcher != nil {
s.db.prefetcher.used(s.addrHash, s.data.Root, nil, used) s.db.prefetcher.used(s.addrHash(), s.data.Root, nil, used)
} }
s.uncommittedStorage = make(Storage) // empties the commit markers s.uncommittedStorage = make(Storage) // empties the commit markers
return tr, nil return tr, nil
@ -491,7 +498,7 @@ func (s *stateObject) deepCopy(db *StateDB) *stateObject {
obj := &stateObject{ obj := &stateObject{
db: db, db: db,
address: s.address, address: s.address,
addrHash: s.addrHash, addressHash: nil,
origin: s.origin, origin: s.origin,
data: s.data, data: s.data,
code: s.code, code: s.code,
@ -541,6 +548,7 @@ func (s *stateObject) Code() []byte {
defer func(start time.Time) { defer func(start time.Time) {
s.db.CodeLoaded += 1 s.db.CodeLoaded += 1
s.db.CodeReads += time.Since(start) s.db.CodeReads += time.Since(start)
s.db.CodeLoadBytes += len(s.code)
}(time.Now()) }(time.Now())
code, err := s.db.reader.Code(s.address, common.BytesToHash(s.CodeHash())) code, err := s.db.reader.Code(s.address, common.BytesToHash(s.CodeHash()))

View file

@ -346,7 +346,7 @@ func (t *SizeTracker) run() {
// Evict the stale statistics // Evict the stale statistics
heap.Push(&h, stats[u.root]) heap.Push(&h, stats[u.root])
for u.blockNumber-h[0].BlockNumber > statEvictThreshold { for len(h) > 0 && u.blockNumber-h[0].BlockNumber > statEvictThreshold {
delete(stats, h[0].StateRoot) delete(stats, h[0].StateRoot)
heap.Pop(&h) heap.Pop(&h)
} }

View file

@ -158,7 +158,15 @@ type StateDB struct {
StorageLoaded int // Number of storage slots retrieved from the database during the state transition 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 StorageUpdated atomic.Int64 // Number of storage slots updated during the state transition
StorageDeleted atomic.Int64 // Number of storage slots deleted 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
// CodeLoadBytes is the total number of bytes read from contract code.
// This value may be smaller than the actual number of bytes read, since
// some APIs (e.g. CodeSize) may load the entire code from either the
// cache or the database when the size is not available in the cache.
CodeLoaded int // Number of contract code loaded during the state transition
CodeLoadBytes int // Total bytes of resolved code
CodeUpdated int // Number of contracts with code changes that persisted
CodeUpdateBytes int // Total bytes of persisted code written
} }
// New creates a new state from a given trie. // New creates a new state from a given trie.
@ -509,21 +517,13 @@ func (s *StateDB) SetStorage(addr common.Address, storage map[common.Hash]common
} }
// SelfDestruct marks the given account as selfdestructed. // SelfDestruct marks the given account as selfdestructed.
// This clears the account balance.
// //
// The account's state object is still available until the state is committed, // The account's state object is still available until the state is committed,
// getStateObject will return a non-nil account after SelfDestruct. // getStateObject will return a non-nil account after SelfDestruct.
func (s *StateDB) SelfDestruct(addr common.Address) uint256.Int { func (s *StateDB) SelfDestruct(addr common.Address) {
stateObject := s.getStateObject(addr) stateObject := s.getStateObject(addr)
var prevBalance uint256.Int
if stateObject == nil { if stateObject == nil {
return prevBalance return
}
prevBalance = *(stateObject.Balance())
// Regardless of whether it is already destructed or not, we do have to
// journal the balance-change, if we set it to zero here.
if !stateObject.Balance().IsZero() {
stateObject.SetBalance(new(uint256.Int))
} }
// If it is already marked as self-destructed, we do not need to add it // If it is already marked as self-destructed, we do not need to add it
// for journalling a second time. // for journalling a second time.
@ -531,18 +531,6 @@ func (s *StateDB) SelfDestruct(addr common.Address) uint256.Int {
s.journal.destruct(addr) s.journal.destruct(addr)
stateObject.markSelfdestructed() stateObject.markSelfdestructed()
} }
return prevBalance
}
func (s *StateDB) SelfDestruct6780(addr common.Address) (uint256.Int, bool) {
stateObject := s.getStateObject(addr)
if stateObject == nil {
return uint256.Int{}, false
}
if stateObject.newContract {
return s.SelfDestruct(addr), true
}
return *(stateObject.Balance()), false
} }
// SetTransientState sets transient storage for a given account. It // SetTransientState sets transient storage for a given account. It
@ -670,6 +658,16 @@ func (s *StateDB) CreateContract(addr common.Address) {
} }
} }
// IsNewContract reports whether the contract at the given address was deployed
// during the current transaction.
func (s *StateDB) IsNewContract(addr common.Address) bool {
obj := s.getStateObject(addr)
if obj == nil {
return false
}
return obj.newContract
}
// Copy creates a deep, independent copy of the state. // Copy creates a deep, independent copy of the state.
// Snapshots of the copied state cannot be applied to the copy. // Snapshots of the copied state cannot be applied to the copy.
func (s *StateDB) Copy() *StateDB { func (s *StateDB) Copy() *StateDB {
@ -869,13 +867,13 @@ func (s *StateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash {
witness := trie.Witness() witness := trie.Witness()
s.witness.AddState(witness) s.witness.AddState(witness)
if s.witnessStats != nil { if s.witnessStats != nil {
s.witnessStats.Add(witness, obj.addrHash) s.witnessStats.Add(witness, obj.addrHash())
} }
} else if obj.trie != nil { } else if obj.trie != nil {
witness := obj.trie.Witness() witness := obj.trie.Witness()
s.witness.AddState(witness) s.witness.AddState(witness)
if s.witnessStats != nil { if s.witnessStats != nil {
s.witnessStats.Add(witness, obj.addrHash) s.witnessStats.Add(witness, obj.addrHash())
} }
} }
} }
@ -893,13 +891,13 @@ func (s *StateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash {
witness := trie.Witness() witness := trie.Witness()
s.witness.AddState(witness) s.witness.AddState(witness)
if s.witnessStats != nil { if s.witnessStats != nil {
s.witnessStats.Add(witness, obj.addrHash) s.witnessStats.Add(witness, obj.addrHash())
} }
} else if obj.trie != nil { } else if obj.trie != nil {
witness := obj.trie.Witness() witness := obj.trie.Witness()
s.witness.AddState(witness) s.witness.AddState(witness)
if s.witnessStats != nil { if s.witnessStats != nil {
s.witnessStats.Add(witness, obj.addrHash) s.witnessStats.Add(witness, obj.addrHash())
} }
} }
} }
@ -945,8 +943,15 @@ func (s *StateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash {
if op.isDelete() { if op.isDelete() {
deletedAddrs = append(deletedAddrs, addr) deletedAddrs = append(deletedAddrs, addr)
} else { } else {
s.updateStateObject(s.stateObjects[addr]) obj := s.stateObjects[addr]
s.updateStateObject(obj)
s.AccountUpdated += 1 s.AccountUpdated += 1
// Count code writes post-Finalise so reverted CREATEs are excluded.
if obj.dirtyCode {
s.CodeUpdated += 1
s.CodeUpdateBytes += len(obj.code)
}
} }
usedAddrs = append(usedAddrs, addr) // Copy needed for closure usedAddrs = append(usedAddrs, addr) // Copy needed for closure
} }
@ -1279,7 +1284,7 @@ func (s *StateDB) commit(deleteEmptyObjects bool, noStorageWiping bool, blockNum
return err return err
} }
lock.Lock() lock.Lock()
updates[obj.addrHash] = update updates[obj.addrHash()] = update
s.StorageCommits = time.Since(start) // overwrite with the longest storage commit runtime s.StorageCommits = time.Since(start) // overwrite with the longest storage commit runtime
lock.Unlock() lock.Unlock()
return nil return nil

View file

@ -17,7 +17,9 @@
package state package state
import ( import (
"bytes"
"math/big" "math/big"
"sort"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/stateless" "github.com/ethereum/go-ethereum/core/stateless"
@ -52,6 +54,10 @@ func (s *hookedStateDB) CreateContract(addr common.Address) {
s.inner.CreateContract(addr) s.inner.CreateContract(addr)
} }
func (s *hookedStateDB) IsNewContract(addr common.Address) bool {
return s.inner.IsNewContract(addr)
}
func (s *hookedStateDB) GetBalance(addr common.Address) *uint256.Int { func (s *hookedStateDB) GetBalance(addr common.Address) *uint256.Int {
return s.inner.GetBalance(addr) return s.inner.GetBalance(addr)
} }
@ -211,56 +217,8 @@ func (s *hookedStateDB) SetState(address common.Address, key common.Hash, value
return prev return prev
} }
func (s *hookedStateDB) SelfDestruct(address common.Address) uint256.Int { func (s *hookedStateDB) SelfDestruct(address common.Address) {
var prevCode []byte s.inner.SelfDestruct(address)
var prevCodeHash common.Hash
if s.hooks.OnCodeChange != nil || s.hooks.OnCodeChangeV2 != nil {
prevCode = s.inner.GetCode(address)
prevCodeHash = s.inner.GetCodeHash(address)
}
prev := s.inner.SelfDestruct(address)
if s.hooks.OnBalanceChange != nil && !prev.IsZero() {
s.hooks.OnBalanceChange(address, prev.ToBig(), new(big.Int), tracing.BalanceDecreaseSelfdestruct)
}
if len(prevCode) > 0 {
if s.hooks.OnCodeChangeV2 != nil {
s.hooks.OnCodeChangeV2(address, prevCodeHash, prevCode, types.EmptyCodeHash, nil, tracing.CodeChangeSelfDestruct)
} else if s.hooks.OnCodeChange != nil {
s.hooks.OnCodeChange(address, prevCodeHash, prevCode, types.EmptyCodeHash, nil)
}
}
return prev
}
func (s *hookedStateDB) SelfDestruct6780(address common.Address) (uint256.Int, bool) {
var prevCode []byte
var prevCodeHash common.Hash
if s.hooks.OnCodeChange != nil || s.hooks.OnCodeChangeV2 != nil {
prevCodeHash = s.inner.GetCodeHash(address)
prevCode = s.inner.GetCode(address)
}
prev, changed := s.inner.SelfDestruct6780(address)
if s.hooks.OnBalanceChange != nil && !prev.IsZero() {
s.hooks.OnBalanceChange(address, prev.ToBig(), new(big.Int), tracing.BalanceDecreaseSelfdestruct)
}
if changed && len(prevCode) > 0 {
if s.hooks.OnCodeChangeV2 != nil {
s.hooks.OnCodeChangeV2(address, prevCodeHash, prevCode, types.EmptyCodeHash, nil, tracing.CodeChangeSelfDestruct)
} else if s.hooks.OnCodeChange != nil {
s.hooks.OnCodeChange(address, prevCodeHash, prevCode, types.EmptyCodeHash, nil)
}
}
return prev, changed
} }
func (s *hookedStateDB) AddLog(log *types.Log) { func (s *hookedStateDB) AddLog(log *types.Log) {
@ -272,17 +230,58 @@ func (s *hookedStateDB) AddLog(log *types.Log) {
} }
func (s *hookedStateDB) Finalise(deleteEmptyObjects bool) { func (s *hookedStateDB) Finalise(deleteEmptyObjects bool) {
defer s.inner.Finalise(deleteEmptyObjects) if s.hooks.OnBalanceChange == nil && s.hooks.OnNonceChangeV2 == nil && s.hooks.OnNonceChange == nil && s.hooks.OnCodeChangeV2 == nil && s.hooks.OnCodeChange == nil {
if s.hooks.OnBalanceChange == nil { // Short circuit if no relevant hooks are set.
s.inner.Finalise(deleteEmptyObjects)
return return
} }
// Collect all self-destructed addresses first, then sort them to ensure
// that state change hooks will be invoked in deterministic
// order when the accounts are deleted below
var selfDestructedAddrs []common.Address
for addr := range s.inner.journal.dirties { for addr := range s.inner.journal.dirties {
obj := s.inner.stateObjects[addr] obj := s.inner.stateObjects[addr]
if obj != nil && obj.selfDestructed { if obj == nil || !obj.selfDestructed {
// If ether was sent to account post-selfdestruct it is burnt. // Not self-destructed, keep searching.
continue
}
selfDestructedAddrs = append(selfDestructedAddrs, addr)
}
sort.Slice(selfDestructedAddrs, func(i, j int) bool {
return bytes.Compare(selfDestructedAddrs[i][:], selfDestructedAddrs[j][:]) < 0
})
for _, addr := range selfDestructedAddrs {
obj := s.inner.stateObjects[addr]
// Bingo: state object was self-destructed, call relevant hooks.
// If ether was sent to account post-selfdestruct, record as burnt.
if s.hooks.OnBalanceChange != nil {
if bal := obj.Balance(); bal.Sign() != 0 { if bal := obj.Balance(); bal.Sign() != 0 {
s.hooks.OnBalanceChange(addr, bal.ToBig(), new(big.Int), tracing.BalanceDecreaseSelfdestructBurn) s.hooks.OnBalanceChange(addr, bal.ToBig(), new(big.Int), tracing.BalanceDecreaseSelfdestructBurn)
} }
} }
// Nonce is set to reset on self-destruct.
if s.hooks.OnNonceChangeV2 != nil {
s.hooks.OnNonceChangeV2(addr, obj.Nonce(), 0, tracing.NonceChangeSelfdestruct)
} else if s.hooks.OnNonceChange != nil {
s.hooks.OnNonceChange(addr, obj.Nonce(), 0)
}
// If an initcode invokes selfdestruct, do not emit a code change.
prevCodeHash := s.inner.GetCodeHash(addr)
if prevCodeHash == types.EmptyCodeHash {
continue
}
// Otherwise, trace the change.
if s.hooks.OnCodeChangeV2 != nil {
s.hooks.OnCodeChangeV2(addr, prevCodeHash, s.inner.GetCode(addr), types.EmptyCodeHash, nil, tracing.CodeChangeSelfDestruct)
} else if s.hooks.OnCodeChange != nil {
s.hooks.OnCodeChange(addr, prevCodeHash, s.inner.GetCode(addr), types.EmptyCodeHash, nil)
}
} }
s.inner.Finalise(deleteEmptyObjects)
} }

View file

@ -49,6 +49,8 @@ func TestBurn(t *testing.T) {
createAndDestroy := func(addr common.Address) { createAndDestroy := func(addr common.Address) {
hooked.AddBalance(addr, uint256.NewInt(100), tracing.BalanceChangeUnspecified) hooked.AddBalance(addr, uint256.NewInt(100), tracing.BalanceChangeUnspecified)
hooked.CreateContract(addr) hooked.CreateContract(addr)
// Simulate what the opcode handler does: clear balance before selfdestruct
hooked.SubBalance(addr, hooked.GetBalance(addr), tracing.BalanceDecreaseSelfdestruct)
hooked.SelfDestruct(addr) hooked.SelfDestruct(addr)
// sanity-check that balance is now 0 // sanity-check that balance is now 0
if have, want := hooked.GetBalance(addr), new(uint256.Int); !have.Eq(want) { if have, want := hooked.GetBalance(addr), new(uint256.Int); !have.Eq(want) {
@ -140,8 +142,8 @@ func TestHooks_OnCodeChangeV2(t *testing.T) {
var result []string var result []string
var wants = []string{ var wants = []string{
"0xaa00000000000000000000000000000000000000.code: (0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470) ->0x1325 (0xa12ae05590de0c93a00bc7ac773c2fdb621e44f814985e72194f921c0050f728) ContractCreation", "0xaa00000000000000000000000000000000000000.code: (0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470) ->0x1325 (0xa12ae05590de0c93a00bc7ac773c2fdb621e44f814985e72194f921c0050f728) ContractCreation",
"0xaa00000000000000000000000000000000000000.code: 0x1325 (0xa12ae05590de0c93a00bc7ac773c2fdb621e44f814985e72194f921c0050f728) -> (0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470) SelfDestruct",
"0xbb00000000000000000000000000000000000000.code: (0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470) ->0x1326 (0x3c54516221d604e623f358bc95996ca3242aaa109bddabcebda13db9b3f90dcb) ContractCreation", "0xbb00000000000000000000000000000000000000.code: (0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470) ->0x1326 (0x3c54516221d604e623f358bc95996ca3242aaa109bddabcebda13db9b3f90dcb) ContractCreation",
"0xaa00000000000000000000000000000000000000.code: 0x1325 (0xa12ae05590de0c93a00bc7ac773c2fdb621e44f814985e72194f921c0050f728) -> (0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470) SelfDestruct",
"0xbb00000000000000000000000000000000000000.code: 0x1326 (0x3c54516221d604e623f358bc95996ca3242aaa109bddabcebda13db9b3f90dcb) -> (0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470) SelfDestruct", "0xbb00000000000000000000000000000000000000.code: 0x1326 (0x3c54516221d604e623f358bc95996ca3242aaa109bddabcebda13db9b3f90dcb) -> (0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470) SelfDestruct",
} }
emitF := func(format string, a ...any) { emitF := func(format string, a ...any) {
@ -157,7 +159,8 @@ func TestHooks_OnCodeChangeV2(t *testing.T) {
sdb.SetCode(common.Address{0xbb}, []byte{0x13, 38}, tracing.CodeChangeContractCreation) sdb.SetCode(common.Address{0xbb}, []byte{0x13, 38}, tracing.CodeChangeContractCreation)
sdb.CreateContract(common.Address{0xbb}) sdb.CreateContract(common.Address{0xbb})
sdb.SelfDestruct6780(common.Address{0xbb}) sdb.SelfDestruct(common.Address{0xbb})
sdb.Finalise(true)
if len(result) != len(wants) { if len(result) != len(wants) {
t.Fatalf("number of tracing events wrong, have %d want %d", len(result), len(wants)) t.Fatalf("number of tracing events wrong, have %d want %d", len(result), len(wants))

View file

@ -31,10 +31,10 @@ import (
"github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/crypto/keccak"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/trie" "github.com/ethereum/go-ethereum/trie"
"github.com/holiman/uint256" "github.com/holiman/uint256"
"golang.org/x/crypto/sha3"
) )
func u64(val uint64) *uint64 { return &val } func u64(val uint64) *uint64 { return &val }
@ -398,7 +398,7 @@ func GenerateBadBlock(parent *types.Block, engine consensus.Engine, txs types.Tr
var receipts []*types.Receipt var receipts []*types.Receipt
// The post-state result doesn't need to be correct (this is a bad block), but we do need something there // The post-state result doesn't need to be correct (this is a bad block), but we do need something there
// Preferably something unique. So let's use a combo of blocknum + txhash // Preferably something unique. So let's use a combo of blocknum + txhash
hasher := sha3.NewLegacyKeccak256() hasher := keccak.NewLegacyKeccak256()
hasher.Write(header.Number.Bytes()) hasher.Write(header.Number.Bytes())
var cumulativeGas uint64 var cumulativeGas uint64
var nBlobs int var nBlobs int

View file

@ -31,8 +31,9 @@ const _BalanceChangeReason_name = "UnspecifiedBalanceIncreaseRewardMineUncleBala
var _BalanceChangeReason_index = [...]uint16{0, 11, 41, 71, 96, 125, 160, 181, 205, 231, 256, 264, 276, 303, 330, 361, 367} var _BalanceChangeReason_index = [...]uint16{0, 11, 41, 71, 96, 125, 160, 181, 205, 231, 256, 264, 276, 303, 330, 361, 367}
func (i BalanceChangeReason) String() string { func (i BalanceChangeReason) String() string {
if i >= BalanceChangeReason(len(_BalanceChangeReason_index)-1) { idx := int(i) - 0
if i < 0 || idx >= len(_BalanceChangeReason_index)-1 {
return "BalanceChangeReason(" + strconv.FormatInt(int64(i), 10) + ")" return "BalanceChangeReason(" + strconv.FormatInt(int64(i), 10) + ")"
} }
return _BalanceChangeReason_name[_BalanceChangeReason_index[i]:_BalanceChangeReason_index[i+1]] return _BalanceChangeReason_name[_BalanceChangeReason_index[idx]:_BalanceChangeReason_index[idx+1]]
} }

View file

@ -22,8 +22,9 @@ const _CodeChangeReason_name = "UnspecifiedContractCreationGenesisAuthorizationA
var _CodeChangeReason_index = [...]uint8{0, 11, 27, 34, 47, 65, 77, 83} var _CodeChangeReason_index = [...]uint8{0, 11, 27, 34, 47, 65, 77, 83}
func (i CodeChangeReason) String() string { func (i CodeChangeReason) String() string {
if i >= CodeChangeReason(len(_CodeChangeReason_index)-1) { idx := int(i) - 0
if i < 0 || idx >= len(_CodeChangeReason_index)-1 {
return "CodeChangeReason(" + strconv.FormatInt(int64(i), 10) + ")" return "CodeChangeReason(" + strconv.FormatInt(int64(i), 10) + ")"
} }
return _CodeChangeReason_name[_CodeChangeReason_index[i]:_CodeChangeReason_index[i+1]] return _CodeChangeReason_name[_CodeChangeReason_index[idx]:_CodeChangeReason_index[idx+1]]
} }

View file

@ -15,15 +15,17 @@ func _() {
_ = x[NonceChangeNewContract-4] _ = x[NonceChangeNewContract-4]
_ = x[NonceChangeAuthorization-5] _ = x[NonceChangeAuthorization-5]
_ = x[NonceChangeRevert-6] _ = x[NonceChangeRevert-6]
_ = x[NonceChangeSelfdestruct-7]
} }
const _NonceChangeReason_name = "UnspecifiedGenesisEoACallContractCreatorNewContractAuthorizationRevert" const _NonceChangeReason_name = "UnspecifiedGenesisEoACallContractCreatorNewContractAuthorizationRevertSelfdestruct"
var _NonceChangeReason_index = [...]uint8{0, 11, 18, 25, 40, 51, 64, 70} var _NonceChangeReason_index = [...]uint8{0, 11, 18, 25, 40, 51, 64, 70, 82}
func (i NonceChangeReason) String() string { func (i NonceChangeReason) String() string {
if i >= NonceChangeReason(len(_NonceChangeReason_index)-1) { idx := int(i) - 0
if i < 0 || idx >= len(_NonceChangeReason_index)-1 {
return "NonceChangeReason(" + strconv.FormatInt(int64(i), 10) + ")" return "NonceChangeReason(" + strconv.FormatInt(int64(i), 10) + ")"
} }
return _NonceChangeReason_name[_NonceChangeReason_index[i]:_NonceChangeReason_index[i+1]] return _NonceChangeReason_name[_NonceChangeReason_index[idx]:_NonceChangeReason_index[idx+1]]
} }

View file

@ -432,6 +432,9 @@ const (
// NonceChangeRevert is emitted when the nonce is reverted back to a previous value due to call failure. // NonceChangeRevert is emitted when the nonce is reverted back to a previous value due to call failure.
// It is only emitted when the tracer has opted in to use the journaling wrapper (WrapWithJournal). // It is only emitted when the tracer has opted in to use the journaling wrapper (WrapWithJournal).
NonceChangeRevert NonceChangeReason = 6 NonceChangeRevert NonceChangeReason = 6
// NonceChangeSelfdestruct is emitted when the nonce is reset to zero due to a self-destruct
NonceChangeSelfdestruct NonceChangeReason = 7
) )
// CodeChangeReason is used to indicate the reason for a code change. // CodeChangeReason is used to indicate the reason for a code change.

View file

@ -2096,6 +2096,11 @@ func (p *BlobPool) Clear() {
p.index = make(map[common.Address][]*blobTxMeta) p.index = make(map[common.Address][]*blobTxMeta)
p.spent = make(map[common.Address]*uint256.Int) p.spent = make(map[common.Address]*uint256.Int)
// Reset counters and the gapped buffer
p.stored = 0
p.gapped = make(map[common.Address][]*types.Transaction)
p.gappedSource = make(map[common.Hash]common.Address)
var ( var (
basefee = uint256.MustFromBig(eip1559.CalcBaseFee(p.chain.Config(), p.head.Load())) basefee = uint256.MustFromBig(eip1559.CalcBaseFee(p.chain.Config(), p.head.Load()))
blobfee = uint256.NewInt(params.BlobTxMinBlobGasprice) blobfee = uint256.NewInt(params.BlobTxMinBlobGasprice)

View file

@ -114,6 +114,9 @@ var (
queuedGauge = metrics.NewRegisteredGauge("txpool/queued", nil) queuedGauge = metrics.NewRegisteredGauge("txpool/queued", nil)
slotsGauge = metrics.NewRegisteredGauge("txpool/slots", nil) slotsGauge = metrics.NewRegisteredGauge("txpool/slots", nil)
pendingAddrsGauge = metrics.NewRegisteredGauge("txpool/pending/accounts", nil)
queuedAddrsGauge = metrics.NewRegisteredGauge("txpool/queued/accounts", nil)
reheapTimer = metrics.NewRegisteredTimer("txpool/reheap", nil) reheapTimer = metrics.NewRegisteredTimer("txpool/reheap", nil)
) )
@ -148,7 +151,7 @@ type Config struct {
AccountQueue uint64 // Maximum number of non-executable transaction slots permitted per account AccountQueue uint64 // Maximum number of non-executable transaction slots permitted per account
GlobalQueue uint64 // Maximum number of non-executable transaction slots for all accounts GlobalQueue uint64 // Maximum number of non-executable transaction slots for all accounts
Lifetime time.Duration // Maximum amount of time non-executable transaction are queued Lifetime time.Duration // Maximum amount of time an account can remain stale in the non-executable pool
} }
// DefaultConfig contains the default configurations for the transaction pool. // DefaultConfig contains the default configurations for the transaction pool.
@ -775,7 +778,7 @@ func (pool *LegacyPool) add(tx *types.Transaction) (replaced bool, err error) {
pool.queueTxEvent(tx) pool.queueTxEvent(tx)
log.Trace("Pooled new executable transaction", "hash", hash, "from", from, "to", tx.To()) log.Trace("Pooled new executable transaction", "hash", hash, "from", from, "to", tx.To())
// Successful promotion, bump the heartbeat // Successful replacement. If needed, bump the heartbeat giving more time to queued txs.
pool.queue.bump(from) pool.queue.bump(from)
return old != nil, nil return old != nil, nil
} }
@ -844,6 +847,7 @@ func (pool *LegacyPool) promoteTx(addr common.Address, hash common.Hash, tx *typ
// Try to insert the transaction into the pending queue // Try to insert the transaction into the pending queue
if pool.pending[addr] == nil { if pool.pending[addr] == nil {
pool.pending[addr] = newList(true) pool.pending[addr] = newList(true)
pendingAddrsGauge.Inc(1)
} }
list := pool.pending[addr] list := pool.pending[addr]
@ -867,7 +871,7 @@ func (pool *LegacyPool) promoteTx(addr common.Address, hash common.Hash, tx *typ
// Set the potentially new pending nonce and notify any subsystems of the new tx // Set the potentially new pending nonce and notify any subsystems of the new tx
pool.pendingNonces.set(addr, tx.Nonce()+1) pool.pendingNonces.set(addr, tx.Nonce()+1)
// Successful promotion, bump the heartbeat // Successful promotion, bump the heartbeat, giving more time to queued txs.
pool.queue.bump(addr) pool.queue.bump(addr)
return true return true
} }
@ -904,8 +908,8 @@ func (pool *LegacyPool) addRemoteSync(tx *types.Transaction) error {
func (pool *LegacyPool) Add(txs []*types.Transaction, sync bool) []error { func (pool *LegacyPool) Add(txs []*types.Transaction, sync bool) []error {
// Filter out known ones without obtaining the pool lock or recovering signatures // Filter out known ones without obtaining the pool lock or recovering signatures
var ( var (
errs = make([]error, len(txs)) hasValid bool
news = make([]*types.Transaction, 0, len(txs)) errs = make([]error, len(txs))
) )
for i, tx := range txs { for i, tx := range txs {
// If the transaction is known, pre-set the error slot // If the transaction is known, pre-set the error slot
@ -923,26 +927,17 @@ func (pool *LegacyPool) Add(txs []*types.Transaction, sync bool) []error {
invalidTxMeter.Mark(1) invalidTxMeter.Mark(1)
continue continue
} }
// Accumulate all unknown transactions for deeper processing hasValid = true
news = append(news, tx)
} }
if len(news) == 0 { if !hasValid {
return errs return errs
} }
// Process all the new transaction and merge any errors into the original slice // Process all the new transaction and merge any errors into the original slice
pool.mu.Lock() pool.mu.Lock()
newErrs, dirtyAddrs := pool.addTxsLocked(news) dirtyAddrs := pool.addTxsLocked(txs, errs)
pool.mu.Unlock() pool.mu.Unlock()
var nilSlot = 0
for _, err := range newErrs {
for errs[nilSlot] != nil {
nilSlot++
}
errs[nilSlot] = err
nilSlot++
}
// Reorg the pool internals if needed and return // Reorg the pool internals if needed and return
done := pool.requestPromoteExecutables(dirtyAddrs) done := pool.requestPromoteExecutables(dirtyAddrs)
if sync { if sync {
@ -953,14 +948,19 @@ func (pool *LegacyPool) Add(txs []*types.Transaction, sync bool) []error {
// addTxsLocked attempts to queue a batch of transactions if they are valid. // addTxsLocked attempts to queue a batch of transactions if they are valid.
// The transaction pool lock must be held. // The transaction pool lock must be held.
// Returns the error for each tx, and the set of accounts that might became promotable. // Sets the error for each tx, and the set of accounts that might became promotable.
func (pool *LegacyPool) addTxsLocked(txs []*types.Transaction) ([]error, *accountSet) { // We only try to add txs that have no error set in the errs slice.
// If adding the transaction returns an error, we set the error in the errs slice.
// Requires len(txs) == len(errs).
func (pool *LegacyPool) addTxsLocked(txs []*types.Transaction, errs []error) *accountSet {
var ( var (
dirty = newAccountSet(pool.signer) dirty = newAccountSet(pool.signer)
errs = make([]error, len(txs))
valid int64 valid int64
) )
for i, tx := range txs { for i, tx := range txs {
if errs[i] != nil {
continue
}
replaced, err := pool.add(tx) replaced, err := pool.add(tx)
errs[i] = err errs[i] = err
if err == nil { if err == nil {
@ -971,7 +971,7 @@ func (pool *LegacyPool) addTxsLocked(txs []*types.Transaction) ([]error, *accoun
} }
} }
validTxMeter.Mark(valid) validTxMeter.Mark(valid)
return errs, dirty return dirty
} }
// Status returns the status (unknown/pending/queued) of a batch of transactions // Status returns the status (unknown/pending/queued) of a batch of transactions
@ -1083,6 +1083,7 @@ func (pool *LegacyPool) removeTx(hash common.Hash, outofbound bool, unreserve bo
// If no more pending transactions are left, remove the list // If no more pending transactions are left, remove the list
if pending.Empty() { if pending.Empty() {
delete(pool.pending, addr) delete(pool.pending, addr)
pendingAddrsGauge.Dec(1)
} }
// Postpone any invalidated transactions // Postpone any invalidated transactions
for _, tx := range invalids { for _, tx := range invalids {
@ -1392,7 +1393,7 @@ func (pool *LegacyPool) reset(oldHead, newHead *types.Header) {
// Inject any transactions discarded due to reorgs // Inject any transactions discarded due to reorgs
log.Debug("Reinjecting stale transactions", "count", len(reinject)) log.Debug("Reinjecting stale transactions", "count", len(reinject))
core.SenderCacher().Recover(pool.signer, reinject) core.SenderCacher().Recover(pool.signer, reinject)
pool.addTxsLocked(reinject) pool.addTxsLocked(reinject, make([]error, len(reinject)))
} }
// promoteExecutables moves transactions that have become processable from the // promoteExecutables moves transactions that have become processable from the
@ -1563,6 +1564,7 @@ func (pool *LegacyPool) demoteUnexecutables() {
// Internal shuffle shouldn't touch the lookup set. // Internal shuffle shouldn't touch the lookup set.
pool.enqueueTx(hash, tx, false) pool.enqueueTx(hash, tx, false)
} }
pool.priced.Removed(len(olds) + len(drops))
pendingGauge.Dec(int64(len(olds) + len(drops) + len(invalids))) pendingGauge.Dec(int64(len(olds) + len(drops) + len(invalids)))
// If there's a gap in front, alert (should never happen) and postpone all transactions // If there's a gap in front, alert (should never happen) and postpone all transactions
@ -1580,6 +1582,7 @@ func (pool *LegacyPool) demoteUnexecutables() {
// Delete the entire pending entry if it became empty. // Delete the entire pending entry if it became empty.
if list.Empty() { if list.Empty() {
delete(pool.pending, addr) delete(pool.pending, addr)
pendingAddrsGauge.Dec(1)
if _, ok := pool.queue.get(addr); !ok { if _, ok := pool.queue.get(addr); !ok {
pool.reserver.Release(addr) pool.reserver.Release(addr)
} }
@ -1839,6 +1842,13 @@ func (pool *LegacyPool) Clear() {
pool.pending = make(map[common.Address]*list) pool.pending = make(map[common.Address]*list)
pool.queue = newQueue(pool.config, pool.signer) pool.queue = newQueue(pool.config, pool.signer)
pool.pendingNonces = newNoncer(pool.currentState) pool.pendingNonces = newNoncer(pool.currentState)
// Reset gauges
pendingGauge.Update(0)
queuedGauge.Update(0)
slotsGauge.Update(0)
pendingAddrsGauge.Update(0)
queuedAddrsGauge.Update(0)
} }
// HasPendingAuth returns a flag indicating whether there are pending // HasPendingAuth returns a flag indicating whether there are pending

View file

@ -88,8 +88,12 @@ func (q *queue) get(addr common.Address) (*list, bool) {
return l, ok return l, ok
} }
// bump updates the heartbeat for the given account address.
// If the address is unknown, the call is a no-op.
func (q *queue) bump(addr common.Address) { func (q *queue) bump(addr common.Address) {
q.beats[addr] = time.Now() if _, ok := q.beats[addr]; ok {
q.beats[addr] = time.Now()
}
} }
func (q *queue) addresses() []common.Address { func (q *queue) addresses() []common.Address {
@ -114,6 +118,7 @@ func (q *queue) remove(addr common.Address, tx *types.Transaction) {
if future.Empty() { if future.Empty() {
delete(q.queued, addr) delete(q.queued, addr)
delete(q.beats, addr) delete(q.beats, addr)
queuedAddrsGauge.Dec(1)
} }
} }
} }
@ -123,6 +128,7 @@ func (q *queue) add(tx *types.Transaction) (*common.Hash, error) {
from, _ := types.Sender(q.signer, tx) // already validated from, _ := types.Sender(q.signer, tx) // already validated
if q.queued[from] == nil { if q.queued[from] == nil {
q.queued[from] = newList(false) q.queued[from] = newList(false)
queuedAddrsGauge.Inc(1)
} }
inserted, old := q.queued[from].Add(tx, q.config.PriceBump) inserted, old := q.queued[from].Add(tx, q.config.PriceBump)
if !inserted { if !inserted {
@ -200,6 +206,7 @@ func (q *queue) promoteExecutables(accounts []common.Address, gasLimit uint64, c
if list.Empty() { if list.Empty() {
delete(q.queued, addr) delete(q.queued, addr)
delete(q.beats, addr) delete(q.beats, addr)
queuedAddrsGauge.Dec(1)
removedAddresses = append(removedAddresses, addr) removedAddresses = append(removedAddresses, addr)
} }
} }

View file

@ -424,3 +424,33 @@ func EncodeBlockReceiptLists(receipts []Receipts) []rlp.RawValue {
} }
return result return result
} }
// SlimReceipt is a wrapper around a Receipt with RLP serialization that omits
// the Bloom field and includes the tx type. Used for era files.
type SlimReceipt Receipt
type slimReceiptRLP struct {
Type uint8
StatusEncoding []byte
CumulativeGasUsed uint64
Logs []*Log
}
// EncodeRLP implements rlp.Encoder, encoding the receipt as
// [tx-type, post-state-or-status, cumulative-gas, logs].
func (r *SlimReceipt) EncodeRLP(w io.Writer) error {
data := &slimReceiptRLP{r.Type, (*Receipt)(r).statusEncoding(), r.CumulativeGasUsed, r.Logs}
return rlp.Encode(w, data)
}
// DecodeRLP implements rlp.Decoder.
func (r *SlimReceipt) DecodeRLP(s *rlp.Stream) error {
var data slimReceiptRLP
if err := s.Decode(&data); err != nil {
return err
}
r.Type = data.Type
r.CumulativeGasUsed = data.CumulativeGasUsed
r.Logs = data.Logs
return (*Receipt)(r).setStatus(data.StatusEncoding)
}

View file

@ -512,6 +512,45 @@ func TestReceiptUnmarshalBinary(t *testing.T) {
} }
} }
func TestSlimReceiptEncodingDecoding(t *testing.T) {
tests := []*Receipt{
legacyReceipt,
accessListReceipt,
eip1559Receipt,
{
Type: BlobTxType,
Status: ReceiptStatusSuccessful,
CumulativeGasUsed: 100,
Logs: []*Log{},
},
}
for i, want := range tests {
enc, err := rlp.EncodeToBytes((*SlimReceipt)(want))
if err != nil {
t.Fatalf("test %d: encode error: %v", i, err)
}
got := new(SlimReceipt)
if err := rlp.DecodeBytes(enc, got); err != nil {
t.Fatalf("test %d: decode error: %v", i, err)
}
if got.Type != want.Type {
t.Errorf("test %d: Type mismatch: got %d, want %d", i, got.Type, want.Type)
}
if got.Status != want.Status {
t.Errorf("test %d: Status mismatch: got %d, want %d", i, got.Status, want.Status)
}
if !bytes.Equal(got.PostState, want.PostState) {
t.Errorf("test %d: PostState mismatch: got %x, want %x", i, got.PostState, want.PostState)
}
if got.CumulativeGasUsed != want.CumulativeGasUsed {
t.Errorf("test %d: CumulativeGasUsed mismatch: got %d, want %d", i, got.CumulativeGasUsed, want.CumulativeGasUsed)
}
if len(got.Logs) != len(want.Logs) {
t.Errorf("test %d: Logs length mismatch: got %d, want %d", i, len(got.Logs), len(want.Logs))
}
}
}
func clearComputedFieldsOnReceipts(receipts []*Receipt) []*Receipt { func clearComputedFieldsOnReceipts(receipts []*Receipt) []*Receipt {
r := make([]*Receipt, len(receipts)) r := make([]*Receipt, len(receipts))
for i, receipt := range receipts { for i, receipt := range receipts {

View file

@ -282,7 +282,10 @@ func (s *modernSigner) SignatureValues(tx *Transaction, sig []byte) (R, S, V *bi
if tx.inner.chainID().Sign() != 0 && tx.inner.chainID().Cmp(s.chainID) != 0 { if tx.inner.chainID().Sign() != 0 && tx.inner.chainID().Cmp(s.chainID) != 0 {
return nil, nil, nil, fmt.Errorf("%w: have %d want %d", ErrInvalidChainId, tx.inner.chainID(), s.chainID) return nil, nil, nil, fmt.Errorf("%w: have %d want %d", ErrInvalidChainId, tx.inner.chainID(), s.chainID)
} }
R, S, _ = decodeSignature(sig) R, S, _, err = decodeSignature(sig)
if err != nil {
return nil, nil, nil, err
}
V = big.NewInt(int64(sig[64])) V = big.NewInt(int64(sig[64]))
return R, S, V, nil return R, S, V, nil
} }
@ -327,7 +330,7 @@ func NewEIP2930Signer(chainId *big.Int) Signer {
// are replay-protected as well as unprotected homestead transactions. // are replay-protected as well as unprotected homestead transactions.
// Deprecated: always use the Signer interface type // Deprecated: always use the Signer interface type
type EIP155Signer struct { type EIP155Signer struct {
chainId, chainIdMul *big.Int chainId *big.Int
} }
func NewEIP155Signer(chainId *big.Int) EIP155Signer { func NewEIP155Signer(chainId *big.Int) EIP155Signer {
@ -335,8 +338,7 @@ func NewEIP155Signer(chainId *big.Int) EIP155Signer {
chainId = new(big.Int) chainId = new(big.Int)
} }
return EIP155Signer{ return EIP155Signer{
chainId: chainId, chainId: chainId,
chainIdMul: new(big.Int).Mul(chainId, big.NewInt(2)),
} }
} }
@ -362,7 +364,8 @@ func (s EIP155Signer) Sender(tx *Transaction) (common.Address, error) {
return common.Address{}, fmt.Errorf("%w: have %d want %d", ErrInvalidChainId, tx.ChainId(), s.chainId) return common.Address{}, fmt.Errorf("%w: have %d want %d", ErrInvalidChainId, tx.ChainId(), s.chainId)
} }
V, R, S := tx.RawSignatureValues() V, R, S := tx.RawSignatureValues()
V = new(big.Int).Sub(V, s.chainIdMul) V = new(big.Int).Sub(V, s.chainId)
V = new(big.Int).Sub(V, s.chainId)
V.Sub(V, big8) V.Sub(V, big8)
return recoverPlain(s.Hash(tx), R, S, V, true) return recoverPlain(s.Hash(tx), R, S, V, true)
} }
@ -373,10 +376,14 @@ func (s EIP155Signer) SignatureValues(tx *Transaction, sig []byte) (R, S, V *big
if tx.Type() != LegacyTxType { if tx.Type() != LegacyTxType {
return nil, nil, nil, ErrTxTypeNotSupported return nil, nil, nil, ErrTxTypeNotSupported
} }
R, S, V = decodeSignature(sig) R, S, V, err = decodeSignature(sig)
if err != nil {
return nil, nil, nil, err
}
if s.chainId.Sign() != 0 { if s.chainId.Sign() != 0 {
V = big.NewInt(int64(sig[64] + 35)) V = big.NewInt(int64(sig[64] + 35))
V.Add(V, s.chainIdMul) V.Add(V, s.chainId)
V.Add(V, s.chainId)
} }
return R, S, V, nil return R, S, V, nil
} }
@ -442,8 +449,8 @@ func (fs FrontierSigner) SignatureValues(tx *Transaction, sig []byte) (r, s, v *
if tx.Type() != LegacyTxType { if tx.Type() != LegacyTxType {
return nil, nil, nil, ErrTxTypeNotSupported return nil, nil, nil, ErrTxTypeNotSupported
} }
r, s, v = decodeSignature(sig) r, s, v, err = decodeSignature(sig)
return r, s, v, nil return r, s, v, err
} }
// Hash returns the hash to be signed by the sender. // Hash returns the hash to be signed by the sender.
@ -459,14 +466,14 @@ func (fs FrontierSigner) Hash(tx *Transaction) common.Hash {
}) })
} }
func decodeSignature(sig []byte) (r, s, v *big.Int) { func decodeSignature(sig []byte) (r, s, v *big.Int, err error) {
if len(sig) != crypto.SignatureLength { if len(sig) != crypto.SignatureLength {
panic(fmt.Sprintf("wrong size for signature: got %d, want %d", len(sig), crypto.SignatureLength)) return nil, nil, nil, fmt.Errorf("wrong size for signature: got %d, want %d", len(sig), crypto.SignatureLength)
} }
r = new(big.Int).SetBytes(sig[:32]) r = new(big.Int).SetBytes(sig[:32])
s = new(big.Int).SetBytes(sig[32:64]) s = new(big.Int).SetBytes(sig[32:64])
v = new(big.Int).SetBytes([]byte{sig[64] + 27}) v = new(big.Int).SetBytes([]byte{sig[64] + 27})
return r, s, v return r, s, v, nil
} }
func recoverPlain(sighash common.Hash, R, S, Vb *big.Int, homestead bool) (common.Address, error) { func recoverPlain(sighash common.Hash, R, S, Vb *big.Int, homestead bool) (common.Address, error) {

View file

@ -200,3 +200,28 @@ func Benchmark_modernSigner_Equal(b *testing.B) {
} }
} }
} }
func TestSignatureValuesError(t *testing.T) {
// 1. Setup a valid transaction
tx := NewTransaction(0, common.Address{}, big.NewInt(0), 0, big.NewInt(0), nil)
signer := HomesteadSigner{}
// 2. Call WithSignature with invalid length sig (not 65 bytes)
invalidSig := make([]byte, 64)
func() {
defer func() {
if r := recover(); r != nil {
t.Fatalf("Panicked for invalid signature length, expected error: %v", r)
}
}()
_, err := tx.WithSignature(signer, invalidSig)
if err == nil {
t.Fatal("Expected error for invalid signature length, got nil")
} else {
// This is just a sanity check to ensure we got an error,
// the exact error message is verified in unit tests elsewhere if needed.
t.Logf("Got expected error: %v", err)
}
}()
}

View file

@ -118,8 +118,8 @@ func (sc *BlobTxSidecar) ToV1() error {
} }
if sc.Version == BlobSidecarVersion0 { if sc.Version == BlobSidecarVersion0 {
proofs := make([]kzg4844.Proof, 0, len(sc.Blobs)*kzg4844.CellProofsPerBlob) proofs := make([]kzg4844.Proof, 0, len(sc.Blobs)*kzg4844.CellProofsPerBlob)
for _, blob := range sc.Blobs { for i := range sc.Blobs {
cellProofs, err := kzg4844.ComputeCellProofs(&blob) cellProofs, err := kzg4844.ComputeCellProofs(&sc.Blobs[i])
if err != nil { if err != nil {
return err return err
} }

View file

@ -94,7 +94,10 @@ func SignSetCode(prv *ecdsa.PrivateKey, auth SetCodeAuthorization) (SetCodeAutho
if err != nil { if err != nil {
return SetCodeAuthorization{}, err return SetCodeAuthorization{}, err
} }
r, s, _ := decodeSignature(sig) r, s, _, err := decodeSignature(sig)
if err != nil {
return SetCodeAuthorization{}, err
}
return SetCodeAuthorization{ return SetCodeAuthorization{
ChainID: auth.ChainID, ChainID: auth.ChainID,
Address: auth.Address, Address: auth.Address,

View file

@ -73,9 +73,8 @@ type BlockContext struct {
type TxContext struct { type TxContext struct {
// Message information // Message information
Origin common.Address // Provides information for ORIGIN Origin common.Address // Provides information for ORIGIN
GasPrice *big.Int // Provides information for GASPRICE (and is used to zero the basefee if NoBaseFee is set) GasPrice *uint256.Int // Provides information for GASPRICE (and is used to zero the basefee if NoBaseFee is set)
BlobHashes []common.Hash // Provides information for BLOBHASH BlobHashes []common.Hash // Provides information for BLOBHASH
BlobFeeCap *big.Int // Is used to zero the blobbasefee if NoBaseFee is set
AccessEvents *state.AccessEvents // Capture all state accesses for this tx AccessEvents *state.AccessEvents // Capture all state accesses for this tx
} }
@ -125,9 +124,6 @@ type EVM struct {
// jumpDests stores results of JUMPDEST analysis. // jumpDests stores results of JUMPDEST analysis.
jumpDests JumpDestCache jumpDests JumpDestCache
hasher crypto.KeccakState // Keccak256 hasher instance shared across opcodes
hasherBuf common.Hash // Keccak256 hasher result array shared across opcodes
readOnly bool // Whether to throw on stateful modifications readOnly bool // Whether to throw on stateful modifications
returnData []byte // Last CALL's return data for subsequent reuse returnData []byte // Last CALL's return data for subsequent reuse
} }
@ -144,7 +140,6 @@ func NewEVM(blockCtx BlockContext, statedb StateDB, chainConfig *params.ChainCon
chainConfig: chainConfig, chainConfig: chainConfig,
chainRules: chainConfig.Rules(blockCtx.BlockNumber, blockCtx.Random != nil, blockCtx.Time), chainRules: chainConfig.Rules(blockCtx.BlockNumber, blockCtx.Random != nil, blockCtx.Time),
jumpDests: newMapJumpDests(), jumpDests: newMapJumpDests(),
hasher: crypto.NewKeccakState(),
} }
evm.precompiles = activePrecompiledContracts(evm.chainRules) evm.precompiles = activePrecompiledContracts(evm.chainRules)
@ -618,7 +613,7 @@ func (evm *EVM) Create(caller common.Address, code []byte, gas uint64, value *ui
// The different between Create2 with Create is Create2 uses keccak256(0xff ++ msg.sender ++ salt ++ keccak256(init_code))[12:] // The different between Create2 with Create is Create2 uses keccak256(0xff ++ msg.sender ++ salt ++ keccak256(init_code))[12:]
// instead of the usual sender-and-nonce-hash as the address where the contract is initialized at. // instead of the usual sender-and-nonce-hash as the address where the contract is initialized at.
func (evm *EVM) Create2(caller common.Address, code []byte, gas uint64, endowment *uint256.Int, salt *uint256.Int) (ret []byte, contractAddr common.Address, leftOverGas uint64, err error) { func (evm *EVM) Create2(caller common.Address, code []byte, gas uint64, endowment *uint256.Int, salt *uint256.Int) (ret []byte, contractAddr common.Address, leftOverGas uint64, err error) {
inithash := crypto.HashData(evm.hasher, code) inithash := crypto.Keccak256Hash(code)
contractAddr = crypto.CreateAddress2(caller, salt.Bytes32(), inithash[:]) contractAddr = crypto.CreateAddress2(caller, salt.Bytes32(), inithash[:])
return evm.create(caller, code, gas, endowment, contractAddr, CREATE2) return evm.create(caller, code, gas, endowment, contractAddr, CREATE2)
} }

View file

@ -97,6 +97,9 @@ var (
) )
func gasSStore(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { func gasSStore(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
if evm.readOnly {
return 0, ErrWriteProtection
}
var ( var (
y, x = stack.Back(1), stack.Back(0) y, x = stack.Back(1), stack.Back(0)
current, original = evm.StateDB.GetStateAndCommittedState(contract.Address(), x.Bytes32()) current, original = evm.StateDB.GetStateAndCommittedState(contract.Address(), x.Bytes32())
@ -181,6 +184,9 @@ func gasSStore(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySi
// (2.2.2.1.) If original value is 0, add SSTORE_SET_GAS - SLOAD_GAS to refund counter. // (2.2.2.1.) If original value is 0, add SSTORE_SET_GAS - SLOAD_GAS to refund counter.
// (2.2.2.2.) Otherwise, add SSTORE_RESET_GAS - SLOAD_GAS gas to refund counter. // (2.2.2.2.) Otherwise, add SSTORE_RESET_GAS - SLOAD_GAS gas to refund counter.
func gasSStoreEIP2200(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { func gasSStoreEIP2200(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
if evm.readOnly {
return 0, ErrWriteProtection
}
// If we fail the minimum gas availability invariant, fail (0) // If we fail the minimum gas availability invariant, fail (0)
if contract.Gas <= params.SstoreSentryGasEIP2200 { if contract.Gas <= params.SstoreSentryGasEIP2200 {
return 0, errors.New("not enough gas for reentrancy sentry") return 0, errors.New("not enough gas for reentrancy sentry")
@ -374,6 +380,10 @@ func gasCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize
transfersValue = !stack.Back(2).IsZero() transfersValue = !stack.Back(2).IsZero()
address = common.Address(stack.Back(1).Bytes20()) address = common.Address(stack.Back(1).Bytes20())
) )
if evm.readOnly && transfersValue {
return 0, ErrWriteProtection
}
if evm.chainRules.IsEIP158 { if evm.chainRules.IsEIP158 {
if transfersValue && evm.StateDB.Empty(address) { if transfersValue && evm.StateDB.Empty(address) {
gas += params.CallNewAccountGas gas += params.CallNewAccountGas
@ -462,6 +472,10 @@ func gasStaticCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memo
} }
func gasSelfdestruct(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { func gasSelfdestruct(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
if evm.readOnly {
return 0, ErrWriteProtection
}
var gas uint64 var gas uint64
// EIP150 homestead gas reprice fork: // EIP150 homestead gas reprice fork:
if evm.chainRules.IsEIP150 { if evm.chainRules.IsEIP150 {

View file

@ -22,6 +22,7 @@ import (
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/tracing" "github.com/ethereum/go-ethereum/core/tracing"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
"github.com/holiman/uint256" "github.com/holiman/uint256"
) )
@ -233,14 +234,12 @@ func opKeccak256(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
offset, size := scope.Stack.pop(), scope.Stack.peek() offset, size := scope.Stack.pop(), scope.Stack.peek()
data := scope.Memory.GetPtr(offset.Uint64(), size.Uint64()) data := scope.Memory.GetPtr(offset.Uint64(), size.Uint64())
evm.hasher.Reset() hash := crypto.Keccak256Hash(data)
evm.hasher.Write(data)
evm.hasher.Read(evm.hasherBuf[:])
if evm.Config.EnablePreimageRecording { if evm.Config.EnablePreimageRecording {
evm.StateDB.AddPreimage(evm.hasherBuf, data) evm.StateDB.AddPreimage(hash, data)
} }
size.SetBytes(evm.hasherBuf[:]) size.SetBytes(hash[:])
return nil, nil return nil, nil
} }
@ -417,8 +416,7 @@ func opExtCodeHash(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
} }
func opGasprice(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) { func opGasprice(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
v, _ := uint256.FromBig(evm.GasPrice) scope.Stack.push(evm.GasPrice.Clone())
scope.Stack.push(v)
return nil, nil return nil, nil
} }
@ -885,13 +883,24 @@ func opSelfdestruct(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
if evm.readOnly { if evm.readOnly {
return nil, ErrWriteProtection return nil, ErrWriteProtection
} }
beneficiary := scope.Stack.pop() var (
balance := evm.StateDB.GetBalance(scope.Contract.Address()) this = scope.Contract.Address()
evm.StateDB.AddBalance(beneficiary.Bytes20(), balance, tracing.BalanceIncreaseSelfdestruct) balance = evm.StateDB.GetBalance(this)
evm.StateDB.SelfDestruct(scope.Contract.Address()) top = scope.Stack.pop()
beneficiary = common.Address(top.Bytes20())
)
// The funds are burned immediately if the beneficiary is the caller itself,
// in this case, the beneficiary's balance is not increased.
if this != beneficiary {
evm.StateDB.AddBalance(beneficiary, balance, tracing.BalanceIncreaseSelfdestruct)
}
// Clear any leftover funds for the account being destructed.
evm.StateDB.SubBalance(this, balance, tracing.BalanceDecreaseSelfdestruct)
evm.StateDB.SelfDestruct(this)
if tracer := evm.Config.Tracer; tracer != nil { if tracer := evm.Config.Tracer; tracer != nil {
if tracer.OnEnter != nil { if tracer.OnEnter != nil {
tracer.OnEnter(evm.depth, byte(SELFDESTRUCT), scope.Contract.Address(), beneficiary.Bytes20(), []byte{}, 0, balance.ToBig()) tracer.OnEnter(evm.depth, byte(SELFDESTRUCT), this, beneficiary, []byte{}, 0, balance.ToBig())
} }
if tracer.OnExit != nil { if tracer.OnExit != nil {
tracer.OnExit(evm.depth, []byte{}, 0, nil, false) tracer.OnExit(evm.depth, []byte{}, 0, nil, false)
@ -904,14 +913,31 @@ func opSelfdestruct6780(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, erro
if evm.readOnly { if evm.readOnly {
return nil, ErrWriteProtection return nil, ErrWriteProtection
} }
beneficiary := scope.Stack.pop() var (
balance := evm.StateDB.GetBalance(scope.Contract.Address()) this = scope.Contract.Address()
evm.StateDB.SubBalance(scope.Contract.Address(), balance, tracing.BalanceDecreaseSelfdestruct) balance = evm.StateDB.GetBalance(this)
evm.StateDB.AddBalance(beneficiary.Bytes20(), balance, tracing.BalanceIncreaseSelfdestruct) top = scope.Stack.pop()
evm.StateDB.SelfDestruct6780(scope.Contract.Address()) beneficiary = common.Address(top.Bytes20())
newContract = evm.StateDB.IsNewContract(this)
)
// Contract is new and will actually be deleted.
if newContract {
if this != beneficiary { // Skip no-op transfer when self-destructing to self.
evm.StateDB.AddBalance(beneficiary, balance, tracing.BalanceIncreaseSelfdestruct)
}
evm.StateDB.SubBalance(this, balance, tracing.BalanceDecreaseSelfdestruct)
evm.StateDB.SelfDestruct(this)
}
// Contract already exists, only do transfer if beneficiary is not self.
if !newContract && this != beneficiary {
evm.StateDB.SubBalance(this, balance, tracing.BalanceDecreaseSelfdestruct)
evm.StateDB.AddBalance(beneficiary, balance, tracing.BalanceIncreaseSelfdestruct)
}
if tracer := evm.Config.Tracer; tracer != nil { if tracer := evm.Config.Tracer; tracer != nil {
if tracer.OnEnter != nil { if tracer.OnEnter != nil {
tracer.OnEnter(evm.depth, byte(SELFDESTRUCT), scope.Contract.Address(), beneficiary.Bytes20(), []byte{}, 0, balance.ToBig()) tracer.OnEnter(evm.depth, byte(SELFDESTRUCT), this, beneficiary, []byte{}, 0, balance.ToBig())
} }
if tracer.OnExit != nil { if tracer.OnExit != nil {
tracer.OnExit(evm.depth, []byte{}, 0, nil, false) tracer.OnExit(evm.depth, []byte{}, 0, nil, false)
@ -945,11 +971,11 @@ func opDupN(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
code := scope.Contract.Code code := scope.Contract.Code
i := *pc + 1 i := *pc + 1
// Ensure an immediate byte exists after DUPN // If the immediate byte is missing, treat as 0x00 (same convention as PUSHn).
if i >= uint64(len(code)) { var x byte
return nil, &ErrInvalidOpCode{opcode: INVALID} if i < uint64(len(code)) {
x = code[i]
} }
x := code[i]
// This range is excluded to preserve compatibility with existing opcodes. // This range is excluded to preserve compatibility with existing opcodes.
if x > 90 && x < 128 { if x > 90 && x < 128 {
@ -972,11 +998,11 @@ func opSwapN(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
code := scope.Contract.Code code := scope.Contract.Code
i := *pc + 1 i := *pc + 1
// Ensure an immediate byte exists after SWAPN // If the immediate byte is missing, treat as 0x00 (same convention as PUSHn).
if i >= uint64(len(code)) { var x byte
return nil, &ErrInvalidOpCode{opcode: INVALID} if i < uint64(len(code)) {
x = code[i]
} }
x := code[i]
// This range is excluded to preserve compatibility with existing opcodes. // This range is excluded to preserve compatibility with existing opcodes.
if x > 90 && x < 128 { if x > 90 && x < 128 {
@ -1001,11 +1027,11 @@ func opExchange(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
code := scope.Contract.Code code := scope.Contract.Code
i := *pc + 1 i := *pc + 1
// Ensure an immediate byte exists after EXCHANGE // If the immediate byte is missing, treat as 0x00 (same convention as PUSHn).
if i >= uint64(len(code)) { var x byte
return nil, &ErrInvalidOpCode{opcode: INVALID} if i < uint64(len(code)) {
x = code[i]
} }
x := code[i]
// This range is excluded both to preserve compatibility with existing opcodes // This range is excluded both to preserve compatibility with existing opcodes
// and to keep decode_pairs 16-aligned arithmetic mapping valid (079, 128255). // and to keep decode_pairs 16-aligned arithmetic mapping valid (079, 128255).

View file

@ -19,6 +19,7 @@ package vm
import ( import (
"bytes" "bytes"
"encoding/json" "encoding/json"
"errors"
"fmt" "fmt"
"math/big" "math/big"
"os" "os"
@ -1013,10 +1014,11 @@ func TestEIP8024_Execution(t *testing.T) {
evm := NewEVM(BlockContext{}, nil, params.TestChainConfig, Config{}) evm := NewEVM(BlockContext{}, nil, params.TestChainConfig, Config{})
tests := []struct { tests := []struct {
name string name string
codeHex string codeHex string
wantErr bool wantErr error
wantVals []uint64 wantOpcode OpCode
wantVals []uint64
}{ }{
{ {
name: "DUPN", name: "DUPN",
@ -1027,6 +1029,15 @@ func TestEIP8024_Execution(t *testing.T) {
1, 1,
}, },
}, },
{
name: "DUPN_MISSING_IMMEDIATE",
codeHex: "60016000808080808080808080808080808080e6",
wantVals: []uint64{
1,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1,
},
},
{ {
name: "SWAPN", name: "SWAPN",
codeHex: "600160008080808080808080808080808080806002e700", codeHex: "600160008080808080808080808080808080806002e700",
@ -1036,76 +1047,94 @@ func TestEIP8024_Execution(t *testing.T) {
2, 2,
}, },
}, },
{
name: "SWAPN_MISSING_IMMEDIATE",
codeHex: "600160008080808080808080808080808080806002e7",
wantVals: []uint64{
1,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
2,
},
},
{ {
name: "EXCHANGE", name: "EXCHANGE",
codeHex: "600060016002e801", codeHex: "600060016002e801",
wantVals: []uint64{2, 0, 1}, wantVals: []uint64{2, 0, 1},
}, },
{ {
name: "INVALID_SWAPN_LOW", name: "EXCHANGE_MISSING_IMMEDIATE",
codeHex: "e75b", codeHex: "600060006000600060006000600060006000600060006000600060006000600060006000600060006000600060006000600060006000600060016002e8",
wantErr: true, wantVals: []uint64{
2,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1,
},
},
{
name: "INVALID_SWAPN_LOW",
codeHex: "e75b",
wantErr: &ErrInvalidOpCode{},
wantOpcode: SWAPN,
}, },
{ {
name: "JUMP over INVALID_DUPN", name: "JUMP over INVALID_DUPN",
codeHex: "600456e65b", codeHex: "600456e65b",
wantErr: false, wantErr: nil,
},
{
name: "UNDERFLOW_DUPN_1",
codeHex: "6000808080808080808080808080808080e600",
wantErr: &ErrStackUnderflow{},
wantOpcode: DUPN,
}, },
// Additional test cases // Additional test cases
{ {
name: "INVALID_DUPN_LOW", name: "INVALID_DUPN_LOW",
codeHex: "e65b", codeHex: "e65b",
wantErr: true, wantErr: &ErrInvalidOpCode{},
wantOpcode: DUPN,
}, },
{ {
name: "INVALID_EXCHANGE_LOW", name: "INVALID_EXCHANGE_LOW",
codeHex: "e850", codeHex: "e850",
wantErr: true, wantErr: &ErrInvalidOpCode{},
wantOpcode: EXCHANGE,
}, },
{ {
name: "INVALID_DUPN_HIGH", name: "INVALID_DUPN_HIGH",
codeHex: "e67f", codeHex: "e67f",
wantErr: true, wantErr: &ErrInvalidOpCode{},
wantOpcode: DUPN,
}, },
{ {
name: "INVALID_SWAPN_HIGH", name: "INVALID_SWAPN_HIGH",
codeHex: "e77f", codeHex: "e77f",
wantErr: true, wantErr: &ErrInvalidOpCode{},
wantOpcode: SWAPN,
}, },
{ {
name: "INVALID_EXCHANGE_HIGH", name: "INVALID_EXCHANGE_HIGH",
codeHex: "e87f", codeHex: "e87f",
wantErr: true, wantErr: &ErrInvalidOpCode{},
wantOpcode: EXCHANGE,
}, },
{ {
name: "UNDERFLOW_DUPN", name: "UNDERFLOW_DUPN_2",
codeHex: "5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5fe600", // (n=17, need 17 items, have 16) codeHex: "5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5fe600", // (n=17, need 17 items, have 16)
wantErr: true, wantErr: &ErrStackUnderflow{},
wantOpcode: DUPN,
}, },
{ {
name: "UNDERFLOW_SWAPN", name: "UNDERFLOW_SWAPN",
codeHex: "5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5fe700", // (n=17, need 18 items, have 17) codeHex: "5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5fe700", // (n=17, need 18 items, have 17)
wantErr: true, wantErr: &ErrStackUnderflow{},
wantOpcode: SWAPN,
}, },
{ {
name: "UNDERFLOW_EXCHANGE", name: "UNDERFLOW_EXCHANGE",
codeHex: "60016002e801", // (n,m)=(1,2), need 3 items, have 2 codeHex: "60016002e801", // (n,m)=(1,2), need 3 items, have 2
wantErr: true, wantErr: &ErrStackUnderflow{},
}, wantOpcode: EXCHANGE,
{
name: "MISSING_IMMEDIATE_DUPN",
codeHex: "e6", // no operand
wantErr: true,
},
{
name: "MISSING_IMMEDIATE_SWAPN",
codeHex: "e7", // no operand
wantErr: true,
},
{
name: "MISSING_IMMEDIATE_EXCHANGE",
codeHex: "e8", // no operand
wantErr: true,
}, },
{ {
name: "PC_INCREMENT", name: "PC_INCREMENT",
@ -1121,37 +1150,63 @@ func TestEIP8024_Execution(t *testing.T) {
pc := uint64(0) pc := uint64(0)
scope := &ScopeContext{Stack: stack, Contract: &Contract{Code: code}} scope := &ScopeContext{Stack: stack, Contract: &Contract{Code: code}}
var err error var err error
var errOp OpCode
for pc < uint64(len(code)) && err == nil { for pc < uint64(len(code)) && err == nil {
op := code[pc] op := code[pc]
switch op { switch OpCode(op) {
case 0x00: case STOP:
return return
case 0x60: case PUSH1:
_, err = opPush1(&pc, evm, scope) _, err = opPush1(&pc, evm, scope)
case 0x80: case DUP1:
dup1 := makeDup(1) dup1 := makeDup(1)
_, err = dup1(&pc, evm, scope) _, err = dup1(&pc, evm, scope)
case 0x56: case JUMP:
_, err = opJump(&pc, evm, scope) _, err = opJump(&pc, evm, scope)
case 0x5b: case JUMPDEST:
_, err = opJumpdest(&pc, evm, scope) _, err = opJumpdest(&pc, evm, scope)
case 0x15: case ISZERO:
_, err = opIszero(&pc, evm, scope) _, err = opIszero(&pc, evm, scope)
case 0xe6: case PUSH0:
_, err = opPush0(&pc, evm, scope)
case DUPN:
_, err = opDupN(&pc, evm, scope) _, err = opDupN(&pc, evm, scope)
case 0xe7: case SWAPN:
_, err = opSwapN(&pc, evm, scope) _, err = opSwapN(&pc, evm, scope)
case 0xe8: case EXCHANGE:
_, err = opExchange(&pc, evm, scope) _, err = opExchange(&pc, evm, scope)
default: default:
err = &ErrInvalidOpCode{opcode: OpCode(op)} t.Fatalf("unexpected opcode %s at pc=%d", OpCode(op), pc)
}
if err != nil {
errOp = OpCode(op)
} }
pc++ pc++
} }
if tc.wantErr { if tc.wantErr != nil {
// Fail because we wanted an error, but didn't get one.
if err == nil { if err == nil {
t.Fatalf("expected error, got nil") t.Fatalf("expected error, got nil")
} }
// Fail if the wrong opcode threw an error.
if errOp != tc.wantOpcode {
t.Fatalf("expected error from opcode %s, got %s", tc.wantOpcode, errOp)
}
// Fail if we don't get the error we expect.
switch tc.wantErr.(type) {
case *ErrInvalidOpCode:
var want *ErrInvalidOpCode
if !errors.As(err, &want) {
t.Fatalf("expected ErrInvalidOpCode, got %v", err)
}
case *ErrStackUnderflow:
var want *ErrStackUnderflow
if !errors.As(err, &want) {
t.Fatalf("expected ErrStackUnderflow, got %v", err)
}
default:
t.Fatalf("unsupported wantErr type %T", tc.wantErr)
}
return return
} }
if err != nil { if err != nil {

View file

@ -57,19 +57,17 @@ type StateDB interface {
GetTransientState(addr common.Address, key common.Hash) common.Hash GetTransientState(addr common.Address, key common.Hash) common.Hash
SetTransientState(addr common.Address, key, value common.Hash) SetTransientState(addr common.Address, key, value common.Hash)
SelfDestruct(common.Address) uint256.Int SelfDestruct(common.Address)
HasSelfDestructed(common.Address) bool HasSelfDestructed(common.Address) bool
// SelfDestruct6780 is post-EIP6780 selfdestruct, which means that it's a
// send-all-to-beneficiary, unless the contract was created in this same
// transaction, in which case it will be destructed.
// This method returns the prior balance, along with a boolean which is
// true iff the object was indeed destructed.
SelfDestruct6780(common.Address) (uint256.Int, bool)
// Exist reports whether the given account exists in state. // Exist reports whether the given account exists in state.
// Notably this also returns true for self-destructed accounts within the current transaction. // Notably this also returns true for self-destructed accounts within the current transaction.
Exist(common.Address) bool Exist(common.Address) bool
// IsNewContract reports whether the contract at the given address was deployed
// during the current transaction.
IsNewContract(addr common.Address) bool
// Empty returns whether the given account is empty. Empty // Empty returns whether the given account is empty. Empty
// is defined according to EIP161 (balance = nonce = code = 0). // is defined according to EIP161 (balance = nonce = code = 0).
Empty(common.Address) bool Empty(common.Address) bool

View file

@ -28,6 +28,9 @@ import (
func makeGasSStoreFunc(clearingRefund uint64) gasFunc { func makeGasSStoreFunc(clearingRefund uint64) gasFunc {
return func(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { return func(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
if evm.readOnly {
return 0, ErrWriteProtection
}
// If we fail the minimum gas availability invariant, fail (0) // If we fail the minimum gas availability invariant, fail (0)
if contract.Gas <= params.SstoreSentryGasEIP2200 { if contract.Gas <= params.SstoreSentryGasEIP2200 {
return 0, errors.New("not enough gas for reentrancy sentry") return 0, errors.New("not enough gas for reentrancy sentry")
@ -226,10 +229,19 @@ func makeSelfdestructGasFn(refundsEnabled bool) gasFunc {
gas uint64 gas uint64
address = common.Address(stack.peek().Bytes20()) address = common.Address(stack.peek().Bytes20())
) )
if evm.readOnly {
return 0, ErrWriteProtection
}
if !evm.StateDB.AddressInAccessList(address) { if !evm.StateDB.AddressInAccessList(address) {
// If the caller cannot afford the cost, this change will be rolled back // If the caller cannot afford the cost, this change will be rolled back
evm.StateDB.AddAddressToAccessList(address) evm.StateDB.AddAddressToAccessList(address)
gas = params.ColdAccountAccessCostEIP2929 gas = params.ColdAccountAccessCostEIP2929
// Terminate the gas measurement if the leftover gas is not sufficient,
// it can effectively prevent accessing the states in the following steps
if contract.Gas < gas {
return 0, ErrOutOfGas
}
} }
// if empty and transfers value // if empty and transfers value
if evm.StateDB.Empty(address) && evm.StateDB.GetBalance(contract.Address()).Sign() != 0 { if evm.StateDB.Empty(address) && evm.StateDB.GetBalance(contract.Address()).Sign() != 0 {
@ -244,12 +256,24 @@ func makeSelfdestructGasFn(refundsEnabled bool) gasFunc {
} }
var ( var (
gasCallEIP7702 = makeCallVariantGasCallEIP7702(gasCall) innerGasCallEIP7702 = makeCallVariantGasCallEIP7702(gasCall)
gasDelegateCallEIP7702 = makeCallVariantGasCallEIP7702(gasDelegateCall) gasDelegateCallEIP7702 = makeCallVariantGasCallEIP7702(gasDelegateCall)
gasStaticCallEIP7702 = makeCallVariantGasCallEIP7702(gasStaticCall) gasStaticCallEIP7702 = makeCallVariantGasCallEIP7702(gasStaticCall)
gasCallCodeEIP7702 = makeCallVariantGasCallEIP7702(gasCallCode) gasCallCodeEIP7702 = makeCallVariantGasCallEIP7702(gasCallCode)
) )
func gasCallEIP7702(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
// Return early if this call attempts to transfer value in a static context.
// Although it's checked in `gasCall`, EIP-7702 loads the target's code before
// to determine if it is resolving a delegation. This could incorrectly record
// the target in the block access list (BAL) if the call later fails.
transfersValue := !stack.Back(2).IsZero()
if evm.readOnly && transfersValue {
return 0, ErrWriteProtection
}
return innerGasCallEIP7702(evm, contract, stack, mem, memorySize)
}
func makeCallVariantGasCallEIP7702(oldCalculator gasFunc) gasFunc { func makeCallVariantGasCallEIP7702(oldCalculator gasFunc) gasFunc {
return func(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { return func(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
var ( var (

View file

@ -19,14 +19,14 @@ package runtime
import ( import (
"github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/core/vm"
"github.com/holiman/uint256"
) )
func NewEnv(cfg *Config) *vm.EVM { func NewEnv(cfg *Config) *vm.EVM {
txContext := vm.TxContext{ txContext := vm.TxContext{
Origin: cfg.Origin, Origin: cfg.Origin,
GasPrice: cfg.GasPrice, GasPrice: uint256.MustFromBig(cfg.GasPrice),
BlobHashes: cfg.BlobHashes, BlobHashes: cfg.BlobHashes,
BlobFeeCap: cfg.BlobFeeCap,
} }
blockContext := vm.BlockContext{ blockContext := vm.BlockContext{
CanTransfer: core.CanTransfer, CanTransfer: core.CanTransfer,

View file

@ -124,6 +124,9 @@ func (prv *PrivateKey) GenerateShared(pub *PublicKey, skLen, macLen int) (sk []b
if prv.PublicKey.Curve != pub.Curve { if prv.PublicKey.Curve != pub.Curve {
return nil, ErrInvalidCurve return nil, ErrInvalidCurve
} }
if pub.X == nil || pub.Y == nil || !pub.Curve.IsOnCurve(pub.X, pub.Y) {
return nil, ErrInvalidPublicKey
}
if skLen+macLen > MaxSharedKeyLength(pub) { if skLen+macLen > MaxSharedKeyLength(pub) {
return nil, ErrSharedKeyTooBig return nil, ErrSharedKeyTooBig
} }

View file

@ -22,17 +22,17 @@ import (
"sync" "sync"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"golang.org/x/crypto/sha3" "github.com/ethereum/go-ethereum/crypto/keccak"
) )
// NewKeccakState creates a new KeccakState // NewKeccakState creates a new KeccakState
func NewKeccakState() KeccakState { func NewKeccakState() KeccakState {
return sha3.NewLegacyKeccak256().(KeccakState) return keccak.NewLegacyKeccak256().(KeccakState)
} }
var hasherPool = sync.Pool{ var hasherPool = sync.Pool{
New: func() any { New: func() any {
return sha3.NewLegacyKeccak256().(KeccakState) return keccak.NewLegacyKeccak256().(KeccakState)
}, },
} }

27
crypto/keccak/LICENSE Normal file
View file

@ -0,0 +1,27 @@
Copyright 2009 The Go Authors.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Google LLC nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

6
crypto/keccak/README.md Normal file
View file

@ -0,0 +1,6 @@
This is a vendored and modified copy of golang.org/x/crypto/sha3, with an assembly
implementation of keccak256. We wish to retain the assembly implementation,
which was removed in v0.44.0.
Ethereum uses a 'legacy' variant of Keccak, which was defined before it became SHA3. As
such, we cannot use the standard library crypto/sha3 package.

44
crypto/keccak/hashes.go Normal file
View file

@ -0,0 +1,44 @@
// Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package keccak
// This file provides functions for creating instances of the SHA-3
// and SHAKE hash functions, as well as utility functions for hashing
// bytes.
import (
"hash"
)
const (
dsbyteSHA3 = 0b00000110
dsbyteKeccak = 0b00000001
dsbyteShake = 0b00011111
dsbyteCShake = 0b00000100
// rateK[c] is the rate in bytes for Keccak[c] where c is the capacity in
// bits. Given the sponge size is 1600 bits, the rate is 1600 - c bits.
rateK256 = (1600 - 256) / 8
rateK448 = (1600 - 448) / 8
rateK512 = (1600 - 512) / 8
rateK768 = (1600 - 768) / 8
rateK1024 = (1600 - 1024) / 8
)
// NewLegacyKeccak256 creates a new Keccak-256 hash.
//
// Only use this function if you require compatibility with an existing cryptosystem
// that uses non-standard padding. All other users should use New256 instead.
func NewLegacyKeccak256() hash.Hash {
return &state{rate: rateK512, outputLen: 32, dsbyte: dsbyteKeccak}
}
// NewLegacyKeccak512 creates a new Keccak-512 hash.
//
// Only use this function if you require compatibility with an existing cryptosystem
// that uses non-standard padding. All other users should use New512 instead.
func NewLegacyKeccak512() hash.Hash {
return &state{rate: rateK1024, outputLen: 64, dsbyte: dsbyteKeccak}
}

414
crypto/keccak/keccakf.go Normal file
View file

@ -0,0 +1,414 @@
// Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build !amd64 || purego || !gc
package keccak
import "math/bits"
// rc stores the round constants for use in the ι step.
var rc = [24]uint64{
0x0000000000000001,
0x0000000000008082,
0x800000000000808A,
0x8000000080008000,
0x000000000000808B,
0x0000000080000001,
0x8000000080008081,
0x8000000000008009,
0x000000000000008A,
0x0000000000000088,
0x0000000080008009,
0x000000008000000A,
0x000000008000808B,
0x800000000000008B,
0x8000000000008089,
0x8000000000008003,
0x8000000000008002,
0x8000000000000080,
0x000000000000800A,
0x800000008000000A,
0x8000000080008081,
0x8000000000008080,
0x0000000080000001,
0x8000000080008008,
}
// keccakF1600 applies the Keccak permutation to a 1600b-wide
// state represented as a slice of 25 uint64s.
func keccakF1600(a *[25]uint64) {
// Implementation translated from Keccak-inplace.c
// in the keccak reference code.
var t, bc0, bc1, bc2, bc3, bc4, d0, d1, d2, d3, d4 uint64
for i := 0; i < 24; i += 4 {
// Combines the 5 steps in each round into 2 steps.
// Unrolls 4 rounds per loop and spreads some steps across rounds.
// Round 1
bc0 = a[0] ^ a[5] ^ a[10] ^ a[15] ^ a[20]
bc1 = a[1] ^ a[6] ^ a[11] ^ a[16] ^ a[21]
bc2 = a[2] ^ a[7] ^ a[12] ^ a[17] ^ a[22]
bc3 = a[3] ^ a[8] ^ a[13] ^ a[18] ^ a[23]
bc4 = a[4] ^ a[9] ^ a[14] ^ a[19] ^ a[24]
d0 = bc4 ^ (bc1<<1 | bc1>>63)
d1 = bc0 ^ (bc2<<1 | bc2>>63)
d2 = bc1 ^ (bc3<<1 | bc3>>63)
d3 = bc2 ^ (bc4<<1 | bc4>>63)
d4 = bc3 ^ (bc0<<1 | bc0>>63)
bc0 = a[0] ^ d0
t = a[6] ^ d1
bc1 = bits.RotateLeft64(t, 44)
t = a[12] ^ d2
bc2 = bits.RotateLeft64(t, 43)
t = a[18] ^ d3
bc3 = bits.RotateLeft64(t, 21)
t = a[24] ^ d4
bc4 = bits.RotateLeft64(t, 14)
a[0] = bc0 ^ (bc2 &^ bc1) ^ rc[i]
a[6] = bc1 ^ (bc3 &^ bc2)
a[12] = bc2 ^ (bc4 &^ bc3)
a[18] = bc3 ^ (bc0 &^ bc4)
a[24] = bc4 ^ (bc1 &^ bc0)
t = a[10] ^ d0
bc2 = bits.RotateLeft64(t, 3)
t = a[16] ^ d1
bc3 = bits.RotateLeft64(t, 45)
t = a[22] ^ d2
bc4 = bits.RotateLeft64(t, 61)
t = a[3] ^ d3
bc0 = bits.RotateLeft64(t, 28)
t = a[9] ^ d4
bc1 = bits.RotateLeft64(t, 20)
a[10] = bc0 ^ (bc2 &^ bc1)
a[16] = bc1 ^ (bc3 &^ bc2)
a[22] = bc2 ^ (bc4 &^ bc3)
a[3] = bc3 ^ (bc0 &^ bc4)
a[9] = bc4 ^ (bc1 &^ bc0)
t = a[20] ^ d0
bc4 = bits.RotateLeft64(t, 18)
t = a[1] ^ d1
bc0 = bits.RotateLeft64(t, 1)
t = a[7] ^ d2
bc1 = bits.RotateLeft64(t, 6)
t = a[13] ^ d3
bc2 = bits.RotateLeft64(t, 25)
t = a[19] ^ d4
bc3 = bits.RotateLeft64(t, 8)
a[20] = bc0 ^ (bc2 &^ bc1)
a[1] = bc1 ^ (bc3 &^ bc2)
a[7] = bc2 ^ (bc4 &^ bc3)
a[13] = bc3 ^ (bc0 &^ bc4)
a[19] = bc4 ^ (bc1 &^ bc0)
t = a[5] ^ d0
bc1 = bits.RotateLeft64(t, 36)
t = a[11] ^ d1
bc2 = bits.RotateLeft64(t, 10)
t = a[17] ^ d2
bc3 = bits.RotateLeft64(t, 15)
t = a[23] ^ d3
bc4 = bits.RotateLeft64(t, 56)
t = a[4] ^ d4
bc0 = bits.RotateLeft64(t, 27)
a[5] = bc0 ^ (bc2 &^ bc1)
a[11] = bc1 ^ (bc3 &^ bc2)
a[17] = bc2 ^ (bc4 &^ bc3)
a[23] = bc3 ^ (bc0 &^ bc4)
a[4] = bc4 ^ (bc1 &^ bc0)
t = a[15] ^ d0
bc3 = bits.RotateLeft64(t, 41)
t = a[21] ^ d1
bc4 = bits.RotateLeft64(t, 2)
t = a[2] ^ d2
bc0 = bits.RotateLeft64(t, 62)
t = a[8] ^ d3
bc1 = bits.RotateLeft64(t, 55)
t = a[14] ^ d4
bc2 = bits.RotateLeft64(t, 39)
a[15] = bc0 ^ (bc2 &^ bc1)
a[21] = bc1 ^ (bc3 &^ bc2)
a[2] = bc2 ^ (bc4 &^ bc3)
a[8] = bc3 ^ (bc0 &^ bc4)
a[14] = bc4 ^ (bc1 &^ bc0)
// Round 2
bc0 = a[0] ^ a[5] ^ a[10] ^ a[15] ^ a[20]
bc1 = a[1] ^ a[6] ^ a[11] ^ a[16] ^ a[21]
bc2 = a[2] ^ a[7] ^ a[12] ^ a[17] ^ a[22]
bc3 = a[3] ^ a[8] ^ a[13] ^ a[18] ^ a[23]
bc4 = a[4] ^ a[9] ^ a[14] ^ a[19] ^ a[24]
d0 = bc4 ^ (bc1<<1 | bc1>>63)
d1 = bc0 ^ (bc2<<1 | bc2>>63)
d2 = bc1 ^ (bc3<<1 | bc3>>63)
d3 = bc2 ^ (bc4<<1 | bc4>>63)
d4 = bc3 ^ (bc0<<1 | bc0>>63)
bc0 = a[0] ^ d0
t = a[16] ^ d1
bc1 = bits.RotateLeft64(t, 44)
t = a[7] ^ d2
bc2 = bits.RotateLeft64(t, 43)
t = a[23] ^ d3
bc3 = bits.RotateLeft64(t, 21)
t = a[14] ^ d4
bc4 = bits.RotateLeft64(t, 14)
a[0] = bc0 ^ (bc2 &^ bc1) ^ rc[i+1]
a[16] = bc1 ^ (bc3 &^ bc2)
a[7] = bc2 ^ (bc4 &^ bc3)
a[23] = bc3 ^ (bc0 &^ bc4)
a[14] = bc4 ^ (bc1 &^ bc0)
t = a[20] ^ d0
bc2 = bits.RotateLeft64(t, 3)
t = a[11] ^ d1
bc3 = bits.RotateLeft64(t, 45)
t = a[2] ^ d2
bc4 = bits.RotateLeft64(t, 61)
t = a[18] ^ d3
bc0 = bits.RotateLeft64(t, 28)
t = a[9] ^ d4
bc1 = bits.RotateLeft64(t, 20)
a[20] = bc0 ^ (bc2 &^ bc1)
a[11] = bc1 ^ (bc3 &^ bc2)
a[2] = bc2 ^ (bc4 &^ bc3)
a[18] = bc3 ^ (bc0 &^ bc4)
a[9] = bc4 ^ (bc1 &^ bc0)
t = a[15] ^ d0
bc4 = bits.RotateLeft64(t, 18)
t = a[6] ^ d1
bc0 = bits.RotateLeft64(t, 1)
t = a[22] ^ d2
bc1 = bits.RotateLeft64(t, 6)
t = a[13] ^ d3
bc2 = bits.RotateLeft64(t, 25)
t = a[4] ^ d4
bc3 = bits.RotateLeft64(t, 8)
a[15] = bc0 ^ (bc2 &^ bc1)
a[6] = bc1 ^ (bc3 &^ bc2)
a[22] = bc2 ^ (bc4 &^ bc3)
a[13] = bc3 ^ (bc0 &^ bc4)
a[4] = bc4 ^ (bc1 &^ bc0)
t = a[10] ^ d0
bc1 = bits.RotateLeft64(t, 36)
t = a[1] ^ d1
bc2 = bits.RotateLeft64(t, 10)
t = a[17] ^ d2
bc3 = bits.RotateLeft64(t, 15)
t = a[8] ^ d3
bc4 = bits.RotateLeft64(t, 56)
t = a[24] ^ d4
bc0 = bits.RotateLeft64(t, 27)
a[10] = bc0 ^ (bc2 &^ bc1)
a[1] = bc1 ^ (bc3 &^ bc2)
a[17] = bc2 ^ (bc4 &^ bc3)
a[8] = bc3 ^ (bc0 &^ bc4)
a[24] = bc4 ^ (bc1 &^ bc0)
t = a[5] ^ d0
bc3 = bits.RotateLeft64(t, 41)
t = a[21] ^ d1
bc4 = bits.RotateLeft64(t, 2)
t = a[12] ^ d2
bc0 = bits.RotateLeft64(t, 62)
t = a[3] ^ d3
bc1 = bits.RotateLeft64(t, 55)
t = a[19] ^ d4
bc2 = bits.RotateLeft64(t, 39)
a[5] = bc0 ^ (bc2 &^ bc1)
a[21] = bc1 ^ (bc3 &^ bc2)
a[12] = bc2 ^ (bc4 &^ bc3)
a[3] = bc3 ^ (bc0 &^ bc4)
a[19] = bc4 ^ (bc1 &^ bc0)
// Round 3
bc0 = a[0] ^ a[5] ^ a[10] ^ a[15] ^ a[20]
bc1 = a[1] ^ a[6] ^ a[11] ^ a[16] ^ a[21]
bc2 = a[2] ^ a[7] ^ a[12] ^ a[17] ^ a[22]
bc3 = a[3] ^ a[8] ^ a[13] ^ a[18] ^ a[23]
bc4 = a[4] ^ a[9] ^ a[14] ^ a[19] ^ a[24]
d0 = bc4 ^ (bc1<<1 | bc1>>63)
d1 = bc0 ^ (bc2<<1 | bc2>>63)
d2 = bc1 ^ (bc3<<1 | bc3>>63)
d3 = bc2 ^ (bc4<<1 | bc4>>63)
d4 = bc3 ^ (bc0<<1 | bc0>>63)
bc0 = a[0] ^ d0
t = a[11] ^ d1
bc1 = bits.RotateLeft64(t, 44)
t = a[22] ^ d2
bc2 = bits.RotateLeft64(t, 43)
t = a[8] ^ d3
bc3 = bits.RotateLeft64(t, 21)
t = a[19] ^ d4
bc4 = bits.RotateLeft64(t, 14)
a[0] = bc0 ^ (bc2 &^ bc1) ^ rc[i+2]
a[11] = bc1 ^ (bc3 &^ bc2)
a[22] = bc2 ^ (bc4 &^ bc3)
a[8] = bc3 ^ (bc0 &^ bc4)
a[19] = bc4 ^ (bc1 &^ bc0)
t = a[15] ^ d0
bc2 = bits.RotateLeft64(t, 3)
t = a[1] ^ d1
bc3 = bits.RotateLeft64(t, 45)
t = a[12] ^ d2
bc4 = bits.RotateLeft64(t, 61)
t = a[23] ^ d3
bc0 = bits.RotateLeft64(t, 28)
t = a[9] ^ d4
bc1 = bits.RotateLeft64(t, 20)
a[15] = bc0 ^ (bc2 &^ bc1)
a[1] = bc1 ^ (bc3 &^ bc2)
a[12] = bc2 ^ (bc4 &^ bc3)
a[23] = bc3 ^ (bc0 &^ bc4)
a[9] = bc4 ^ (bc1 &^ bc0)
t = a[5] ^ d0
bc4 = bits.RotateLeft64(t, 18)
t = a[16] ^ d1
bc0 = bits.RotateLeft64(t, 1)
t = a[2] ^ d2
bc1 = bits.RotateLeft64(t, 6)
t = a[13] ^ d3
bc2 = bits.RotateLeft64(t, 25)
t = a[24] ^ d4
bc3 = bits.RotateLeft64(t, 8)
a[5] = bc0 ^ (bc2 &^ bc1)
a[16] = bc1 ^ (bc3 &^ bc2)
a[2] = bc2 ^ (bc4 &^ bc3)
a[13] = bc3 ^ (bc0 &^ bc4)
a[24] = bc4 ^ (bc1 &^ bc0)
t = a[20] ^ d0
bc1 = bits.RotateLeft64(t, 36)
t = a[6] ^ d1
bc2 = bits.RotateLeft64(t, 10)
t = a[17] ^ d2
bc3 = bits.RotateLeft64(t, 15)
t = a[3] ^ d3
bc4 = bits.RotateLeft64(t, 56)
t = a[14] ^ d4
bc0 = bits.RotateLeft64(t, 27)
a[20] = bc0 ^ (bc2 &^ bc1)
a[6] = bc1 ^ (bc3 &^ bc2)
a[17] = bc2 ^ (bc4 &^ bc3)
a[3] = bc3 ^ (bc0 &^ bc4)
a[14] = bc4 ^ (bc1 &^ bc0)
t = a[10] ^ d0
bc3 = bits.RotateLeft64(t, 41)
t = a[21] ^ d1
bc4 = bits.RotateLeft64(t, 2)
t = a[7] ^ d2
bc0 = bits.RotateLeft64(t, 62)
t = a[18] ^ d3
bc1 = bits.RotateLeft64(t, 55)
t = a[4] ^ d4
bc2 = bits.RotateLeft64(t, 39)
a[10] = bc0 ^ (bc2 &^ bc1)
a[21] = bc1 ^ (bc3 &^ bc2)
a[7] = bc2 ^ (bc4 &^ bc3)
a[18] = bc3 ^ (bc0 &^ bc4)
a[4] = bc4 ^ (bc1 &^ bc0)
// Round 4
bc0 = a[0] ^ a[5] ^ a[10] ^ a[15] ^ a[20]
bc1 = a[1] ^ a[6] ^ a[11] ^ a[16] ^ a[21]
bc2 = a[2] ^ a[7] ^ a[12] ^ a[17] ^ a[22]
bc3 = a[3] ^ a[8] ^ a[13] ^ a[18] ^ a[23]
bc4 = a[4] ^ a[9] ^ a[14] ^ a[19] ^ a[24]
d0 = bc4 ^ (bc1<<1 | bc1>>63)
d1 = bc0 ^ (bc2<<1 | bc2>>63)
d2 = bc1 ^ (bc3<<1 | bc3>>63)
d3 = bc2 ^ (bc4<<1 | bc4>>63)
d4 = bc3 ^ (bc0<<1 | bc0>>63)
bc0 = a[0] ^ d0
t = a[1] ^ d1
bc1 = bits.RotateLeft64(t, 44)
t = a[2] ^ d2
bc2 = bits.RotateLeft64(t, 43)
t = a[3] ^ d3
bc3 = bits.RotateLeft64(t, 21)
t = a[4] ^ d4
bc4 = bits.RotateLeft64(t, 14)
a[0] = bc0 ^ (bc2 &^ bc1) ^ rc[i+3]
a[1] = bc1 ^ (bc3 &^ bc2)
a[2] = bc2 ^ (bc4 &^ bc3)
a[3] = bc3 ^ (bc0 &^ bc4)
a[4] = bc4 ^ (bc1 &^ bc0)
t = a[5] ^ d0
bc2 = bits.RotateLeft64(t, 3)
t = a[6] ^ d1
bc3 = bits.RotateLeft64(t, 45)
t = a[7] ^ d2
bc4 = bits.RotateLeft64(t, 61)
t = a[8] ^ d3
bc0 = bits.RotateLeft64(t, 28)
t = a[9] ^ d4
bc1 = bits.RotateLeft64(t, 20)
a[5] = bc0 ^ (bc2 &^ bc1)
a[6] = bc1 ^ (bc3 &^ bc2)
a[7] = bc2 ^ (bc4 &^ bc3)
a[8] = bc3 ^ (bc0 &^ bc4)
a[9] = bc4 ^ (bc1 &^ bc0)
t = a[10] ^ d0
bc4 = bits.RotateLeft64(t, 18)
t = a[11] ^ d1
bc0 = bits.RotateLeft64(t, 1)
t = a[12] ^ d2
bc1 = bits.RotateLeft64(t, 6)
t = a[13] ^ d3
bc2 = bits.RotateLeft64(t, 25)
t = a[14] ^ d4
bc3 = bits.RotateLeft64(t, 8)
a[10] = bc0 ^ (bc2 &^ bc1)
a[11] = bc1 ^ (bc3 &^ bc2)
a[12] = bc2 ^ (bc4 &^ bc3)
a[13] = bc3 ^ (bc0 &^ bc4)
a[14] = bc4 ^ (bc1 &^ bc0)
t = a[15] ^ d0
bc1 = bits.RotateLeft64(t, 36)
t = a[16] ^ d1
bc2 = bits.RotateLeft64(t, 10)
t = a[17] ^ d2
bc3 = bits.RotateLeft64(t, 15)
t = a[18] ^ d3
bc4 = bits.RotateLeft64(t, 56)
t = a[19] ^ d4
bc0 = bits.RotateLeft64(t, 27)
a[15] = bc0 ^ (bc2 &^ bc1)
a[16] = bc1 ^ (bc3 &^ bc2)
a[17] = bc2 ^ (bc4 &^ bc3)
a[18] = bc3 ^ (bc0 &^ bc4)
a[19] = bc4 ^ (bc1 &^ bc0)
t = a[20] ^ d0
bc3 = bits.RotateLeft64(t, 41)
t = a[21] ^ d1
bc4 = bits.RotateLeft64(t, 2)
t = a[22] ^ d2
bc0 = bits.RotateLeft64(t, 62)
t = a[23] ^ d3
bc1 = bits.RotateLeft64(t, 55)
t = a[24] ^ d4
bc2 = bits.RotateLeft64(t, 39)
a[20] = bc0 ^ (bc2 &^ bc1)
a[21] = bc1 ^ (bc3 &^ bc2)
a[22] = bc2 ^ (bc4 &^ bc3)
a[23] = bc3 ^ (bc0 &^ bc4)
a[24] = bc4 ^ (bc1 &^ bc0)
}
}

View file

@ -0,0 +1,13 @@
// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build amd64 && !purego && gc
package keccak
// This function is implemented in keccakf_amd64.s.
//go:noescape
func keccakF1600(a *[25]uint64)

File diff suppressed because it is too large Load diff

244
crypto/keccak/sha3.go Normal file
View file

@ -0,0 +1,244 @@
// Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package keccak
import (
"crypto/subtle"
"encoding/binary"
"errors"
"unsafe"
"golang.org/x/sys/cpu"
)
// spongeDirection indicates the direction bytes are flowing through the sponge.
type spongeDirection int
const (
// spongeAbsorbing indicates that the sponge is absorbing input.
spongeAbsorbing spongeDirection = iota
// spongeSqueezing indicates that the sponge is being squeezed.
spongeSqueezing
)
type state struct {
a [1600 / 8]byte // main state of the hash
// a[n:rate] is the buffer. If absorbing, it's the remaining space to XOR
// into before running the permutation. If squeezing, it's the remaining
// output to produce before running the permutation.
n, rate int
// dsbyte contains the "domain separation" bits and the first bit of
// the padding. Sections 6.1 and 6.2 of [1] separate the outputs of the
// SHA-3 and SHAKE functions by appending bitstrings to the message.
// Using a little-endian bit-ordering convention, these are "01" for SHA-3
// and "1111" for SHAKE, or 00000010b and 00001111b, respectively. Then the
// padding rule from section 5.1 is applied to pad the message to a multiple
// of the rate, which involves adding a "1" bit, zero or more "0" bits, and
// a final "1" bit. We merge the first "1" bit from the padding into dsbyte,
// giving 00000110b (0x06) and 00011111b (0x1f).
// [1] http://csrc.nist.gov/publications/drafts/fips-202/fips_202_draft.pdf
// "Draft FIPS 202: SHA-3 Standard: Permutation-Based Hash and
// Extendable-Output Functions (May 2014)"
dsbyte byte
outputLen int // the default output size in bytes
state spongeDirection // whether the sponge is absorbing or squeezing
}
// BlockSize returns the rate of sponge underlying this hash function.
func (d *state) BlockSize() int { return d.rate }
// Size returns the output size of the hash function in bytes.
func (d *state) Size() int { return d.outputLen }
// Reset clears the internal state by zeroing the sponge state and
// the buffer indexes, and setting Sponge.state to absorbing.
func (d *state) Reset() {
// Zero the permutation's state.
for i := range d.a {
d.a[i] = 0
}
d.state = spongeAbsorbing
d.n = 0
}
func (d *state) clone() *state {
ret := *d
return &ret
}
// permute applies the KeccakF-1600 permutation.
func (d *state) permute() {
var a *[25]uint64
if cpu.IsBigEndian {
a = new([25]uint64)
for i := range a {
a[i] = binary.LittleEndian.Uint64(d.a[i*8:])
}
} else {
a = (*[25]uint64)(unsafe.Pointer(&d.a))
}
keccakF1600(a)
d.n = 0
if cpu.IsBigEndian {
for i := range a {
binary.LittleEndian.PutUint64(d.a[i*8:], a[i])
}
}
}
// pads appends the domain separation bits in dsbyte, applies
// the multi-bitrate 10..1 padding rule, and permutes the state.
func (d *state) padAndPermute() {
// Pad with this instance's domain-separator bits. We know that there's
// at least one byte of space in the sponge because, if it were full,
// permute would have been called to empty it. dsbyte also contains the
// first one bit for the padding. See the comment in the state struct.
d.a[d.n] ^= d.dsbyte
// This adds the final one bit for the padding. Because of the way that
// bits are numbered from the LSB upwards, the final bit is the MSB of
// the last byte.
d.a[d.rate-1] ^= 0x80
// Apply the permutation
d.permute()
d.state = spongeSqueezing
}
// Write absorbs more data into the hash's state. It panics if any
// output has already been read.
func (d *state) Write(p []byte) (n int, err error) {
if d.state != spongeAbsorbing {
panic("sha3: Write after Read")
}
n = len(p)
for len(p) > 0 {
x := subtle.XORBytes(d.a[d.n:d.rate], d.a[d.n:d.rate], p)
d.n += x
p = p[x:]
// If the sponge is full, apply the permutation.
if d.n == d.rate {
d.permute()
}
}
return
}
// Read squeezes an arbitrary number of bytes from the sponge.
func (d *state) Read(out []byte) (n int, err error) {
// If we're still absorbing, pad and apply the permutation.
if d.state == spongeAbsorbing {
d.padAndPermute()
}
n = len(out)
// Now, do the squeezing.
for len(out) > 0 {
// Apply the permutation if we've squeezed the sponge dry.
if d.n == d.rate {
d.permute()
}
x := copy(out, d.a[d.n:d.rate])
d.n += x
out = out[x:]
}
return
}
// Sum applies padding to the hash state and then squeezes out the desired
// number of output bytes. It panics if any output has already been read.
func (d *state) Sum(in []byte) []byte {
if d.state != spongeAbsorbing {
panic("sha3: Sum after Read")
}
// Make a copy of the original hash so that caller can keep writing
// and summing.
dup := d.clone()
hash := make([]byte, dup.outputLen, 64) // explicit cap to allow stack allocation
dup.Read(hash)
return append(in, hash...)
}
const (
magicSHA3 = "sha\x08"
magicShake = "sha\x09"
magicCShake = "sha\x0a"
magicKeccak = "sha\x0b"
// magic || rate || main state || n || sponge direction
marshaledSize = len(magicSHA3) + 1 + 200 + 1 + 1
)
func (d *state) MarshalBinary() ([]byte, error) {
return d.AppendBinary(make([]byte, 0, marshaledSize))
}
func (d *state) AppendBinary(b []byte) ([]byte, error) {
switch d.dsbyte {
case dsbyteSHA3:
b = append(b, magicSHA3...)
case dsbyteShake:
b = append(b, magicShake...)
case dsbyteCShake:
b = append(b, magicCShake...)
case dsbyteKeccak:
b = append(b, magicKeccak...)
default:
panic("unknown dsbyte")
}
// rate is at most 168, and n is at most rate.
b = append(b, byte(d.rate))
b = append(b, d.a[:]...)
b = append(b, byte(d.n), byte(d.state))
return b, nil
}
func (d *state) UnmarshalBinary(b []byte) error {
if len(b) != marshaledSize {
return errors.New("sha3: invalid hash state")
}
magic := string(b[:len(magicSHA3)])
b = b[len(magicSHA3):]
switch {
case magic == magicSHA3 && d.dsbyte == dsbyteSHA3:
case magic == magicShake && d.dsbyte == dsbyteShake:
case magic == magicCShake && d.dsbyte == dsbyteCShake:
case magic == magicKeccak && d.dsbyte == dsbyteKeccak:
default:
return errors.New("sha3: invalid hash state identifier")
}
rate := int(b[0])
b = b[1:]
if rate != d.rate {
return errors.New("sha3: invalid hash state function")
}
copy(d.a[:], b)
b = b[len(d.a):]
n, state := int(b[0]), spongeDirection(b[1])
if n > d.rate {
return errors.New("sha3: invalid hash state")
}
d.n = n
if state != spongeAbsorbing && state != spongeSqueezing {
return errors.New("sha3: invalid hash state")
}
d.state = state
return nil
}

210
crypto/keccak/sha3_test.go Normal file
View file

@ -0,0 +1,210 @@
// Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package keccak
// Tests include all the ShortMsgKATs provided by the Keccak team at
// https://github.com/gvanas/KeccakCodePackage
//
// They only include the zero-bit case of the bitwise testvectors
// published by NIST in the draft of FIPS-202.
import (
"bytes"
"compress/flate"
"encoding"
"encoding/hex"
"encoding/json"
"hash"
"math/rand"
"os"
"strings"
"testing"
)
const (
testString = "brekeccakkeccak koax koax"
katFilename = "testdata/keccakKats.json.deflate"
)
// testDigests contains functions returning hash.Hash instances
// with output-length equal to the KAT length for SHA-3, Keccak
// and SHAKE instances.
var testDigests = map[string]func() hash.Hash{
"Keccak-256": NewLegacyKeccak256,
"Keccak-512": NewLegacyKeccak512,
}
// decodeHex converts a hex-encoded string into a raw byte string.
func decodeHex(s string) []byte {
b, err := hex.DecodeString(s)
if err != nil {
panic(err)
}
return b
}
// structs used to marshal JSON test-cases.
type KeccakKats struct {
Kats map[string][]struct {
Digest string `json:"digest"`
Length int64 `json:"length"`
Message string `json:"message"`
// Defined only for cSHAKE
N string `json:"N"`
S string `json:"S"`
}
}
// TestKeccakKats tests the SHA-3 and Shake implementations against all the
// ShortMsgKATs from https://github.com/gvanas/KeccakCodePackage
// (The testvectors are stored in keccakKats.json.deflate due to their length.)
func TestKeccakKats(t *testing.T) {
// Read the KATs.
deflated, err := os.Open(katFilename)
if err != nil {
t.Errorf("error opening %s: %s", katFilename, err)
}
file := flate.NewReader(deflated)
dec := json.NewDecoder(file)
var katSet KeccakKats
err = dec.Decode(&katSet)
if err != nil {
t.Errorf("error decoding KATs: %s", err)
}
for algo, function := range testDigests {
d := function()
for _, kat := range katSet.Kats[algo] {
d.Reset()
in, err := hex.DecodeString(kat.Message)
if err != nil {
t.Errorf("error decoding KAT: %s", err)
}
d.Write(in[:kat.Length/8])
got := strings.ToUpper(hex.EncodeToString(d.Sum(nil)))
if got != kat.Digest {
t.Errorf("function=%s, length=%d\nmessage:\n %s\ngot:\n %s\nwanted:\n %s",
algo, kat.Length, kat.Message, got, kat.Digest)
t.Logf("wanted %+v", kat)
t.FailNow()
}
continue
}
}
}
// TestKeccak does a basic test of the non-standardized Keccak hash functions.
func TestKeccak(t *testing.T) {
tests := []struct {
fn func() hash.Hash
data []byte
want string
}{
{
NewLegacyKeccak256,
[]byte("abc"),
"4e03657aea45a94fc7d47ba826c8d667c0d1e6e33a64a036ec44f58fa12d6c45",
},
{
NewLegacyKeccak512,
[]byte("abc"),
"18587dc2ea106b9a1563e32b3312421ca164c7f1f07bc922a9c83d77cea3a1e5d0c69910739025372dc14ac9642629379540c17e2a65b19d77aa511a9d00bb96",
},
}
for _, u := range tests {
h := u.fn()
h.Write(u.data)
got := h.Sum(nil)
want := decodeHex(u.want)
if !bytes.Equal(got, want) {
t.Errorf("unexpected hash for size %d: got '%x' want '%s'", h.Size()*8, got, u.want)
}
}
}
// TestUnalignedWrite tests that writing data in an arbitrary pattern with
// small input buffers.
func TestUnalignedWrite(t *testing.T) {
buf := sequentialBytes(0x10000)
for alg, df := range testDigests {
d := df()
d.Reset()
d.Write(buf)
want := d.Sum(nil)
d.Reset()
for i := 0; i < len(buf); {
// Cycle through offsets which make a 137 byte sequence.
// Because 137 is prime this sequence should exercise all corner cases.
offsets := [17]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 1}
for _, j := range offsets {
if v := len(buf) - i; v < j {
j = v
}
d.Write(buf[i : i+j])
i += j
}
}
got := d.Sum(nil)
if !bytes.Equal(got, want) {
t.Errorf("Unaligned writes, alg=%s\ngot %q, want %q", alg, got, want)
}
}
}
// sequentialBytes produces a buffer of size consecutive bytes 0x00, 0x01, ..., used for testing.
//
// The alignment of each slice is intentionally randomized to detect alignment
// issues in the implementation. See https://golang.org/issue/37644.
// Ideally, the compiler should fuzz the alignment itself.
// (See https://golang.org/issue/35128.)
func sequentialBytes(size int) []byte {
alignmentOffset := rand.Intn(8)
result := make([]byte, size+alignmentOffset)[alignmentOffset:]
for i := range result {
result[i] = byte(i)
}
return result
}
func TestMarshalUnmarshal(t *testing.T) {
t.Run("Keccak-256", func(t *testing.T) { testMarshalUnmarshal(t, NewLegacyKeccak256()) })
t.Run("Keccak-512", func(t *testing.T) { testMarshalUnmarshal(t, NewLegacyKeccak512()) })
}
// TODO(filippo): move this to crypto/internal/cryptotest.
func testMarshalUnmarshal(t *testing.T, h hash.Hash) {
buf := make([]byte, 200)
rand.Read(buf)
n := rand.Intn(200)
h.Write(buf)
want := h.Sum(nil)
h.Reset()
h.Write(buf[:n])
b, err := h.(encoding.BinaryMarshaler).MarshalBinary()
if err != nil {
t.Errorf("MarshalBinary: %v", err)
}
h.Write(bytes.Repeat([]byte{0}, 200))
if err := h.(encoding.BinaryUnmarshaler).UnmarshalBinary(b); err != nil {
t.Errorf("UnmarshalBinary: %v", err)
}
h.Write(buf[n:])
got := h.Sum(nil)
if !bytes.Equal(got, want) {
t.Errorf("got %x, want %x", got, want)
}
}
// BenchmarkPermutationFunction measures the speed of the permutation function
// with no input data.
func BenchmarkPermutationFunction(b *testing.B) {
b.SetBytes(int64(200))
var lanes [25]uint64
for i := 0; i < b.N; i++ {
keccakF1600(&lanes)
}
}

Binary file not shown.

View file

@ -143,9 +143,9 @@ func ckzgComputeCellProofs(blob *Blob) ([]Proof, error) {
if err != nil { if err != nil {
return []Proof{}, err return []Proof{}, err
} }
var p []Proof p := make([]Proof, len(proofs))
for _, proof := range proofs { for i, proof := range proofs {
p = append(p, (Proof)(proof)) p[i] = (Proof)(proof)
} }
return p, nil return p, nil
} }

View file

@ -108,9 +108,9 @@ func gokzgComputeCellProofs(blob *Blob) ([]Proof, error) {
if err != nil { if err != nil {
return []Proof{}, err return []Proof{}, err
} }
var p []Proof p := make([]Proof, len(proofs))
for _, proof := range proofs { for i, proof := range proofs {
p = append(p, (Proof)(proof)) p[i] = (Proof)(proof)
} }
return p, nil return p, nil
} }

View file

@ -222,17 +222,19 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) {
} }
var ( var (
options = &core.BlockChainConfig{ options = &core.BlockChainConfig{
TrieCleanLimit: config.TrieCleanCache, TrieCleanLimit: config.TrieCleanCache,
NoPrefetch: config.NoPrefetch, NoPrefetch: config.NoPrefetch,
TrieDirtyLimit: config.TrieDirtyCache, TrieDirtyLimit: config.TrieDirtyCache,
ArchiveMode: config.NoPruning, ArchiveMode: config.NoPruning,
TrieTimeLimit: config.TrieTimeout, TrieTimeLimit: config.TrieTimeout,
SnapshotLimit: config.SnapshotCache, SnapshotLimit: config.SnapshotCache,
Preimages: config.Preimages, Preimages: config.Preimages,
StateHistory: config.StateHistory, StateHistory: config.StateHistory,
StateScheme: scheme, TrienodeHistory: config.TrienodeHistory,
ChainHistoryMode: config.HistoryMode, NodeFullValueCheckpoint: config.NodeFullValueCheckpoint,
TxLookupLimit: int64(min(config.TransactionHistory, math.MaxInt64)), StateScheme: scheme,
ChainHistoryMode: config.HistoryMode,
TxLookupLimit: int64(min(config.TransactionHistory, math.MaxInt64)),
VmConfig: vm.Config{ VmConfig: vm.Config{
EnablePreimageRecording: config.EnablePreimageRecording, EnablePreimageRecording: config.EnablePreimageRecording,
EnableWitnessStats: config.EnableWitnessStats, EnableWitnessStats: config.EnableWitnessStats,
@ -493,6 +495,9 @@ func (s *Ethereum) updateFilterMapsHeads() {
if head == nil || newHead.Hash() != head.Hash() { if head == nil || newHead.Hash() != head.Hash() {
head = newHead head = newHead
chainView := s.newChainView(head) chainView := s.newChainView(head)
if chainView == nil {
return
}
historyCutoff, _ := s.blockchain.HistoryPruningCutoff() historyCutoff, _ := s.blockchain.HistoryPruningCutoff()
var finalBlock uint64 var finalBlock uint64
if fb := s.blockchain.CurrentFinalBlock(); fb != nil { if fb := s.blockchain.CurrentFinalBlock(); fb != nil {

View file

@ -35,6 +35,7 @@ import (
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/miner" "github.com/ethereum/go-ethereum/miner"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/triedb/pathdb"
) )
// FullNodeGPO contains default gasprice oracle settings for full node. // FullNodeGPO contains default gasprice oracle settings for full node.
@ -49,30 +50,33 @@ var FullNodeGPO = gasprice.Config{
// Defaults contains default settings for use on the Ethereum main net. // Defaults contains default settings for use on the Ethereum main net.
var Defaults = Config{ var Defaults = Config{
HistoryMode: history.KeepAll, HistoryMode: history.KeepAll,
SyncMode: SnapSync, SyncMode: SnapSync,
NetworkId: 0, // enable auto configuration of networkID == chainID NetworkId: 0, // enable auto configuration of networkID == chainID
TxLookupLimit: 2350000, TxLookupLimit: 2350000,
TransactionHistory: 2350000, TransactionHistory: 2350000,
LogHistory: 2350000, LogHistory: 2350000,
StateHistory: params.FullImmutabilityThreshold, StateHistory: pathdb.Defaults.StateHistory,
DatabaseCache: 512, TrienodeHistory: pathdb.Defaults.TrienodeHistory,
TrieCleanCache: 154, NodeFullValueCheckpoint: pathdb.Defaults.FullValueCheckpoint,
TrieDirtyCache: 256, DatabaseCache: 512,
TrieTimeout: 60 * time.Minute, TrieCleanCache: 154,
SnapshotCache: 102, TrieDirtyCache: 256,
FilterLogCacheSize: 32, TrieTimeout: 60 * time.Minute,
LogQueryLimit: 1000, SnapshotCache: 102,
Miner: miner.DefaultConfig, FilterLogCacheSize: 32,
TxPool: legacypool.DefaultConfig, LogQueryLimit: 1000,
BlobPool: blobpool.DefaultConfig, Miner: miner.DefaultConfig,
RPCGasCap: 50000000, TxPool: legacypool.DefaultConfig,
RPCEVMTimeout: 5 * time.Second, BlobPool: blobpool.DefaultConfig,
GPO: FullNodeGPO, RPCGasCap: 50000000,
RPCTxFeeCap: 1, // 1 ether RPCEVMTimeout: 5 * time.Second,
TxSyncDefaultTimeout: 20 * time.Second, GPO: FullNodeGPO,
TxSyncMaxTimeout: 1 * time.Minute, RPCTxFeeCap: 1, // 1 ether
SlowBlockThreshold: time.Second * 2, TxSyncDefaultTimeout: 20 * time.Second,
TxSyncMaxTimeout: 1 * time.Minute,
SlowBlockThreshold: -1, // Disabled by default; set via --debug.logslowblock flag
RangeLimit: 0,
} }
//go:generate go run github.com/fjl/gencodec -type Config -formats toml -out gen_config.go //go:generate go run github.com/fjl/gencodec -type Config -formats toml -out gen_config.go
@ -108,6 +112,13 @@ type Config struct {
LogNoHistory bool `toml:",omitempty"` // No log search index is maintained. LogNoHistory bool `toml:",omitempty"` // No log search index is maintained.
LogExportCheckpoints string // export log index checkpoints to file LogExportCheckpoints string // export log index checkpoints to file
StateHistory uint64 `toml:",omitempty"` // The maximum number of blocks from head whose state histories are reserved. StateHistory uint64 `toml:",omitempty"` // The maximum number of blocks from head whose state histories are reserved.
TrienodeHistory int64 `toml:",omitempty"` // Number of blocks from the chain head for which trienode histories are retained
// The frequency of full-value encoding. For example, a value of 16 means
// that, on average, for a given trie node across its 16 consecutive historical
// versions, only one version is stored in full format, while the others
// are stored in diff mode for storage compression.
NodeFullValueCheckpoint uint32 `toml:",omitempty"`
// State scheme represents the scheme used to store ethereum states and trie // State scheme represents the scheme used to store ethereum states and trie
// nodes on top. It can be 'hash', 'path', or none which means use the scheme // nodes on top. It can be 'hash', 'path', or none which means use the scheme
@ -119,8 +130,9 @@ type Config struct {
// presence of these blocks for every new peer connection. // presence of these blocks for every new peer connection.
RequiredBlocks map[uint64]common.Hash `toml:"-"` RequiredBlocks map[uint64]common.Hash `toml:"-"`
// SlowBlockThreshold is the block execution speed threshold (Mgas/s) // SlowBlockThreshold is the block execution time threshold beyond which
// below which detailed statistics are logged. // detailed statistics are logged. Negative means disabled (default), zero
// logs all blocks, positive filters by execution time.
SlowBlockThreshold time.Duration `toml:",omitempty"` SlowBlockThreshold time.Duration `toml:",omitempty"`
// Database options // Database options
@ -194,6 +206,9 @@ type Config struct {
// EIP-7966: eth_sendRawTransactionSync timeouts // EIP-7966: eth_sendRawTransactionSync timeouts
TxSyncDefaultTimeout time.Duration `toml:",omitempty"` TxSyncDefaultTimeout time.Duration `toml:",omitempty"`
TxSyncMaxTimeout time.Duration `toml:",omitempty"` TxSyncMaxTimeout time.Duration `toml:",omitempty"`
// RangeLimit restricts the maximum range (end - start) for range queries.
RangeLimit uint64 `toml:",omitempty"`
} }
// CreateConsensusEngine creates a consensus engine for the given chain config. // CreateConsensusEngine creates a consensus engine for the given chain config.

View file

@ -31,6 +31,8 @@ func (c Config) MarshalTOML() (interface{}, error) {
LogNoHistory bool `toml:",omitempty"` LogNoHistory bool `toml:",omitempty"`
LogExportCheckpoints string LogExportCheckpoints string
StateHistory uint64 `toml:",omitempty"` StateHistory uint64 `toml:",omitempty"`
TrienodeHistory int64 `toml:",omitempty"`
NodeFullValueCheckpoint uint32 `toml:",omitempty"`
StateScheme string `toml:",omitempty"` StateScheme string `toml:",omitempty"`
RequiredBlocks map[uint64]common.Hash `toml:"-"` RequiredBlocks map[uint64]common.Hash `toml:"-"`
SlowBlockThreshold time.Duration `toml:",omitempty"` SlowBlockThreshold time.Duration `toml:",omitempty"`
@ -65,6 +67,7 @@ func (c Config) MarshalTOML() (interface{}, error) {
OverrideVerkle *uint64 `toml:",omitempty"` OverrideVerkle *uint64 `toml:",omitempty"`
TxSyncDefaultTimeout time.Duration `toml:",omitempty"` TxSyncDefaultTimeout time.Duration `toml:",omitempty"`
TxSyncMaxTimeout time.Duration `toml:",omitempty"` TxSyncMaxTimeout time.Duration `toml:",omitempty"`
RangeLimit uint64 `toml:",omitempty"`
} }
var enc Config var enc Config
enc.Genesis = c.Genesis enc.Genesis = c.Genesis
@ -81,6 +84,8 @@ func (c Config) MarshalTOML() (interface{}, error) {
enc.LogNoHistory = c.LogNoHistory enc.LogNoHistory = c.LogNoHistory
enc.LogExportCheckpoints = c.LogExportCheckpoints enc.LogExportCheckpoints = c.LogExportCheckpoints
enc.StateHistory = c.StateHistory enc.StateHistory = c.StateHistory
enc.TrienodeHistory = c.TrienodeHistory
enc.NodeFullValueCheckpoint = c.NodeFullValueCheckpoint
enc.StateScheme = c.StateScheme enc.StateScheme = c.StateScheme
enc.RequiredBlocks = c.RequiredBlocks enc.RequiredBlocks = c.RequiredBlocks
enc.SlowBlockThreshold = c.SlowBlockThreshold enc.SlowBlockThreshold = c.SlowBlockThreshold
@ -115,6 +120,7 @@ func (c Config) MarshalTOML() (interface{}, error) {
enc.OverrideVerkle = c.OverrideVerkle enc.OverrideVerkle = c.OverrideVerkle
enc.TxSyncDefaultTimeout = c.TxSyncDefaultTimeout enc.TxSyncDefaultTimeout = c.TxSyncDefaultTimeout
enc.TxSyncMaxTimeout = c.TxSyncMaxTimeout enc.TxSyncMaxTimeout = c.TxSyncMaxTimeout
enc.RangeLimit = c.RangeLimit
return &enc, nil return &enc, nil
} }
@ -135,6 +141,8 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error {
LogNoHistory *bool `toml:",omitempty"` LogNoHistory *bool `toml:",omitempty"`
LogExportCheckpoints *string LogExportCheckpoints *string
StateHistory *uint64 `toml:",omitempty"` StateHistory *uint64 `toml:",omitempty"`
TrienodeHistory *int64 `toml:",omitempty"`
NodeFullValueCheckpoint *uint32 `toml:",omitempty"`
StateScheme *string `toml:",omitempty"` StateScheme *string `toml:",omitempty"`
RequiredBlocks map[uint64]common.Hash `toml:"-"` RequiredBlocks map[uint64]common.Hash `toml:"-"`
SlowBlockThreshold *time.Duration `toml:",omitempty"` SlowBlockThreshold *time.Duration `toml:",omitempty"`
@ -169,6 +177,7 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error {
OverrideVerkle *uint64 `toml:",omitempty"` OverrideVerkle *uint64 `toml:",omitempty"`
TxSyncDefaultTimeout *time.Duration `toml:",omitempty"` TxSyncDefaultTimeout *time.Duration `toml:",omitempty"`
TxSyncMaxTimeout *time.Duration `toml:",omitempty"` TxSyncMaxTimeout *time.Duration `toml:",omitempty"`
RangeLimit *uint64 `toml:",omitempty"`
} }
var dec Config var dec Config
if err := unmarshal(&dec); err != nil { if err := unmarshal(&dec); err != nil {
@ -216,6 +225,12 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error {
if dec.StateHistory != nil { if dec.StateHistory != nil {
c.StateHistory = *dec.StateHistory c.StateHistory = *dec.StateHistory
} }
if dec.TrienodeHistory != nil {
c.TrienodeHistory = *dec.TrienodeHistory
}
if dec.NodeFullValueCheckpoint != nil {
c.NodeFullValueCheckpoint = *dec.NodeFullValueCheckpoint
}
if dec.StateScheme != nil { if dec.StateScheme != nil {
c.StateScheme = *dec.StateScheme c.StateScheme = *dec.StateScheme
} }
@ -318,5 +333,8 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error {
if dec.TxSyncMaxTimeout != nil { if dec.TxSyncMaxTimeout != nil {
c.TxSyncMaxTimeout = *dec.TxSyncMaxTimeout c.TxSyncMaxTimeout = *dec.TxSyncMaxTimeout
} }
if dec.RangeLimit != nil {
c.RangeLimit = *dec.RangeLimit
}
return nil return nil
} }

View file

@ -87,6 +87,19 @@ type txFetcherTest struct {
steps []interface{} steps []interface{}
} }
// newTestTxFetcher creates a tx fetcher with noop callbacks, simulated clock,
// and deterministic randomness.
func newTestTxFetcher() *TxFetcher {
return NewTxFetcher(
func(common.Hash, byte) error { return nil },
func(txs []*types.Transaction) []error {
return make([]error, len(txs))
},
func(string, []common.Hash) error { return nil },
nil,
)
}
// Tests that transaction announcements with associated metadata are added to a // Tests that transaction announcements with associated metadata are added to a
// waitlist, and none of them are scheduled for retrieval until the wait expires. // waitlist, and none of them are scheduled for retrieval until the wait expires.
// //
@ -95,14 +108,7 @@ type txFetcherTest struct {
// with all the useless extra fields. // with all the useless extra fields.
func TestTransactionFetcherWaiting(t *testing.T) { func TestTransactionFetcherWaiting(t *testing.T) {
testTransactionFetcherParallel(t, txFetcherTest{ testTransactionFetcherParallel(t, txFetcherTest{
init: func() *TxFetcher { init: newTestTxFetcher,
return NewTxFetcher(
func(common.Hash, byte) error { return nil },
nil,
func(string, []common.Hash) error { return nil },
nil,
)
},
steps: []interface{}{ steps: []interface{}{
// Initial announcement to get something into the waitlist // Initial announcement to get something into the waitlist
doTxNotify{peer: "A", hashes: []common.Hash{{0x01}, {0x02}}, types: []byte{types.LegacyTxType, types.LegacyTxType}, sizes: []uint32{111, 222}}, doTxNotify{peer: "A", hashes: []common.Hash{{0x01}, {0x02}}, types: []byte{types.LegacyTxType, types.LegacyTxType}, sizes: []uint32{111, 222}},
@ -297,14 +303,7 @@ func TestTransactionFetcherWaiting(t *testing.T) {
// already scheduled. // already scheduled.
func TestTransactionFetcherSkipWaiting(t *testing.T) { func TestTransactionFetcherSkipWaiting(t *testing.T) {
testTransactionFetcherParallel(t, txFetcherTest{ testTransactionFetcherParallel(t, txFetcherTest{
init: func() *TxFetcher { init: newTestTxFetcher,
return NewTxFetcher(
func(common.Hash, byte) error { return nil },
nil,
func(string, []common.Hash) error { return nil },
nil,
)
},
steps: []interface{}{ steps: []interface{}{
// Push an initial announcement through to the scheduled stage // Push an initial announcement through to the scheduled stage
doTxNotify{ doTxNotify{
@ -387,14 +386,7 @@ func TestTransactionFetcherSkipWaiting(t *testing.T) {
// and subsequent announces block or get allotted to someone else. // and subsequent announces block or get allotted to someone else.
func TestTransactionFetcherSingletonRequesting(t *testing.T) { func TestTransactionFetcherSingletonRequesting(t *testing.T) {
testTransactionFetcherParallel(t, txFetcherTest{ testTransactionFetcherParallel(t, txFetcherTest{
init: func() *TxFetcher { init: newTestTxFetcher,
return NewTxFetcher(
func(common.Hash, byte) error { return nil },
nil,
func(string, []common.Hash) error { return nil },
nil,
)
},
steps: []interface{}{ steps: []interface{}{
// Push an initial announcement through to the scheduled stage // Push an initial announcement through to the scheduled stage
doTxNotify{peer: "A", hashes: []common.Hash{{0x01}, {0x02}}, types: []byte{types.LegacyTxType, types.LegacyTxType}, sizes: []uint32{111, 222}}, doTxNotify{peer: "A", hashes: []common.Hash{{0x01}, {0x02}}, types: []byte{types.LegacyTxType, types.LegacyTxType}, sizes: []uint32{111, 222}},
@ -493,15 +485,12 @@ func TestTransactionFetcherFailedRescheduling(t *testing.T) {
proceed := make(chan struct{}) proceed := make(chan struct{})
testTransactionFetcherParallel(t, txFetcherTest{ testTransactionFetcherParallel(t, txFetcherTest{
init: func() *TxFetcher { init: func() *TxFetcher {
return NewTxFetcher( f := newTestTxFetcher()
func(common.Hash, byte) error { return nil }, f.fetchTxs = func(origin string, hashes []common.Hash) error {
nil, <-proceed
func(origin string, hashes []common.Hash) error { return errors.New("peer disconnected")
<-proceed }
return errors.New("peer disconnected") return f
},
nil,
)
}, },
steps: []interface{}{ steps: []interface{}{
// Push an initial announcement through to the scheduled stage // Push an initial announcement through to the scheduled stage
@ -576,16 +565,7 @@ func TestTransactionFetcherFailedRescheduling(t *testing.T) {
// are cleaned up. // are cleaned up.
func TestTransactionFetcherCleanup(t *testing.T) { func TestTransactionFetcherCleanup(t *testing.T) {
testTransactionFetcherParallel(t, txFetcherTest{ testTransactionFetcherParallel(t, txFetcherTest{
init: func() *TxFetcher { init: newTestTxFetcher,
return NewTxFetcher(
func(common.Hash, byte) error { return nil },
func(txs []*types.Transaction) []error {
return make([]error, len(txs))
},
func(string, []common.Hash) error { return nil },
nil,
)
},
steps: []interface{}{ steps: []interface{}{
// Push an initial announcement through to the scheduled stage // Push an initial announcement through to the scheduled stage
doTxNotify{peer: "A", hashes: []common.Hash{testTxsHashes[0]}, types: []byte{testTxs[0].Type()}, sizes: []uint32{uint32(testTxs[0].Size())}}, doTxNotify{peer: "A", hashes: []common.Hash{testTxsHashes[0]}, types: []byte{testTxs[0].Type()}, sizes: []uint32{uint32(testTxs[0].Size())}},
@ -620,16 +600,7 @@ func TestTransactionFetcherCleanup(t *testing.T) {
// this was a bug)). // this was a bug)).
func TestTransactionFetcherCleanupEmpty(t *testing.T) { func TestTransactionFetcherCleanupEmpty(t *testing.T) {
testTransactionFetcherParallel(t, txFetcherTest{ testTransactionFetcherParallel(t, txFetcherTest{
init: func() *TxFetcher { init: newTestTxFetcher,
return NewTxFetcher(
func(common.Hash, byte) error { return nil },
func(txs []*types.Transaction) []error {
return make([]error, len(txs))
},
func(string, []common.Hash) error { return nil },
nil,
)
},
steps: []interface{}{ steps: []interface{}{
// Push an initial announcement through to the scheduled stage // Push an initial announcement through to the scheduled stage
doTxNotify{peer: "A", hashes: []common.Hash{testTxsHashes[0]}, types: []byte{testTxs[0].Type()}, sizes: []uint32{uint32(testTxs[0].Size())}}, doTxNotify{peer: "A", hashes: []common.Hash{testTxsHashes[0]}, types: []byte{testTxs[0].Type()}, sizes: []uint32{uint32(testTxs[0].Size())}},
@ -663,16 +634,7 @@ func TestTransactionFetcherCleanupEmpty(t *testing.T) {
// different peer, or self if they are after the cutoff point. // different peer, or self if they are after the cutoff point.
func TestTransactionFetcherMissingRescheduling(t *testing.T) { func TestTransactionFetcherMissingRescheduling(t *testing.T) {
testTransactionFetcherParallel(t, txFetcherTest{ testTransactionFetcherParallel(t, txFetcherTest{
init: func() *TxFetcher { init: newTestTxFetcher,
return NewTxFetcher(
func(common.Hash, byte) error { return nil },
func(txs []*types.Transaction) []error {
return make([]error, len(txs))
},
func(string, []common.Hash) error { return nil },
nil,
)
},
steps: []interface{}{ steps: []interface{}{
// Push an initial announcement through to the scheduled stage // Push an initial announcement through to the scheduled stage
doTxNotify{peer: "A", doTxNotify{peer: "A",
@ -724,16 +686,7 @@ func TestTransactionFetcherMissingRescheduling(t *testing.T) {
// delivered, the peer gets properly cleaned out from the internal state. // delivered, the peer gets properly cleaned out from the internal state.
func TestTransactionFetcherMissingCleanup(t *testing.T) { func TestTransactionFetcherMissingCleanup(t *testing.T) {
testTransactionFetcherParallel(t, txFetcherTest{ testTransactionFetcherParallel(t, txFetcherTest{
init: func() *TxFetcher { init: newTestTxFetcher,
return NewTxFetcher(
func(common.Hash, byte) error { return nil },
func(txs []*types.Transaction) []error {
return make([]error, len(txs))
},
func(string, []common.Hash) error { return nil },
nil,
)
},
steps: []interface{}{ steps: []interface{}{
// Push an initial announcement through to the scheduled stage // Push an initial announcement through to the scheduled stage
doTxNotify{peer: "A", doTxNotify{peer: "A",
@ -773,16 +726,7 @@ func TestTransactionFetcherMissingCleanup(t *testing.T) {
// Tests that transaction broadcasts properly clean up announcements. // Tests that transaction broadcasts properly clean up announcements.
func TestTransactionFetcherBroadcasts(t *testing.T) { func TestTransactionFetcherBroadcasts(t *testing.T) {
testTransactionFetcherParallel(t, txFetcherTest{ testTransactionFetcherParallel(t, txFetcherTest{
init: func() *TxFetcher { init: newTestTxFetcher,
return NewTxFetcher(
func(common.Hash, byte) error { return nil },
func(txs []*types.Transaction) []error {
return make([]error, len(txs))
},
func(string, []common.Hash) error { return nil },
nil,
)
},
steps: []interface{}{ steps: []interface{}{
// Set up three transactions to be in different stats, waiting, queued and fetching // Set up three transactions to be in different stats, waiting, queued and fetching
doTxNotify{peer: "A", hashes: []common.Hash{testTxsHashes[0]}, types: []byte{testTxs[0].Type()}, sizes: []uint32{uint32(testTxs[0].Size())}}, doTxNotify{peer: "A", hashes: []common.Hash{testTxsHashes[0]}, types: []byte{testTxs[0].Type()}, sizes: []uint32{uint32(testTxs[0].Size())}},
@ -829,14 +773,7 @@ func TestTransactionFetcherBroadcasts(t *testing.T) {
// Tests that the waiting list timers properly reset and reschedule. // Tests that the waiting list timers properly reset and reschedule.
func TestTransactionFetcherWaitTimerResets(t *testing.T) { func TestTransactionFetcherWaitTimerResets(t *testing.T) {
testTransactionFetcherParallel(t, txFetcherTest{ testTransactionFetcherParallel(t, txFetcherTest{
init: func() *TxFetcher { init: newTestTxFetcher,
return NewTxFetcher(
func(common.Hash, byte) error { return nil },
nil,
func(string, []common.Hash) error { return nil },
nil,
)
},
steps: []interface{}{ steps: []interface{}{
doTxNotify{peer: "A", hashes: []common.Hash{{0x01}}, types: []byte{types.LegacyTxType}, sizes: []uint32{111}}, doTxNotify{peer: "A", hashes: []common.Hash{{0x01}}, types: []byte{types.LegacyTxType}, sizes: []uint32{111}},
isWaiting(map[string][]announce{ isWaiting(map[string][]announce{
@ -899,16 +836,7 @@ func TestTransactionFetcherWaitTimerResets(t *testing.T) {
// out and be re-scheduled for someone else. // out and be re-scheduled for someone else.
func TestTransactionFetcherTimeoutRescheduling(t *testing.T) { func TestTransactionFetcherTimeoutRescheduling(t *testing.T) {
testTransactionFetcherParallel(t, txFetcherTest{ testTransactionFetcherParallel(t, txFetcherTest{
init: func() *TxFetcher { init: newTestTxFetcher,
return NewTxFetcher(
func(common.Hash, byte) error { return nil },
func(txs []*types.Transaction) []error {
return make([]error, len(txs))
},
func(string, []common.Hash) error { return nil },
nil,
)
},
steps: []interface{}{ steps: []interface{}{
// Push an initial announcement through to the scheduled stage // Push an initial announcement through to the scheduled stage
doTxNotify{ doTxNotify{
@ -977,14 +905,7 @@ func TestTransactionFetcherTimeoutRescheduling(t *testing.T) {
// Tests that the fetching timeout timers properly reset and reschedule. // Tests that the fetching timeout timers properly reset and reschedule.
func TestTransactionFetcherTimeoutTimerResets(t *testing.T) { func TestTransactionFetcherTimeoutTimerResets(t *testing.T) {
testTransactionFetcherParallel(t, txFetcherTest{ testTransactionFetcherParallel(t, txFetcherTest{
init: func() *TxFetcher { init: newTestTxFetcher,
return NewTxFetcher(
func(common.Hash, byte) error { return nil },
nil,
func(string, []common.Hash) error { return nil },
nil,
)
},
steps: []interface{}{ steps: []interface{}{
doTxNotify{peer: "A", hashes: []common.Hash{{0x01}}, types: []byte{types.LegacyTxType}, sizes: []uint32{111}}, doTxNotify{peer: "A", hashes: []common.Hash{{0x01}}, types: []byte{types.LegacyTxType}, sizes: []uint32{111}},
doWait{time: txArriveTimeout, step: true}, doWait{time: txArriveTimeout, step: true},
@ -1055,14 +976,7 @@ func TestTransactionFetcherRateLimiting(t *testing.T) {
}) })
} }
testTransactionFetcherParallel(t, txFetcherTest{ testTransactionFetcherParallel(t, txFetcherTest{
init: func() *TxFetcher { init: newTestTxFetcher,
return NewTxFetcher(
func(common.Hash, byte) error { return nil },
nil,
func(string, []common.Hash) error { return nil },
nil,
)
},
steps: []interface{}{ steps: []interface{}{
// Announce all the transactions, wait a bit and ensure only a small // Announce all the transactions, wait a bit and ensure only a small
// percentage gets requested // percentage gets requested
@ -1085,14 +999,7 @@ func TestTransactionFetcherRateLimiting(t *testing.T) {
// be requested at a time, to keep the responses below a reasonable level. // be requested at a time, to keep the responses below a reasonable level.
func TestTransactionFetcherBandwidthLimiting(t *testing.T) { func TestTransactionFetcherBandwidthLimiting(t *testing.T) {
testTransactionFetcherParallel(t, txFetcherTest{ testTransactionFetcherParallel(t, txFetcherTest{
init: func() *TxFetcher { init: newTestTxFetcher,
return NewTxFetcher(
func(common.Hash, byte) error { return nil },
nil,
func(string, []common.Hash) error { return nil },
nil,
)
},
steps: []interface{}{ steps: []interface{}{
// Announce mid size transactions from A to verify that multiple // Announce mid size transactions from A to verify that multiple
// ones can be piled into a single request. // ones can be piled into a single request.
@ -1202,14 +1109,7 @@ func TestTransactionFetcherDoSProtection(t *testing.T) {
}) })
} }
testTransactionFetcherParallel(t, txFetcherTest{ testTransactionFetcherParallel(t, txFetcherTest{
init: func() *TxFetcher { init: newTestTxFetcher,
return NewTxFetcher(
func(common.Hash, byte) error { return nil },
nil,
func(string, []common.Hash) error { return nil },
nil,
)
},
steps: []interface{}{ steps: []interface{}{
// Announce half of the transaction and wait for them to be scheduled // Announce half of the transaction and wait for them to be scheduled
doTxNotify{peer: "A", hashes: hashesA[:maxTxAnnounces/2], types: typesA[:maxTxAnnounces/2], sizes: sizesA[:maxTxAnnounces/2]}, doTxNotify{peer: "A", hashes: hashesA[:maxTxAnnounces/2], types: typesA[:maxTxAnnounces/2], sizes: sizesA[:maxTxAnnounces/2]},
@ -1270,24 +1170,21 @@ func TestTransactionFetcherDoSProtection(t *testing.T) {
func TestTransactionFetcherUnderpricedDedup(t *testing.T) { func TestTransactionFetcherUnderpricedDedup(t *testing.T) {
testTransactionFetcherParallel(t, txFetcherTest{ testTransactionFetcherParallel(t, txFetcherTest{
init: func() *TxFetcher { init: func() *TxFetcher {
return NewTxFetcher( f := newTestTxFetcher()
func(common.Hash, byte) error { return nil }, f.addTxs = func(txs []*types.Transaction) []error {
func(txs []*types.Transaction) []error { errs := make([]error, len(txs))
errs := make([]error, len(txs)) for i := 0; i < len(errs); i++ {
for i := 0; i < len(errs); i++ { if i%3 == 0 {
if i%3 == 0 { errs[i] = txpool.ErrUnderpriced
errs[i] = txpool.ErrUnderpriced } else if i%3 == 1 {
} else if i%3 == 1 { errs[i] = txpool.ErrReplaceUnderpriced
errs[i] = txpool.ErrReplaceUnderpriced } else {
} else { errs[i] = txpool.ErrTxGasPriceTooLow
errs[i] = txpool.ErrTxGasPriceTooLow
}
} }
return errs }
}, return errs
func(string, []common.Hash) error { return nil }, }
nil, return f
)
}, },
steps: []interface{}{ steps: []interface{}{
// Deliver a transaction through the fetcher, but reject as underpriced // Deliver a transaction through the fetcher, but reject as underpriced
@ -1371,18 +1268,15 @@ func TestTransactionFetcherUnderpricedDoSProtection(t *testing.T) {
} }
testTransactionFetcher(t, txFetcherTest{ testTransactionFetcher(t, txFetcherTest{
init: func() *TxFetcher { init: func() *TxFetcher {
return NewTxFetcher( f := newTestTxFetcher()
func(common.Hash, byte) error { return nil }, f.addTxs = func(txs []*types.Transaction) []error {
func(txs []*types.Transaction) []error { errs := make([]error, len(txs))
errs := make([]error, len(txs)) for i := 0; i < len(errs); i++ {
for i := 0; i < len(errs); i++ { errs[i] = txpool.ErrUnderpriced
errs[i] = txpool.ErrUnderpriced }
} return errs
return errs }
}, return f
func(string, []common.Hash) error { return nil },
nil,
)
}, },
steps: append(steps, []interface{}{ steps: append(steps, []interface{}{
// The preparation of the test has already been done in `steps`, add the last check // The preparation of the test has already been done in `steps`, add the last check
@ -1402,16 +1296,7 @@ func TestTransactionFetcherUnderpricedDoSProtection(t *testing.T) {
// Tests that unexpected deliveries don't corrupt the internal state. // Tests that unexpected deliveries don't corrupt the internal state.
func TestTransactionFetcherOutOfBoundDeliveries(t *testing.T) { func TestTransactionFetcherOutOfBoundDeliveries(t *testing.T) {
testTransactionFetcherParallel(t, txFetcherTest{ testTransactionFetcherParallel(t, txFetcherTest{
init: func() *TxFetcher { init: newTestTxFetcher,
return NewTxFetcher(
func(common.Hash, byte) error { return nil },
func(txs []*types.Transaction) []error {
return make([]error, len(txs))
},
func(string, []common.Hash) error { return nil },
nil,
)
},
steps: []interface{}{ steps: []interface{}{
// Deliver something out of the blue // Deliver something out of the blue
isWaiting(nil), isWaiting(nil),
@ -1461,16 +1346,7 @@ func TestTransactionFetcherOutOfBoundDeliveries(t *testing.T) {
// live or dangling stages. // live or dangling stages.
func TestTransactionFetcherDrop(t *testing.T) { func TestTransactionFetcherDrop(t *testing.T) {
testTransactionFetcherParallel(t, txFetcherTest{ testTransactionFetcherParallel(t, txFetcherTest{
init: func() *TxFetcher { init: newTestTxFetcher,
return NewTxFetcher(
func(common.Hash, byte) error { return nil },
func(txs []*types.Transaction) []error {
return make([]error, len(txs))
},
func(string, []common.Hash) error { return nil },
nil,
)
},
steps: []interface{}{ steps: []interface{}{
// Set up a few hashes into various stages // Set up a few hashes into various stages
doTxNotify{peer: "A", hashes: []common.Hash{{0x01}}, types: []byte{types.LegacyTxType}, sizes: []uint32{111}}, doTxNotify{peer: "A", hashes: []common.Hash{{0x01}}, types: []byte{types.LegacyTxType}, sizes: []uint32{111}},
@ -1535,16 +1411,7 @@ func TestTransactionFetcherDrop(t *testing.T) {
// available peer. // available peer.
func TestTransactionFetcherDropRescheduling(t *testing.T) { func TestTransactionFetcherDropRescheduling(t *testing.T) {
testTransactionFetcherParallel(t, txFetcherTest{ testTransactionFetcherParallel(t, txFetcherTest{
init: func() *TxFetcher { init: newTestTxFetcher,
return NewTxFetcher(
func(common.Hash, byte) error { return nil },
func(txs []*types.Transaction) []error {
return make([]error, len(txs))
},
func(string, []common.Hash) error { return nil },
nil,
)
},
steps: []interface{}{ steps: []interface{}{
// Set up a few hashes into various stages // Set up a few hashes into various stages
doTxNotify{peer: "A", hashes: []common.Hash{{0x01}}, types: []byte{types.LegacyTxType}, sizes: []uint32{111}}, doTxNotify{peer: "A", hashes: []common.Hash{{0x01}}, types: []byte{types.LegacyTxType}, sizes: []uint32{111}},
@ -1582,14 +1449,9 @@ func TestInvalidAnnounceMetadata(t *testing.T) {
drop := make(chan string, 2) drop := make(chan string, 2)
testTransactionFetcherParallel(t, txFetcherTest{ testTransactionFetcherParallel(t, txFetcherTest{
init: func() *TxFetcher { init: func() *TxFetcher {
return NewTxFetcher( f := newTestTxFetcher()
func(common.Hash, byte) error { return nil }, f.dropPeer = func(peer string) { drop <- peer }
func(txs []*types.Transaction) []error { return f
return make([]error, len(txs))
},
func(string, []common.Hash) error { return nil },
func(peer string) { drop <- peer },
)
}, },
steps: []interface{}{ steps: []interface{}{
// Initial announcement to get something into the waitlist // Initial announcement to get something into the waitlist
@ -1664,16 +1526,7 @@ func TestInvalidAnnounceMetadata(t *testing.T) {
// announced one. // announced one.
func TestTransactionFetcherFuzzCrash01(t *testing.T) { func TestTransactionFetcherFuzzCrash01(t *testing.T) {
testTransactionFetcherParallel(t, txFetcherTest{ testTransactionFetcherParallel(t, txFetcherTest{
init: func() *TxFetcher { init: newTestTxFetcher,
return NewTxFetcher(
func(common.Hash, byte) error { return nil },
func(txs []*types.Transaction) []error {
return make([]error, len(txs))
},
func(string, []common.Hash) error { return nil },
nil,
)
},
steps: []interface{}{ steps: []interface{}{
// Get a transaction into fetching mode and make it dangling with a broadcast // Get a transaction into fetching mode and make it dangling with a broadcast
doTxNotify{peer: "A", hashes: []common.Hash{testTxsHashes[0]}, types: []byte{testTxs[0].Type()}, sizes: []uint32{uint32(testTxs[0].Size())}}, doTxNotify{peer: "A", hashes: []common.Hash{testTxsHashes[0]}, types: []byte{testTxs[0].Type()}, sizes: []uint32{uint32(testTxs[0].Size())}},
@ -1692,16 +1545,7 @@ func TestTransactionFetcherFuzzCrash01(t *testing.T) {
// concurrently announced one. // concurrently announced one.
func TestTransactionFetcherFuzzCrash02(t *testing.T) { func TestTransactionFetcherFuzzCrash02(t *testing.T) {
testTransactionFetcherParallel(t, txFetcherTest{ testTransactionFetcherParallel(t, txFetcherTest{
init: func() *TxFetcher { init: newTestTxFetcher,
return NewTxFetcher(
func(common.Hash, byte) error { return nil },
func(txs []*types.Transaction) []error {
return make([]error, len(txs))
},
func(string, []common.Hash) error { return nil },
nil,
)
},
steps: []interface{}{ steps: []interface{}{
// Get a transaction into fetching mode and make it dangling with a broadcast // Get a transaction into fetching mode and make it dangling with a broadcast
doTxNotify{peer: "A", hashes: []common.Hash{testTxsHashes[0]}, types: []byte{testTxs[0].Type()}, sizes: []uint32{uint32(testTxs[0].Size())}}, doTxNotify{peer: "A", hashes: []common.Hash{testTxsHashes[0]}, types: []byte{testTxs[0].Type()}, sizes: []uint32{uint32(testTxs[0].Size())}},
@ -1722,16 +1566,7 @@ func TestTransactionFetcherFuzzCrash02(t *testing.T) {
// with a concurrent notify. // with a concurrent notify.
func TestTransactionFetcherFuzzCrash03(t *testing.T) { func TestTransactionFetcherFuzzCrash03(t *testing.T) {
testTransactionFetcherParallel(t, txFetcherTest{ testTransactionFetcherParallel(t, txFetcherTest{
init: func() *TxFetcher { init: newTestTxFetcher,
return NewTxFetcher(
func(common.Hash, byte) error { return nil },
func(txs []*types.Transaction) []error {
return make([]error, len(txs))
},
func(string, []common.Hash) error { return nil },
nil,
)
},
steps: []interface{}{ steps: []interface{}{
// Get a transaction into fetching mode and make it dangling with a broadcast // Get a transaction into fetching mode and make it dangling with a broadcast
doTxNotify{ doTxNotify{
@ -1762,17 +1597,12 @@ func TestTransactionFetcherFuzzCrash04(t *testing.T) {
testTransactionFetcherParallel(t, txFetcherTest{ testTransactionFetcherParallel(t, txFetcherTest{
init: func() *TxFetcher { init: func() *TxFetcher {
return NewTxFetcher( f := newTestTxFetcher()
func(common.Hash, byte) error { return nil }, f.fetchTxs = func(string, []common.Hash) error {
func(txs []*types.Transaction) []error { <-proceed
return make([]error, len(txs)) return errors.New("peer disconnected")
}, }
func(string, []common.Hash) error { return f
<-proceed
return errors.New("peer disconnected")
},
nil,
)
}, },
steps: []interface{}{ steps: []interface{}{
// Get a transaction into fetching mode and make it dangling with a broadcast // Get a transaction into fetching mode and make it dangling with a broadcast
@ -1796,14 +1626,7 @@ func TestTransactionFetcherFuzzCrash04(t *testing.T) {
// once they are announced in the network. // once they are announced in the network.
func TestBlobTransactionAnnounce(t *testing.T) { func TestBlobTransactionAnnounce(t *testing.T) {
testTransactionFetcherParallel(t, txFetcherTest{ testTransactionFetcherParallel(t, txFetcherTest{
init: func() *TxFetcher { init: newTestTxFetcher,
return NewTxFetcher(
func(common.Hash, byte) error { return nil },
nil,
func(string, []common.Hash) error { return nil },
nil,
)
},
steps: []interface{}{ steps: []interface{}{
// Initial announcement to get something into the waitlist // Initial announcement to get something into the waitlist
doTxNotify{peer: "A", hashes: []common.Hash{{0x01}, {0x02}}, types: []byte{types.LegacyTxType, types.LegacyTxType}, sizes: []uint32{111, 222}}, doTxNotify{peer: "A", hashes: []common.Hash{{0x01}, {0x02}}, types: []byte{types.LegacyTxType, types.LegacyTxType}, sizes: []uint32{111, 222}},
@ -1864,16 +1687,7 @@ func TestBlobTransactionAnnounce(t *testing.T) {
func TestTransactionFetcherDropAlternates(t *testing.T) { func TestTransactionFetcherDropAlternates(t *testing.T) {
testTransactionFetcherParallel(t, txFetcherTest{ testTransactionFetcherParallel(t, txFetcherTest{
init: func() *TxFetcher { init: newTestTxFetcher,
return NewTxFetcher(
func(common.Hash, byte) error { return nil },
func(txs []*types.Transaction) []error {
return make([]error, len(txs))
},
func(string, []common.Hash) error { return nil },
nil,
)
},
steps: []interface{}{ steps: []interface{}{
doTxNotify{peer: "A", hashes: []common.Hash{testTxsHashes[0]}, types: []byte{testTxs[0].Type()}, sizes: []uint32{uint32(testTxs[0].Size())}}, doTxNotify{peer: "A", hashes: []common.Hash{testTxsHashes[0]}, types: []byte{testTxs[0].Type()}, sizes: []uint32{uint32(testTxs[0].Size())}},
doWait{time: txArriveTimeout, step: true}, doWait{time: txArriveTimeout, step: true},
@ -1915,20 +1729,15 @@ func TestTransactionFetcherDropAlternates(t *testing.T) {
func TestTransactionFetcherWrongMetadata(t *testing.T) { func TestTransactionFetcherWrongMetadata(t *testing.T) {
testTransactionFetcherParallel(t, txFetcherTest{ testTransactionFetcherParallel(t, txFetcherTest{
init: func() *TxFetcher { init: func() *TxFetcher {
return NewTxFetcher( f := newTestTxFetcher()
func(_ common.Hash, kind byte) error { f.validateMeta = func(name common.Hash, kind byte) error {
switch kind { switch kind {
case types.LegacyTxType, types.AccessListTxType, types.DynamicFeeTxType, types.BlobTxType, types.SetCodeTxType: case types.LegacyTxType, types.AccessListTxType, types.DynamicFeeTxType, types.BlobTxType, types.SetCodeTxType:
return nil return nil
} }
return types.ErrTxTypeNotSupported return types.ErrTxTypeNotSupported
}, }
func(txs []*types.Transaction) []error { return f
return make([]error, len(txs))
},
func(string, []common.Hash) error { return nil },
nil,
)
}, },
steps: []interface{}{ steps: []interface{}{
doTxNotify{peer: "A", hashes: []common.Hash{{0x01}, {0x02}}, types: []byte{0xff, types.LegacyTxType}, sizes: []uint32{111, 222}}, doTxNotify{peer: "A", hashes: []common.Hash{{0x01}, {0x02}}, types: []byte{0xff, types.LegacyTxType}, sizes: []uint32{111, 222}},
@ -1976,20 +1785,16 @@ func TestTransactionProtocolViolation(t *testing.T) {
) )
testTransactionFetcherParallel(t, txFetcherTest{ testTransactionFetcherParallel(t, txFetcherTest{
init: func() *TxFetcher { init: func() *TxFetcher {
return NewTxFetcher( f := newTestTxFetcher()
func(common.Hash, byte) error { return nil }, f.addTxs = func(txs []*types.Transaction) []error {
func(txs []*types.Transaction) []error { var errs []error
var errs []error for range txs {
for range txs { errs = append(errs, txpool.ErrKZGVerificationError)
errs = append(errs, txpool.ErrKZGVerificationError) }
} return errs
return errs }
}, f.dropPeer = func(string) { drop <- struct{}{} }
func(a string, b []common.Hash) error { return f
return nil
},
func(peer string) { drop <- struct{}{} },
)
}, },
steps: []interface{}{ steps: []interface{}{
// Initial announcement to get something into the waitlist // Initial announcement to get something into the waitlist

View file

@ -89,6 +89,7 @@ type FilterAPI struct {
filters map[rpc.ID]*filter filters map[rpc.ID]*filter
timeout time.Duration timeout time.Duration
logQueryLimit int logQueryLimit int
rangeLimit uint64
} }
// NewFilterAPI returns a new FilterAPI instance. // NewFilterAPI returns a new FilterAPI instance.
@ -99,6 +100,7 @@ func NewFilterAPI(system *FilterSystem) *FilterAPI {
filters: make(map[rpc.ID]*filter), filters: make(map[rpc.ID]*filter),
timeout: system.cfg.Timeout, timeout: system.cfg.Timeout,
logQueryLimit: system.cfg.LogQueryLimit, logQueryLimit: system.cfg.LogQueryLimit,
rangeLimit: system.cfg.RangeLimit,
} }
go api.timeoutLoop(system.cfg.Timeout) go api.timeoutLoop(system.cfg.Timeout)
@ -479,7 +481,7 @@ func (api *FilterAPI) GetLogs(ctx context.Context, crit FilterCriteria) ([]*type
return nil, &history.PrunedHistoryError{} return nil, &history.PrunedHistoryError{}
} }
// Construct the range filter // Construct the range filter
filter = api.sys.NewRangeFilter(begin, end, crit.Addresses, crit.Topics) filter = api.sys.NewRangeFilter(begin, end, crit.Addresses, crit.Topics, api.rangeLimit)
} }
// Run the filter and return all the logs // Run the filter and return all the logs
@ -531,7 +533,7 @@ func (api *FilterAPI) GetFilterLogs(ctx context.Context, id rpc.ID) ([]*types.Lo
end = f.crit.ToBlock.Int64() end = f.crit.ToBlock.Int64()
} }
// Construct the range filter // Construct the range filter
filter = api.sys.NewRangeFilter(begin, end, f.crit.Addresses, f.crit.Topics) filter = api.sys.NewRangeFilter(begin, end, f.crit.Addresses, f.crit.Topics, api.rangeLimit)
} }
// Run the filter and return all the logs // Run the filter and return all the logs
logs, err := filter.Logs(ctx) logs, err := filter.Logs(ctx)

View file

@ -19,6 +19,7 @@ package filters
import ( import (
"context" "context"
"errors" "errors"
"fmt"
"math" "math"
"math/big" "math/big"
"slices" "slices"
@ -44,15 +45,17 @@ type Filter struct {
begin, end int64 // Range interval if filtering multiple blocks begin, end int64 // Range interval if filtering multiple blocks
rangeLogsTestHook chan rangeLogsTestEvent rangeLogsTestHook chan rangeLogsTestEvent
rangeLimit uint64
} }
// NewRangeFilter creates a new filter which uses a bloom filter on blocks to // NewRangeFilter creates a new filter which uses a bloom filter on blocks to
// figure out whether a particular block is interesting or not. // figure out whether a particular block is interesting or not.
func (sys *FilterSystem) NewRangeFilter(begin, end int64, addresses []common.Address, topics [][]common.Hash) *Filter { func (sys *FilterSystem) NewRangeFilter(begin, end int64, addresses []common.Address, topics [][]common.Hash, rangeLimit uint64) *Filter {
// Create a generic filter and convert it into a range filter // Create a generic filter and convert it into a range filter
filter := newFilter(sys, addresses, topics) filter := newFilter(sys, addresses, topics)
filter.begin = begin filter.begin = begin
filter.end = end filter.end = end
filter.rangeLimit = rangeLimit
return filter return filter
} }
@ -143,6 +146,9 @@ func (f *Filter) Logs(ctx context.Context) ([]*types.Log, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
if f.rangeLimit != 0 && (end-begin) > f.rangeLimit {
return nil, fmt.Errorf("exceed maximum block range: %d", f.rangeLimit)
}
return f.rangeLogs(ctx, begin, end) return f.rangeLogs(ctx, begin, end)
} }
@ -494,7 +500,7 @@ func (f *Filter) checkMatches(ctx context.Context, header *types.Header) ([]*typ
// filterLogs creates a slice of logs matching the given criteria. // filterLogs creates a slice of logs matching the given criteria.
func filterLogs(logs []*types.Log, fromBlock, toBlock *big.Int, addresses []common.Address, topics [][]common.Hash) []*types.Log { func filterLogs(logs []*types.Log, fromBlock, toBlock *big.Int, addresses []common.Address, topics [][]common.Hash) []*types.Log {
var check = func(log *types.Log) bool { check := func(log *types.Log) bool {
if fromBlock != nil && fromBlock.Int64() >= 0 && fromBlock.Uint64() > log.BlockNumber { if fromBlock != nil && fromBlock.Int64() >= 0 && fromBlock.Uint64() > log.BlockNumber {
return false return false
} }

View file

@ -44,6 +44,7 @@ type Config struct {
LogCacheSize int // maximum number of cached blocks (default: 32) LogCacheSize int // maximum number of cached blocks (default: 32)
Timeout time.Duration // how long filters stay active (default: 5min) Timeout time.Duration // how long filters stay active (default: 5min)
LogQueryLimit int // maximum number of addresses allowed in filter criteria (default: 1000) LogQueryLimit int // maximum number of addresses allowed in filter criteria (default: 1000)
RangeLimit uint64 // maximum block range allowed in filter criteria (default: 0)
} }
func (cfg Config) withDefaults() Config { func (cfg Config) withDefaults() Config {

View file

@ -109,7 +109,7 @@ func benchmarkFilters(b *testing.B, history uint64, noHistory bool) {
backend.startFilterMaps(history, noHistory, filtermaps.DefaultParams) backend.startFilterMaps(history, noHistory, filtermaps.DefaultParams)
defer backend.stopFilterMaps() defer backend.stopFilterMaps()
filter := sys.NewRangeFilter(0, int64(rpc.LatestBlockNumber), []common.Address{addr1, addr2, addr3, addr4}, nil) filter := sys.NewRangeFilter(0, int64(rpc.LatestBlockNumber), []common.Address{addr1, addr2, addr3, addr4}, nil, 0)
for b.Loop() { for b.Loop() {
filter.begin = 0 filter.begin = 0
logs, _ := filter.Logs(context.Background()) logs, _ := filter.Logs(context.Background())
@ -317,70 +317,70 @@ func testFilters(t *testing.T, history uint64, noHistory bool) {
want: `[{"address":"0xfe00000000000000000000000000000000000000","topics":["0x0000000000000000000000000000000000000000000000000000746f70696332","0x0000000000000000000000000000000000000000000000000000746f70696331"],"data":"0x","blockNumber":"0x3","transactionHash":"0xdefe471992a07a02acdfbe33edaae22fbb86d7d3cec3f1b8e4e77702fb3acc1d","transactionIndex":"0x0","blockHash":"0x7a7556792ca7d37882882e2b001fe14833eaf81c2c7f865c9c771ec37a024f6b","blockTimestamp":"0x1e","logIndex":"0x0","removed":false}]`, want: `[{"address":"0xfe00000000000000000000000000000000000000","topics":["0x0000000000000000000000000000000000000000000000000000746f70696332","0x0000000000000000000000000000000000000000000000000000746f70696331"],"data":"0x","blockNumber":"0x3","transactionHash":"0xdefe471992a07a02acdfbe33edaae22fbb86d7d3cec3f1b8e4e77702fb3acc1d","transactionIndex":"0x0","blockHash":"0x7a7556792ca7d37882882e2b001fe14833eaf81c2c7f865c9c771ec37a024f6b","blockTimestamp":"0x1e","logIndex":"0x0","removed":false}]`,
}, },
{ {
f: sys.NewRangeFilter(0, int64(rpc.LatestBlockNumber), []common.Address{contract}, [][]common.Hash{{hash1, hash2, hash3, hash4}}), f: sys.NewRangeFilter(0, int64(rpc.LatestBlockNumber), []common.Address{contract}, [][]common.Hash{{hash1, hash2, hash3, hash4}}, 0),
want: `[{"address":"0xfe00000000000000000000000000000000000000","topics":["0x0000000000000000000000000000000000000000000000000000746f70696331"],"data":"0x","blockNumber":"0x2","transactionHash":"0xa8028c655b6423204c8edfbc339f57b042d6bec2b6a61145d76b7c08b4cccd42","transactionIndex":"0x0","blockHash":"0x24417bb49ce44cfad65da68f33b510bf2a129c0d89ccf06acb6958b8585ccf34","blockTimestamp":"0x14","logIndex":"0x0","removed":false},{"address":"0xfe00000000000000000000000000000000000000","topics":["0x0000000000000000000000000000000000000000000000000000746f70696332","0x0000000000000000000000000000000000000000000000000000746f70696331"],"data":"0x","blockNumber":"0x3","transactionHash":"0xdefe471992a07a02acdfbe33edaae22fbb86d7d3cec3f1b8e4e77702fb3acc1d","transactionIndex":"0x0","blockHash":"0x7a7556792ca7d37882882e2b001fe14833eaf81c2c7f865c9c771ec37a024f6b","blockTimestamp":"0x1e","logIndex":"0x0","removed":false},{"address":"0xfe00000000000000000000000000000000000000","topics":["0x0000000000000000000000000000000000000000000000000000746f70696334"],"data":"0x","blockNumber":"0x3e8","transactionHash":"0x9a87842100a638dfa5da8842b4beda691d2fd77b0c84b57f24ecfa9fb208f747","transactionIndex":"0x0","blockHash":"0xb360bad5265261c075ece02d3bf0e39498a6a76310482cdfd90588748e6c5ee0","blockTimestamp":"0x2710","logIndex":"0x0","removed":false}]`, want: `[{"address":"0xfe00000000000000000000000000000000000000","topics":["0x0000000000000000000000000000000000000000000000000000746f70696331"],"data":"0x","blockNumber":"0x2","transactionHash":"0xa8028c655b6423204c8edfbc339f57b042d6bec2b6a61145d76b7c08b4cccd42","transactionIndex":"0x0","blockHash":"0x24417bb49ce44cfad65da68f33b510bf2a129c0d89ccf06acb6958b8585ccf34","blockTimestamp":"0x14","logIndex":"0x0","removed":false},{"address":"0xfe00000000000000000000000000000000000000","topics":["0x0000000000000000000000000000000000000000000000000000746f70696332","0x0000000000000000000000000000000000000000000000000000746f70696331"],"data":"0x","blockNumber":"0x3","transactionHash":"0xdefe471992a07a02acdfbe33edaae22fbb86d7d3cec3f1b8e4e77702fb3acc1d","transactionIndex":"0x0","blockHash":"0x7a7556792ca7d37882882e2b001fe14833eaf81c2c7f865c9c771ec37a024f6b","blockTimestamp":"0x1e","logIndex":"0x0","removed":false},{"address":"0xfe00000000000000000000000000000000000000","topics":["0x0000000000000000000000000000000000000000000000000000746f70696334"],"data":"0x","blockNumber":"0x3e8","transactionHash":"0x9a87842100a638dfa5da8842b4beda691d2fd77b0c84b57f24ecfa9fb208f747","transactionIndex":"0x0","blockHash":"0xb360bad5265261c075ece02d3bf0e39498a6a76310482cdfd90588748e6c5ee0","blockTimestamp":"0x2710","logIndex":"0x0","removed":false}]`,
}, },
{ {
f: sys.NewRangeFilter(900, 999, []common.Address{contract}, [][]common.Hash{{hash3}}), f: sys.NewRangeFilter(900, 999, []common.Address{contract}, [][]common.Hash{{hash3}}, 0),
}, },
{ {
f: sys.NewRangeFilter(990, int64(rpc.LatestBlockNumber), []common.Address{contract2}, [][]common.Hash{{hash3}}), f: sys.NewRangeFilter(990, int64(rpc.LatestBlockNumber), []common.Address{contract2}, [][]common.Hash{{hash3}}, 0),
want: `[{"address":"0xff00000000000000000000000000000000000000","topics":["0x0000000000000000000000000000000000000000000000000000746f70696333"],"data":"0x","blockNumber":"0x3e7","transactionHash":"0x53e3675800c6908424b61b35a44e51ca4c73ca603e58a65b32c67968b4f42200","transactionIndex":"0x0","blockHash":"0x2e4620a2b426b0612ec6cad9603f466723edaed87f98c9137405dd4f7a2409ff","blockTimestamp":"0x2706","logIndex":"0x0","removed":false}]`, want: `[{"address":"0xff00000000000000000000000000000000000000","topics":["0x0000000000000000000000000000000000000000000000000000746f70696333"],"data":"0x","blockNumber":"0x3e7","transactionHash":"0x53e3675800c6908424b61b35a44e51ca4c73ca603e58a65b32c67968b4f42200","transactionIndex":"0x0","blockHash":"0x2e4620a2b426b0612ec6cad9603f466723edaed87f98c9137405dd4f7a2409ff","blockTimestamp":"0x2706","logIndex":"0x0","removed":false}]`,
}, },
{ {
f: sys.NewRangeFilter(1, 10, []common.Address{contract}, [][]common.Hash{{hash2}, {hash1}}), f: sys.NewRangeFilter(1, 10, []common.Address{contract}, [][]common.Hash{{hash2}, {hash1}}, 0),
want: `[{"address":"0xfe00000000000000000000000000000000000000","topics":["0x0000000000000000000000000000000000000000000000000000746f70696332","0x0000000000000000000000000000000000000000000000000000746f70696331"],"data":"0x","blockNumber":"0x3","transactionHash":"0xdefe471992a07a02acdfbe33edaae22fbb86d7d3cec3f1b8e4e77702fb3acc1d","transactionIndex":"0x0","blockHash":"0x7a7556792ca7d37882882e2b001fe14833eaf81c2c7f865c9c771ec37a024f6b","blockTimestamp":"0x1e","logIndex":"0x0","removed":false}]`, want: `[{"address":"0xfe00000000000000000000000000000000000000","topics":["0x0000000000000000000000000000000000000000000000000000746f70696332","0x0000000000000000000000000000000000000000000000000000746f70696331"],"data":"0x","blockNumber":"0x3","transactionHash":"0xdefe471992a07a02acdfbe33edaae22fbb86d7d3cec3f1b8e4e77702fb3acc1d","transactionIndex":"0x0","blockHash":"0x7a7556792ca7d37882882e2b001fe14833eaf81c2c7f865c9c771ec37a024f6b","blockTimestamp":"0x1e","logIndex":"0x0","removed":false}]`,
}, },
{ {
f: sys.NewRangeFilter(1, 10, nil, [][]common.Hash{{hash1, hash2}}), f: sys.NewRangeFilter(1, 10, nil, [][]common.Hash{{hash1, hash2}}, 0),
want: `[{"address":"0xfe00000000000000000000000000000000000000","topics":["0x0000000000000000000000000000000000000000000000000000746f70696331"],"data":"0x","blockNumber":"0x2","transactionHash":"0xa8028c655b6423204c8edfbc339f57b042d6bec2b6a61145d76b7c08b4cccd42","transactionIndex":"0x0","blockHash":"0x24417bb49ce44cfad65da68f33b510bf2a129c0d89ccf06acb6958b8585ccf34","blockTimestamp":"0x14","logIndex":"0x0","removed":false},{"address":"0xff00000000000000000000000000000000000000","topics":["0x0000000000000000000000000000000000000000000000000000746f70696331"],"data":"0x","blockNumber":"0x2","transactionHash":"0xdba3e2ea9a7d690b722d70ee605fd67ba4c00d1d3aecd5cf187a7b92ad8eb3df","transactionIndex":"0x1","blockHash":"0x24417bb49ce44cfad65da68f33b510bf2a129c0d89ccf06acb6958b8585ccf34","blockTimestamp":"0x14","logIndex":"0x1","removed":false},{"address":"0xfe00000000000000000000000000000000000000","topics":["0x0000000000000000000000000000000000000000000000000000746f70696332","0x0000000000000000000000000000000000000000000000000000746f70696331"],"data":"0x","blockNumber":"0x3","transactionHash":"0xdefe471992a07a02acdfbe33edaae22fbb86d7d3cec3f1b8e4e77702fb3acc1d","transactionIndex":"0x0","blockHash":"0x7a7556792ca7d37882882e2b001fe14833eaf81c2c7f865c9c771ec37a024f6b","blockTimestamp":"0x1e","logIndex":"0x0","removed":false}]`, want: `[{"address":"0xfe00000000000000000000000000000000000000","topics":["0x0000000000000000000000000000000000000000000000000000746f70696331"],"data":"0x","blockNumber":"0x2","transactionHash":"0xa8028c655b6423204c8edfbc339f57b042d6bec2b6a61145d76b7c08b4cccd42","transactionIndex":"0x0","blockHash":"0x24417bb49ce44cfad65da68f33b510bf2a129c0d89ccf06acb6958b8585ccf34","blockTimestamp":"0x14","logIndex":"0x0","removed":false},{"address":"0xff00000000000000000000000000000000000000","topics":["0x0000000000000000000000000000000000000000000000000000746f70696331"],"data":"0x","blockNumber":"0x2","transactionHash":"0xdba3e2ea9a7d690b722d70ee605fd67ba4c00d1d3aecd5cf187a7b92ad8eb3df","transactionIndex":"0x1","blockHash":"0x24417bb49ce44cfad65da68f33b510bf2a129c0d89ccf06acb6958b8585ccf34","blockTimestamp":"0x14","logIndex":"0x1","removed":false},{"address":"0xfe00000000000000000000000000000000000000","topics":["0x0000000000000000000000000000000000000000000000000000746f70696332","0x0000000000000000000000000000000000000000000000000000746f70696331"],"data":"0x","blockNumber":"0x3","transactionHash":"0xdefe471992a07a02acdfbe33edaae22fbb86d7d3cec3f1b8e4e77702fb3acc1d","transactionIndex":"0x0","blockHash":"0x7a7556792ca7d37882882e2b001fe14833eaf81c2c7f865c9c771ec37a024f6b","blockTimestamp":"0x1e","logIndex":"0x0","removed":false}]`,
}, },
{ {
f: sys.NewRangeFilter(0, int64(rpc.LatestBlockNumber), nil, [][]common.Hash{{common.BytesToHash([]byte("fail"))}}), f: sys.NewRangeFilter(0, int64(rpc.LatestBlockNumber), nil, [][]common.Hash{{common.BytesToHash([]byte("fail"))}}, 0),
}, },
{ {
f: sys.NewRangeFilter(0, int64(rpc.LatestBlockNumber), []common.Address{common.BytesToAddress([]byte("failmenow"))}, nil), f: sys.NewRangeFilter(0, int64(rpc.LatestBlockNumber), []common.Address{common.BytesToAddress([]byte("failmenow"))}, nil, 0),
}, },
{ {
f: sys.NewRangeFilter(0, int64(rpc.LatestBlockNumber), nil, [][]common.Hash{{common.BytesToHash([]byte("fail"))}, {hash1}}), f: sys.NewRangeFilter(0, int64(rpc.LatestBlockNumber), nil, [][]common.Hash{{common.BytesToHash([]byte("fail"))}, {hash1}}, 0),
}, },
{ {
f: sys.NewRangeFilter(int64(rpc.LatestBlockNumber), int64(rpc.LatestBlockNumber), nil, nil), f: sys.NewRangeFilter(int64(rpc.LatestBlockNumber), int64(rpc.LatestBlockNumber), nil, nil, 0),
want: `[{"address":"0xfe00000000000000000000000000000000000000","topics":["0x0000000000000000000000000000000000000000000000000000746f70696334"],"data":"0x","blockNumber":"0x3e8","transactionHash":"0x9a87842100a638dfa5da8842b4beda691d2fd77b0c84b57f24ecfa9fb208f747","transactionIndex":"0x0","blockHash":"0xb360bad5265261c075ece02d3bf0e39498a6a76310482cdfd90588748e6c5ee0","blockTimestamp":"0x2710","logIndex":"0x0","removed":false}]`, want: `[{"address":"0xfe00000000000000000000000000000000000000","topics":["0x0000000000000000000000000000000000000000000000000000746f70696334"],"data":"0x","blockNumber":"0x3e8","transactionHash":"0x9a87842100a638dfa5da8842b4beda691d2fd77b0c84b57f24ecfa9fb208f747","transactionIndex":"0x0","blockHash":"0xb360bad5265261c075ece02d3bf0e39498a6a76310482cdfd90588748e6c5ee0","blockTimestamp":"0x2710","logIndex":"0x0","removed":false}]`,
}, },
{ {
f: sys.NewRangeFilter(int64(rpc.FinalizedBlockNumber), int64(rpc.LatestBlockNumber), nil, nil), f: sys.NewRangeFilter(int64(rpc.FinalizedBlockNumber), int64(rpc.LatestBlockNumber), nil, nil, 0),
want: `[{"address":"0xff00000000000000000000000000000000000000","topics":["0x0000000000000000000000000000000000000000000000000000746f70696333"],"data":"0x","blockNumber":"0x3e7","transactionHash":"0x53e3675800c6908424b61b35a44e51ca4c73ca603e58a65b32c67968b4f42200","transactionIndex":"0x0","blockHash":"0x2e4620a2b426b0612ec6cad9603f466723edaed87f98c9137405dd4f7a2409ff","blockTimestamp":"0x2706","logIndex":"0x0","removed":false},{"address":"0xfe00000000000000000000000000000000000000","topics":["0x0000000000000000000000000000000000000000000000000000746f70696334"],"data":"0x","blockNumber":"0x3e8","transactionHash":"0x9a87842100a638dfa5da8842b4beda691d2fd77b0c84b57f24ecfa9fb208f747","transactionIndex":"0x0","blockHash":"0xb360bad5265261c075ece02d3bf0e39498a6a76310482cdfd90588748e6c5ee0","blockTimestamp":"0x2710","logIndex":"0x0","removed":false}]`, want: `[{"address":"0xff00000000000000000000000000000000000000","topics":["0x0000000000000000000000000000000000000000000000000000746f70696333"],"data":"0x","blockNumber":"0x3e7","transactionHash":"0x53e3675800c6908424b61b35a44e51ca4c73ca603e58a65b32c67968b4f42200","transactionIndex":"0x0","blockHash":"0x2e4620a2b426b0612ec6cad9603f466723edaed87f98c9137405dd4f7a2409ff","blockTimestamp":"0x2706","logIndex":"0x0","removed":false},{"address":"0xfe00000000000000000000000000000000000000","topics":["0x0000000000000000000000000000000000000000000000000000746f70696334"],"data":"0x","blockNumber":"0x3e8","transactionHash":"0x9a87842100a638dfa5da8842b4beda691d2fd77b0c84b57f24ecfa9fb208f747","transactionIndex":"0x0","blockHash":"0xb360bad5265261c075ece02d3bf0e39498a6a76310482cdfd90588748e6c5ee0","blockTimestamp":"0x2710","logIndex":"0x0","removed":false}]`,
}, },
{ {
f: sys.NewRangeFilter(int64(rpc.FinalizedBlockNumber), int64(rpc.FinalizedBlockNumber), nil, nil), f: sys.NewRangeFilter(int64(rpc.FinalizedBlockNumber), int64(rpc.FinalizedBlockNumber), nil, nil, 0),
want: `[{"address":"0xff00000000000000000000000000000000000000","topics":["0x0000000000000000000000000000000000000000000000000000746f70696333"],"data":"0x","blockNumber":"0x3e7","transactionHash":"0x53e3675800c6908424b61b35a44e51ca4c73ca603e58a65b32c67968b4f42200","transactionIndex":"0x0","blockHash":"0x2e4620a2b426b0612ec6cad9603f466723edaed87f98c9137405dd4f7a2409ff","blockTimestamp":"0x2706","logIndex":"0x0","removed":false}]`, want: `[{"address":"0xff00000000000000000000000000000000000000","topics":["0x0000000000000000000000000000000000000000000000000000746f70696333"],"data":"0x","blockNumber":"0x3e7","transactionHash":"0x53e3675800c6908424b61b35a44e51ca4c73ca603e58a65b32c67968b4f42200","transactionIndex":"0x0","blockHash":"0x2e4620a2b426b0612ec6cad9603f466723edaed87f98c9137405dd4f7a2409ff","blockTimestamp":"0x2706","logIndex":"0x0","removed":false}]`,
}, },
{ {
f: sys.NewRangeFilter(int64(rpc.LatestBlockNumber), int64(rpc.FinalizedBlockNumber), nil, nil), f: sys.NewRangeFilter(int64(rpc.LatestBlockNumber), int64(rpc.FinalizedBlockNumber), nil, nil, 0),
}, },
{ {
f: sys.NewRangeFilter(int64(rpc.SafeBlockNumber), int64(rpc.LatestBlockNumber), nil, nil), f: sys.NewRangeFilter(int64(rpc.SafeBlockNumber), int64(rpc.LatestBlockNumber), nil, nil, 0),
err: "safe header not found", err: "safe header not found",
}, },
{ {
f: sys.NewRangeFilter(int64(rpc.SafeBlockNumber), int64(rpc.SafeBlockNumber), nil, nil), f: sys.NewRangeFilter(int64(rpc.SafeBlockNumber), int64(rpc.SafeBlockNumber), nil, nil, 0),
err: "safe header not found", err: "safe header not found",
}, },
{ {
f: sys.NewRangeFilter(int64(rpc.LatestBlockNumber), int64(rpc.SafeBlockNumber), nil, nil), f: sys.NewRangeFilter(int64(rpc.LatestBlockNumber), int64(rpc.SafeBlockNumber), nil, nil, 0),
err: "safe header not found", err: "safe header not found",
}, },
{ {
f: sys.NewRangeFilter(int64(rpc.PendingBlockNumber), int64(rpc.PendingBlockNumber), nil, nil), f: sys.NewRangeFilter(int64(rpc.PendingBlockNumber), int64(rpc.PendingBlockNumber), nil, nil, 0),
err: errPendingLogsUnsupported.Error(), err: errPendingLogsUnsupported.Error(),
}, },
{ {
f: sys.NewRangeFilter(int64(rpc.LatestBlockNumber), int64(rpc.PendingBlockNumber), nil, nil), f: sys.NewRangeFilter(int64(rpc.LatestBlockNumber), int64(rpc.PendingBlockNumber), nil, nil, 0),
err: errPendingLogsUnsupported.Error(), err: errPendingLogsUnsupported.Error(),
}, },
{ {
f: sys.NewRangeFilter(int64(rpc.PendingBlockNumber), int64(rpc.LatestBlockNumber), nil, nil), f: sys.NewRangeFilter(int64(rpc.PendingBlockNumber), int64(rpc.LatestBlockNumber), nil, nil, 0),
err: errPendingLogsUnsupported.Error(), err: errPendingLogsUnsupported.Error(),
}, },
} { } {
@ -403,7 +403,7 @@ func testFilters(t *testing.T, history uint64, noHistory bool) {
} }
t.Run("timeout", func(t *testing.T) { t.Run("timeout", func(t *testing.T) {
f := sys.NewRangeFilter(0, rpc.LatestBlockNumber.Int64(), nil, nil) f := sys.NewRangeFilter(0, rpc.LatestBlockNumber.Int64(), nil, nil, 0)
ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(-time.Hour)) ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(-time.Hour))
defer cancel() defer cancel()
_, err := f.Logs(ctx) _, err := f.Logs(ctx)
@ -464,7 +464,7 @@ func TestRangeLogs(t *testing.T) {
newFilter := func(begin, end int64) { newFilter := func(begin, end int64) {
testCase++ testCase++
event = 0 event = 0
filter = sys.NewRangeFilter(begin, end, addresses, nil) filter = sys.NewRangeFilter(begin, end, addresses, nil, 0)
filter.rangeLogsTestHook = make(chan rangeLogsTestEvent) filter.rangeLogsTestHook = make(chan rangeLogsTestEvent)
go func(filter *Filter) { go func(filter *Filter) {
filter.Logs(context.Background()) filter.Logs(context.Background())
@ -601,3 +601,39 @@ func TestRangeLogs(t *testing.T) {
expEvent(rangeLogsTestReorg, 400, 901) expEvent(rangeLogsTestReorg, 400, 901)
expEvent(rangeLogsTestDone, 0, 0) expEvent(rangeLogsTestDone, 0, 0)
} }
func TestRangeLimit(t *testing.T) {
db := rawdb.NewMemoryDatabase()
backend, sys := newTestFilterSystem(db, Config{})
defer db.Close()
gspec := &core.Genesis{
Config: params.TestChainConfig,
Alloc: types.GenesisAlloc{},
BaseFee: big.NewInt(params.InitialBaseFee),
}
_, err := gspec.Commit(db, triedb.NewDatabase(db, nil), nil)
if err != nil {
t.Fatal(err)
}
chain, _ := core.GenerateChain(gspec.Config, gspec.ToBlock(), ethash.NewFaker(), db, 10, func(i int, gen *core.BlockGen) {})
options := core.DefaultConfig().WithStateScheme(rawdb.HashScheme)
options.TxLookupLimit = 0
bc, err := core.NewBlockChain(db, gspec, ethash.NewFaker(), options)
if err != nil {
t.Fatal(err)
}
_, err = bc.InsertChain(chain)
if err != nil {
t.Fatal(err)
}
backend.startFilterMaps(0, false, filtermaps.DefaultParams)
defer backend.stopFilterMaps()
// Set rangeLimit to 5, but request a range of 9 (end - begin = 9, from 0 to 9)
filter := sys.NewRangeFilter(0, 9, nil, nil, 5)
_, err = filter.Logs(context.Background())
if err == nil || !strings.Contains(err.Error(), "exceed maximum block range") {
t.Fatalf("expected range limit error, got %v", err)
}
}

View file

@ -31,6 +31,7 @@ import (
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rpc" "github.com/ethereum/go-ethereum/rpc"
"github.com/holiman/uint256"
) )
const sampleNumber = 3 // Number of transactions sampled in a block const sampleNumber = 3 // Number of transactions sampled in a block
@ -257,12 +258,12 @@ func (oracle *Oracle) getBlockValues(ctx context.Context, blockNum uint64, limit
sortedTxs := make([]*types.Transaction, len(txs)) sortedTxs := make([]*types.Transaction, len(txs))
copy(sortedTxs, txs) copy(sortedTxs, txs)
baseFee := block.BaseFee() baseFee := block.BaseFee()
baseFee256 := new(uint256.Int)
if baseFee != nil {
baseFee256.SetFromBig(baseFee)
}
slices.SortFunc(sortedTxs, func(a, b *types.Transaction) int { slices.SortFunc(sortedTxs, func(a, b *types.Transaction) int {
// It's okay to discard the error because a tx would never be return a.EffectiveGasTipCmp(b, baseFee256)
// accepted into a block with an invalid effective tip.
tip1, _ := a.EffectiveGasTip(baseFee)
tip2, _ := b.EffectiveGasTip(baseFee)
return tip1.Cmp(tip2)
}) })
var prices []*big.Int var prices []*big.Int

View file

@ -32,6 +32,7 @@ import (
"github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/crypto/keccak"
"github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/internal/testrand" "github.com/ethereum/go-ethereum/internal/testrand"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
@ -41,7 +42,6 @@ import (
"github.com/ethereum/go-ethereum/triedb" "github.com/ethereum/go-ethereum/triedb"
"github.com/ethereum/go-ethereum/triedb/pathdb" "github.com/ethereum/go-ethereum/triedb/pathdb"
"github.com/holiman/uint256" "github.com/holiman/uint256"
"golang.org/x/crypto/sha3"
) )
func TestHashing(t *testing.T) { func TestHashing(t *testing.T) {
@ -55,7 +55,7 @@ func TestHashing(t *testing.T) {
} }
var want, got string var want, got string
var old = func() { var old = func() {
hasher := sha3.NewLegacyKeccak256() hasher := keccak.NewLegacyKeccak256()
for i := 0; i < len(bytecodes); i++ { for i := 0; i < len(bytecodes); i++ {
hasher.Reset() hasher.Reset()
hasher.Write(bytecodes[i]) hasher.Write(bytecodes[i])
@ -88,7 +88,7 @@ func BenchmarkHashing(b *testing.B) {
bytecodes[i] = buf bytecodes[i] = buf
} }
var old = func() { var old = func() {
hasher := sha3.NewLegacyKeccak256() hasher := keccak.NewLegacyKeccak256()
for i := 0; i < len(bytecodes); i++ { for i := 0; i < len(bytecodes); i++ {
hasher.Reset() hasher.Reset()
hasher.Write(bytecodes[i]) hasher.Write(bytecodes[i])

View file

@ -1373,3 +1373,350 @@ func TestStandardTraceBlockToFile(t *testing.T) {
} }
} }
} }
func TestTraceBadBlock(t *testing.T) {
t.Parallel()
var (
accounts = newAccounts(2)
storageContract = common.HexToAddress("0x00000000000000000000000000000000deadbeef")
signer = types.HomesteadSigner{}
txHashs = make([]common.Hash, 0, 2)
genesis = &core.Genesis{
Config: params.TestChainConfig,
Alloc: types.GenesisAlloc{
accounts[0].addr: {Balance: big.NewInt(params.Ether)},
accounts[1].addr: {Balance: big.NewInt(params.Ether)},
storageContract: {
Nonce: 1,
Balance: big.NewInt(0),
Code: []byte{
byte(vm.PUSH1), 0x2a, // push 42
byte(vm.PUSH1), 0x00, // push slot 0
byte(vm.SSTORE), // sstore(0, 42)
byte(vm.STOP),
},
},
},
}
)
backend := newTestBackend(t, 1, genesis, func(i int, b *core.BlockGen) {
// tx 0: plain transfer
tx, _ := types.SignTx(types.NewTx(&types.LegacyTx{
Nonce: 0,
To: &accounts[1].addr,
Value: big.NewInt(1000),
Gas: params.TxGas,
GasPrice: b.BaseFee(),
Data: nil}),
signer, accounts[0].key)
b.AddTx(tx)
txHashs = append(txHashs, tx.Hash())
// tx 1: call storage contract (executes PUSH1, PUSH1, SSTORE, STOP)
tx, _ = types.SignTx(types.NewTx(&types.LegacyTx{
Nonce: 1,
To: &storageContract,
Value: big.NewInt(0),
Gas: 50000,
GasPrice: b.BaseFee(),
Data: nil}),
signer, accounts[0].key)
b.AddTx(tx)
txHashs = append(txHashs, tx.Hash())
})
defer backend.teardown()
// Write the block as a bad block so parent state is available
block := backend.chain.GetBlockByNumber(1)
rawdb.WriteBadBlock(backend.chaindb, block)
api := NewAPI(backend)
result, err := api.TraceBadBlock(context.Background(), block.Hash(), nil)
if err != nil {
t.Fatalf("want no error, have %v", err)
}
if len(result) != 2 {
t.Fatalf("expected 2 tx traces, got %d", len(result))
}
// First tx: plain transfer
have, _ := json.Marshal(result)
var traces []struct {
TxHash common.Hash `json:"txHash"`
Result struct {
Gas uint64 `json:"gas"`
Failed bool `json:"failed"`
StructLogs []json.RawMessage `json:"structLogs"`
} `json:"result"`
}
if err := json.Unmarshal(have, &traces); err != nil {
t.Fatalf("failed to unmarshal traces: %v", err)
}
if traces[0].TxHash != txHashs[0] {
t.Errorf("tx 0: hash mismatch, have %v, want %v", traces[0].TxHash, txHashs[0])
}
if traces[0].Result.Gas != params.TxGas {
t.Errorf("tx 0: gas mismatch, have %d, want %d", traces[0].Result.Gas, params.TxGas)
}
if len(traces[0].Result.StructLogs) != 0 {
t.Errorf("tx 0: expected empty structLogs for plain transfer, got %d entries", len(traces[0].Result.StructLogs))
}
// Second tx: contract call
if traces[1].TxHash != txHashs[1] {
t.Errorf("tx 1: hash mismatch, have %v, want %v", traces[1].TxHash, txHashs[1])
}
if traces[1].Result.Failed {
t.Error("tx 1: expected success, got failed")
}
// Contract has 4 opcodes: PUSH1, PUSH1, SSTORE, STOP
if len(traces[1].Result.StructLogs) != 4 {
t.Errorf("tx 1: expected 4 structLog entries for contract call, got %d", len(traces[1].Result.StructLogs))
}
// Non-existent bad block
_, err = api.TraceBadBlock(context.Background(), common.Hash{42}, nil)
if err == nil {
t.Fatal("want error for non-existent bad block, have none")
}
wantErr := fmt.Sprintf("bad block %#x not found", common.Hash{42})
if err.Error() != wantErr {
t.Errorf("error mismatch, want '%s', have '%v'", wantErr, err)
}
}
func TestIntermediateRoots(t *testing.T) {
t.Parallel()
// Initialize test accounts and a contract that writes to storage.
var (
accounts = newAccounts(2)
storageContract = common.HexToAddress("0x00000000000000000000000000000000deadbeef")
signer = types.HomesteadSigner{}
genesis = &core.Genesis{
Config: params.TestChainConfig,
Alloc: types.GenesisAlloc{
accounts[0].addr: {Balance: big.NewInt(params.Ether)},
accounts[1].addr: {Balance: big.NewInt(params.Ether)},
// Contract: SSTORE(CALLVALUE, CALLVALUE)
storageContract: {
Nonce: 1,
Balance: big.NewInt(0),
Code: []byte{
byte(vm.CALLVALUE),
byte(vm.CALLVALUE),
byte(vm.SSTORE),
byte(vm.STOP),
},
},
},
}
)
backend := newTestBackend(t, 1, genesis, func(i int, b *core.BlockGen) {
// tx 0: plain transfer
tx, _ := types.SignTx(types.NewTx(&types.LegacyTx{
Nonce: 0,
To: &accounts[1].addr,
Value: big.NewInt(1000),
Gas: params.TxGas,
GasPrice: b.BaseFee(),
Data: nil}),
signer, accounts[0].key)
b.AddTx(tx)
// tx 1: sstore(1, 1)
tx, _ = types.SignTx(types.NewTx(&types.LegacyTx{
Nonce: 1,
To: &storageContract,
Value: big.NewInt(1),
Gas: 50000,
GasPrice: b.BaseFee(),
Data: nil}),
signer, accounts[0].key)
b.AddTx(tx)
// tx 2: sstore(2, 2)
tx, _ = types.SignTx(types.NewTx(&types.LegacyTx{
Nonce: 2,
To: &storageContract,
Value: big.NewInt(2),
Gas: 50000,
GasPrice: b.BaseFee(),
Data: nil}),
signer, accounts[0].key)
b.AddTx(tx)
})
defer backend.teardown()
api := NewAPI(backend)
block := backend.chain.GetBlockByNumber(1)
// Should return one root per tx
roots, err := api.IntermediateRoots(context.Background(), block.Hash(), nil)
if err != nil {
t.Fatalf("want no error, have %v", err)
}
if len(roots) != 3 {
t.Fatalf("root count mismatch, have %d, want 3", len(roots))
}
for i, root := range roots {
if root == (common.Hash{}) {
t.Errorf("root[%d] should not be zero", i)
}
}
if roots[0] == roots[1] {
t.Error("root[0] and root[1] should differ (transfer vs sstore)")
}
if roots[1] == roots[2] {
t.Error("root[1] and root[2] should differ (sstore to different slots)")
}
// Intermediate roots of a bad block
rawdb.WriteBadBlock(backend.chaindb, block)
badRoots, err := api.IntermediateRoots(context.Background(), block.Hash(), nil)
if err != nil {
t.Fatalf("want no error for bad block fallback, have %v", err)
}
if !reflect.DeepEqual(roots, badRoots) {
t.Errorf("bad block roots mismatch, have %v, want %v", badRoots, roots)
}
// Genesis block: should return error
genesisBlock := backend.chain.GetBlockByNumber(0)
_, err = api.IntermediateRoots(context.Background(), genesisBlock.Hash(), nil)
if err == nil || err.Error() != "genesis is not traceable" {
t.Fatalf("want 'genesis is not traceable' error, have %v", err)
}
// Non-existent block: should return error
_, err = api.IntermediateRoots(context.Background(), common.Hash{42}, nil)
if err == nil {
t.Fatal("want error for non-existent block, have none")
}
wantErr := fmt.Sprintf("block %#x not found", common.Hash{42})
if err.Error() != wantErr {
t.Errorf("error mismatch, want '%s', have '%v'", wantErr, err)
}
}
func TestStandardTraceBadBlockToFile(t *testing.T) {
var (
key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
address = crypto.PubkeyToAddress(key.PublicKey)
funds = big.NewInt(1000000000000000)
aa = common.HexToAddress("0x7217d81b76bdd8707601e959454e3d776aee5f43")
aaCode = []byte{byte(vm.PUSH1), 0x00, byte(vm.POP)}
bb = common.HexToAddress("0x7217d81b76bdd8707601e959454e3d776aee5f44")
bbCode = []byte{byte(vm.PUSH2), 0x00, 0x01, byte(vm.POP)}
)
genesis := &core.Genesis{
Config: params.TestChainConfig,
Alloc: types.GenesisAlloc{
address: {Balance: funds},
aa: {
Code: aaCode,
Nonce: 1,
Balance: big.NewInt(0),
},
bb: {
Code: bbCode,
Nonce: 1,
Balance: big.NewInt(0),
},
},
}
txHashs := make([]common.Hash, 0, 2)
backend := newTestBackend(t, 1, genesis, func(i int, b *core.BlockGen) {
b.SetCoinbase(common.Address{1})
tx, _ := types.SignTx(types.NewTx(&types.LegacyTx{
Nonce: 0,
To: &aa,
Value: big.NewInt(0),
Gas: 50000,
GasPrice: b.BaseFee(),
Data: nil,
}), types.HomesteadSigner{}, key)
b.AddTx(tx)
txHashs = append(txHashs, tx.Hash())
tx, _ = types.SignTx(types.NewTx(&types.LegacyTx{
Nonce: 1,
To: &bb,
Value: big.NewInt(1),
Gas: 100000,
GasPrice: b.BaseFee(),
Data: nil,
}), types.HomesteadSigner{}, key)
b.AddTx(tx)
txHashs = append(txHashs, tx.Hash())
})
defer backend.teardown()
// Write the block as a bad block
block := backend.chain.GetBlockByNumber(1)
rawdb.WriteBadBlock(backend.chaindb, block)
var testSuite = []struct {
config *StdTraceConfig
want []string
}{
{
// All txs traced
config: nil,
want: []string{
`{"pc":0,"op":96,"gas":"0x7148","gasCost":"0x3","memSize":0,"stack":[],"depth":1,"refund":0,"opName":"PUSH1"}
{"pc":2,"op":80,"gas":"0x7145","gasCost":"0x2","memSize":0,"stack":["0x0"],"depth":1,"refund":0,"opName":"POP"}
{"pc":3,"op":0,"gas":"0x7143","gasCost":"0x0","memSize":0,"stack":[],"depth":1,"refund":0,"opName":"STOP"}
{"output":"","gasUsed":"0x5"}
`,
`{"pc":0,"op":97,"gas":"0x13498","gasCost":"0x3","memSize":0,"stack":[],"depth":1,"refund":0,"opName":"PUSH2"}
{"pc":3,"op":80,"gas":"0x13495","gasCost":"0x2","memSize":0,"stack":["0x1"],"depth":1,"refund":0,"opName":"POP"}
{"pc":4,"op":0,"gas":"0x13493","gasCost":"0x0","memSize":0,"stack":[],"depth":1,"refund":0,"opName":"STOP"}
{"output":"","gasUsed":"0x5"}
`,
},
},
{
// Specific tx traced
config: &StdTraceConfig{TxHash: txHashs[1]},
want: []string{
`{"pc":0,"op":97,"gas":"0x13498","gasCost":"0x3","memSize":0,"stack":[],"depth":1,"refund":0,"opName":"PUSH2"}
{"pc":3,"op":80,"gas":"0x13495","gasCost":"0x2","memSize":0,"stack":["0x1"],"depth":1,"refund":0,"opName":"POP"}
{"pc":4,"op":0,"gas":"0x13493","gasCost":"0x0","memSize":0,"stack":[],"depth":1,"refund":0,"opName":"STOP"}
{"output":"","gasUsed":"0x5"}
`,
},
},
}
api := NewAPI(backend)
for i, tc := range testSuite {
txTraces, err := api.StandardTraceBadBlockToFile(context.Background(), block.Hash(), tc.config)
if err != nil {
t.Fatalf("test %d: unexpected error %v", i, err)
}
if len(txTraces) != len(tc.want) {
t.Fatalf("test %d: file count mismatch, have %d, want %d", i, len(txTraces), len(tc.want))
}
for j, traceFileName := range txTraces {
defer os.Remove(traceFileName)
traceReceived, err := os.ReadFile(traceFileName)
if err != nil {
t.Fatalf("test %d: could not read trace file: %v", i, err)
}
if tc.want[j] != string(traceReceived) {
t.Fatalf("test %d, trace %d: result mismatch\nhave:\n%s\nwant:\n%s", i, j, string(traceReceived), tc.want[j])
}
}
}
// Non-existent bad block
_, err := api.StandardTraceBadBlockToFile(context.Background(), common.Hash{42}, nil)
if err == nil {
t.Fatal("want error for non-existent bad block, have none")
}
}

View file

@ -43,6 +43,7 @@ type callLog struct {
Address common.Address `json:"address"` Address common.Address `json:"address"`
Topics []common.Hash `json:"topics"` Topics []common.Hash `json:"topics"`
Data hexutil.Bytes `json:"data"` Data hexutil.Bytes `json:"data"`
Index hexutil.Uint `json:"index"`
Position hexutil.Uint `json:"position"` Position hexutil.Uint `json:"position"`
} }
@ -300,7 +301,7 @@ func TestInternals(t *testing.T) {
byte(vm.LOG0), byte(vm.LOG0),
}, },
tracer: mkTracer("callTracer", json.RawMessage(`{ "withLog": true }`)), tracer: mkTracer("callTracer", json.RawMessage(`{ "withLog": true }`)),
want: fmt.Sprintf(`{"from":"%s","gas":"0x13880","gasUsed":"0x5b9e","to":"0x00000000000000000000000000000000deadbeef","input":"0x","logs":[{"address":"0x00000000000000000000000000000000deadbeef","topics":[],"data":"0x000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","position":"0x0"}],"value":"0x0","type":"CALL"}`, originHex), want: fmt.Sprintf(`{"from":"%s","gas":"0x13880","gasUsed":"0x5b9e","to":"0x00000000000000000000000000000000deadbeef","input":"0x","logs":[{"address":"0x00000000000000000000000000000000deadbeef","topics":[],"data":"0x000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","index":"0x0","position":"0x0"}],"value":"0x0","type":"CALL"}`, originHex),
}, },
{ {
// Leads to OOM on the prestate tracer // Leads to OOM on the prestate tracer

View file

@ -0,0 +1,653 @@
// 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 tracetest
import (
"math/big"
"testing"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/consensus"
"github.com/ethereum/go-ethereum/consensus/beacon"
"github.com/ethereum/go-ethereum/consensus/ethash"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/tracing"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/params"
)
// accountState represents the expected final state of an account
type accountState struct {
Balance *big.Int
Nonce uint64
Code []byte
Exists bool
}
// selfdestructStateTracer tracks state changes during selfdestruct operations
type selfdestructStateTracer struct {
env *tracing.VMContext
accounts map[common.Address]*accountState
}
func newSelfdestructStateTracer() *selfdestructStateTracer {
return &selfdestructStateTracer{
accounts: make(map[common.Address]*accountState),
}
}
func (t *selfdestructStateTracer) OnTxStart(env *tracing.VMContext, tx *types.Transaction, from common.Address) {
t.env = env
}
func (t *selfdestructStateTracer) OnTxEnd(receipt *types.Receipt, err error) {
// Nothing to do
}
func (t *selfdestructStateTracer) getOrCreateAccount(addr common.Address) *accountState {
if acc, ok := t.accounts[addr]; ok {
return acc
}
// Initialize with current state from statedb
acc := &accountState{
Balance: t.env.StateDB.GetBalance(addr).ToBig(),
Nonce: t.env.StateDB.GetNonce(addr),
Code: t.env.StateDB.GetCode(addr),
Exists: t.env.StateDB.Exist(addr),
}
t.accounts[addr] = acc
return acc
}
func (t *selfdestructStateTracer) OnBalanceChange(addr common.Address, prev, new *big.Int, reason tracing.BalanceChangeReason) {
acc := t.getOrCreateAccount(addr)
acc.Balance = new
}
func (t *selfdestructStateTracer) OnNonceChangeV2(addr common.Address, prev, new uint64, reason tracing.NonceChangeReason) {
acc := t.getOrCreateAccount(addr)
acc.Nonce = new
// If this is a selfdestruct nonce change, mark account as not existing
if reason == tracing.NonceChangeSelfdestruct {
acc.Exists = false
}
}
func (t *selfdestructStateTracer) OnCodeChangeV2(addr common.Address, prevCodeHash common.Hash, prevCode []byte, codeHash common.Hash, code []byte, reason tracing.CodeChangeReason) {
acc := t.getOrCreateAccount(addr)
acc.Code = code
// If this is a selfdestruct code change, mark account as not existing
if reason == tracing.CodeChangeSelfDestruct {
acc.Exists = false
}
}
func (t *selfdestructStateTracer) Hooks() *tracing.Hooks {
return &tracing.Hooks{
OnTxStart: t.OnTxStart,
OnTxEnd: t.OnTxEnd,
OnBalanceChange: t.OnBalanceChange,
OnNonceChangeV2: t.OnNonceChangeV2,
OnCodeChangeV2: t.OnCodeChangeV2,
}
}
func (t *selfdestructStateTracer) Accounts() map[common.Address]*accountState {
return t.accounts
}
// verifyAccountState compares actual and expected account state and reports any mismatches
func verifyAccountState(t *testing.T, addr common.Address, actual, expected *accountState) {
if actual.Balance.Cmp(expected.Balance) != 0 {
t.Errorf("address %s: balance mismatch: have %s, want %s",
addr.Hex(), actual.Balance, expected.Balance)
}
if actual.Nonce != expected.Nonce {
t.Errorf("address %s: nonce mismatch: have %d, want %d",
addr.Hex(), actual.Nonce, expected.Nonce)
}
if len(actual.Code) != len(expected.Code) {
t.Errorf("address %s: code length mismatch: have %d, want %d",
addr.Hex(), len(actual.Code), len(expected.Code))
}
if actual.Exists != expected.Exists {
t.Errorf("address %s: exists mismatch: have %v, want %v",
addr.Hex(), actual.Exists, expected.Exists)
}
}
// setupTestBlockchain creates a blockchain with the given genesis and transaction,
// returns the blockchain, the first block, and a statedb at genesis for testing
func setupTestBlockchain(t *testing.T, genesis *core.Genesis, tx *types.Transaction, useBeacon bool) (*core.BlockChain, *types.Block, *state.StateDB) {
var engine consensus.Engine
if useBeacon {
engine = beacon.New(ethash.NewFaker())
} else {
engine = ethash.NewFaker()
}
_, blocks, _ := core.GenerateChainWithGenesis(genesis, engine, 1, func(i int, b *core.BlockGen) {
b.AddTx(tx)
})
db := rawdb.NewMemoryDatabase()
blockchain, err := core.NewBlockChain(db, genesis, engine, nil)
if err != nil {
t.Fatalf("failed to create blockchain: %v", err)
}
if _, err := blockchain.InsertChain(blocks); err != nil {
t.Fatalf("failed to insert chain: %v", err)
}
genesisBlock := blockchain.GetBlockByNumber(0)
if genesisBlock == nil {
t.Fatalf("failed to get genesis block")
}
statedb, err := blockchain.StateAt(genesisBlock.Root())
if err != nil {
t.Fatalf("failed to get state: %v", err)
}
return blockchain, blocks[0], statedb
}
func TestSelfdestructStateTracer(t *testing.T) {
t.Parallel()
const (
// Gas limit high enough for all test scenarios (factory creation + multiple calls)
testGasLimit = 500000
// Common balance amounts used across tests
testBalanceInitial = 100 // Initial balance for contracts being tested
testBalanceSent = 50 // Amount sent back in sendback tests
testBalanceFactory = 200 // Factory needs extra balance for contract creation
)
// Helper to create *big.Int for wei amounts
wei := func(amount int64) *big.Int {
return big.NewInt(amount)
}
// Test account (transaction sender)
var (
key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
caller = crypto.PubkeyToAddress(key.PublicKey)
)
// Simple selfdestruct test contracts
var (
contract = common.HexToAddress("0x00000000000000000000000000000000000000bb")
recipient = common.HexToAddress("0x00000000000000000000000000000000000000cc")
)
// Build selfdestruct code: PUSH20 <recipient> SELFDESTRUCT
selfdestructCode := []byte{byte(vm.PUSH20)}
selfdestructCode = append(selfdestructCode, recipient.Bytes()...)
selfdestructCode = append(selfdestructCode, byte(vm.SELFDESTRUCT))
// Factory test contracts (create-and-destroy pattern)
var (
factory = common.HexToAddress("0x00000000000000000000000000000000000000ff")
)
// Factory code: creates a contract with 100 wei and calls it to trigger selfdestruct back to factory
// See selfdestruct_test_contracts/factory.yul for source
// Runtime bytecode compiled with: solc --strict-assembly --evm-version paris factory.yul --bin
// (Using paris to avoid PUSH0 opcode which is not available pre-Shanghai)
var (
factoryCode = common.Hex2Bytes("6a6133ff6000526002601ef360a81b600052600080808080600b816064f05af100")
createdContractAddr = crypto.CreateAddress(factory, 0) // Address where factory creates the contract
)
// Sendback test contracts (A→B→A pattern)
// For the refund test: Coordinator calls A, then B
// A selfdestructs to B, B sends funds back to A
var (
contractA = common.HexToAddress("0x00000000000000000000000000000000000000aa")
contractB = common.HexToAddress("0x00000000000000000000000000000000000000bb")
coordinator = common.HexToAddress("0x00000000000000000000000000000000000000cc")
)
// Contract A: if msg.value > 0, accept funds; else selfdestruct to B
// See selfdestruct_test_contracts/contractA.yul for source
// Runtime bytecode compiled with: solc --strict-assembly --evm-version paris contractA.yul --bin
contractACode := common.Hex2Bytes("60003411600a5760bbff5b00")
// Contract B: sends 50 wei back to contract A
// See selfdestruct_test_contracts/contractB.yul for source
// Runtime bytecode compiled with: solc --strict-assembly --evm-version paris contractB.yul --bin
contractBCode := common.Hex2Bytes("6000808080603260aa5af100")
// Coordinator: calls A (A selfdestructs to B), then calls B (B sends funds to A)
// See selfdestruct_test_contracts/coordinator.yul for source
// Runtime bytecode compiled with: solc --strict-assembly --evm-version paris coordinator.yul --bin
coordinatorCode := common.Hex2Bytes("60008080808060aa818080808060bb955af1505af100")
// Factory for create-and-refund test: creates A with 100 wei, calls A, calls B
// See selfdestruct_test_contracts/factoryRefund.yul for source
// Runtime bytecode compiled with: solc --strict-assembly --evm-version paris factoryRefund.yul --bin
var (
factoryRefund = common.HexToAddress("0x00000000000000000000000000000000000000dd")
factoryRefundCode = common.Hex2Bytes("60008080808060bb78600c600d600039600c6000f3fe60003411600a5760bbff5b0082528180808080601960076064f05af1505af100")
createdContractAddrA = crypto.CreateAddress(factoryRefund, 0) // Address where factory creates contract A
)
// Self-destruct-to-self test contracts
var (
contractSelfDestruct = common.HexToAddress("0x00000000000000000000000000000000000000aa")
coordinatorSendAfter = common.HexToAddress("0x00000000000000000000000000000000000000ee")
)
// Contract that selfdestructs to self
// See selfdestruct_test_contracts/contractSelfDestruct.yul
contractSelfDestructCode := common.Hex2Bytes("30ff")
// Coordinator: calls contract (triggers selfdestruct to self), stores balance, sends 50 wei, stores balance again
// See selfdestruct_test_contracts/coordinatorSendAfter.yul
coordinatorSendAfterCode := common.Hex2Bytes("60aa600080808080855af150803160005560008080806032855af1503160015500")
// Factory with balance checking: creates contract, calls it, checks balances
// See selfdestruct_test_contracts/factorySelfDestructBalanceCheck.yul
var (
factorySelfDestructBalanceCheck = common.HexToAddress("0x00000000000000000000000000000000000000fd")
factorySelfDestructBalanceCheckCode = common.Hex2Bytes("6e6002600d60003960026000f3fe30ff600052600f60116064f0600080808080855af150803160005560008080806032855af1503160015500")
createdContractAddrSelfBalanceCheck = crypto.CreateAddress(factorySelfDestructBalanceCheck, 0)
)
tests := []struct {
name string
description string
targetContract common.Address
genesis *core.Genesis
useBeacon bool
expectedResults map[common.Address]accountState
expectedStorage map[common.Address]map[uint64]*big.Int
}{
{
name: "pre_6780_existing",
description: "Pre-EIP-6780: Existing contract selfdestructs to recipient. Contract should be destroyed and balance transferred.",
targetContract: contract,
genesis: &core.Genesis{
Config: params.AllEthashProtocolChanges,
Alloc: types.GenesisAlloc{
caller: {Balance: big.NewInt(params.Ether)},
contract: {
Balance: wei(testBalanceInitial),
Code: selfdestructCode,
},
},
},
useBeacon: false,
expectedResults: map[common.Address]accountState{
contract: {
Balance: wei(0),
Nonce: 0,
Code: []byte{},
Exists: false,
},
recipient: {
Balance: wei(testBalanceInitial), // Received contract's balance
Nonce: 0,
Code: []byte{},
Exists: true,
},
},
},
{
name: "post_6780_existing",
description: "Post-EIP-6780: Existing contract selfdestructs to recipient. Balance transferred but contract NOT destroyed (code/storage remain).",
targetContract: contract,
genesis: &core.Genesis{
Config: params.AllDevChainProtocolChanges,
Alloc: types.GenesisAlloc{
caller: {Balance: big.NewInt(params.Ether)},
contract: {
Balance: wei(testBalanceInitial),
Code: selfdestructCode,
},
},
},
useBeacon: true,
expectedResults: map[common.Address]accountState{
contract: {
Balance: wei(0),
Nonce: 0,
Code: selfdestructCode,
Exists: true,
},
recipient: {
Balance: wei(testBalanceInitial),
Nonce: 0,
Code: []byte{},
Exists: true,
},
},
},
{
name: "pre_6780_create_destroy",
description: "Pre-EIP-6780: Factory creates contract with 100 wei, contract selfdestructs back to factory. Contract destroyed, factory gets refund.",
targetContract: factory,
genesis: &core.Genesis{
Config: params.AllEthashProtocolChanges,
Alloc: types.GenesisAlloc{
caller: {Balance: big.NewInt(params.Ether)},
factory: {
Balance: wei(testBalanceFactory),
Code: factoryCode,
},
},
},
useBeacon: false,
expectedResults: map[common.Address]accountState{
factory: {
Balance: wei(testBalanceFactory),
Nonce: 1,
Code: factoryCode,
Exists: true,
},
createdContractAddr: {
Balance: wei(0),
Nonce: 0,
Code: []byte{},
Exists: false,
},
},
},
{
name: "post_6780_create_destroy",
description: "Post-EIP-6780: Factory creates contract with 100 wei, contract selfdestructs back to factory. Contract destroyed (EIP-6780 exception for same-tx creation).",
targetContract: factory,
genesis: &core.Genesis{
Config: params.AllDevChainProtocolChanges,
Alloc: types.GenesisAlloc{
caller: {Balance: big.NewInt(params.Ether)},
factory: {
Balance: wei(testBalanceFactory),
Code: factoryCode,
},
},
},
useBeacon: true,
expectedResults: map[common.Address]accountState{
factory: {
Balance: wei(testBalanceFactory),
Nonce: 1,
Code: factoryCode,
Exists: true,
},
createdContractAddr: {
Balance: wei(0),
Nonce: 0,
Code: []byte{},
Exists: false,
},
},
},
{
name: "pre_6780_sendback",
description: "Pre-EIP-6780: Contract A selfdestructs sending funds to B, then B sends funds back to A's address. Funds sent to destroyed address are burnt.",
targetContract: coordinator,
genesis: &core.Genesis{
Config: params.AllEthashProtocolChanges,
Alloc: types.GenesisAlloc{
caller: {Balance: big.NewInt(params.Ether)},
contractA: {
Balance: wei(testBalanceInitial),
Code: contractACode,
},
contractB: {
Balance: wei(0),
Code: contractBCode,
},
coordinator: {
Code: coordinatorCode,
},
},
},
useBeacon: false,
expectedResults: map[common.Address]accountState{
contractA: {
Balance: wei(0),
Nonce: 0,
Code: []byte{},
Exists: false,
},
contractB: {
// 100 received - 50 sent back
Balance: wei(testBalanceSent),
Nonce: 0,
Code: contractBCode,
Exists: true,
},
},
},
{
name: "post_6780_existing_sendback",
description: "Post-EIP-6780: Existing contract A selfdestructs to B, then B sends funds back to A. Funds are NOT burnt (A still exists post-6780).",
targetContract: coordinator,
genesis: &core.Genesis{
Config: params.AllDevChainProtocolChanges,
Alloc: types.GenesisAlloc{
caller: {Balance: big.NewInt(params.Ether)},
contractA: {
Balance: wei(testBalanceInitial),
Code: contractACode,
},
contractB: {
Balance: wei(0),
Code: contractBCode,
},
coordinator: {
Code: coordinatorCode,
},
},
},
useBeacon: true,
expectedResults: map[common.Address]accountState{
contractA: {
Balance: wei(testBalanceSent),
Nonce: 0,
Code: contractACode,
Exists: true,
},
contractB: {
Balance: wei(testBalanceSent),
Nonce: 0,
Code: contractBCode,
Exists: true,
},
},
},
{
name: "post_6780_create_destroy_sendback",
description: "Post-EIP-6780: Factory creates A, A selfdestructs to B, B sends funds back to A. Funds are burnt (A was destroyed via EIP-6780 exception).",
targetContract: factoryRefund,
genesis: &core.Genesis{
Config: params.AllDevChainProtocolChanges,
Alloc: types.GenesisAlloc{
caller: {Balance: big.NewInt(params.Ether)},
contractB: {
Balance: wei(0),
Code: contractBCode,
},
factoryRefund: {
Balance: wei(testBalanceFactory),
Code: factoryRefundCode,
},
},
},
useBeacon: true,
expectedResults: map[common.Address]accountState{
createdContractAddrA: {
// Funds sent back are burnt!
Balance: wei(0),
Nonce: 0,
Code: []byte{},
Exists: false,
},
contractB: {
Balance: wei(testBalanceSent),
Nonce: 0,
Code: contractBCode,
Exists: true,
},
},
},
{
name: "post_6780_existing_to_self",
description: "Post-EIP-6780: Pre-existing contract selfdestructs to itself. Balance NOT burnt (selfdestruct-to-self is no-op for existing contracts).",
targetContract: coordinatorSendAfter,
genesis: &core.Genesis{
Config: params.AllDevChainProtocolChanges,
Alloc: types.GenesisAlloc{
caller: {Balance: big.NewInt(params.Ether)},
contractSelfDestruct: {
Balance: wei(testBalanceInitial),
Code: contractSelfDestructCode,
},
coordinatorSendAfter: {
Balance: wei(testBalanceInitial),
Code: coordinatorSendAfterCode,
},
},
},
useBeacon: true,
expectedResults: map[common.Address]accountState{
contractSelfDestruct: {
Balance: wei(150),
Nonce: 0,
Code: contractSelfDestructCode,
Exists: true,
},
coordinatorSendAfter: {
Balance: wei(testBalanceSent),
Nonce: 0,
Code: coordinatorSendAfterCode,
Exists: true,
},
},
expectedStorage: map[common.Address]map[uint64]*big.Int{
coordinatorSendAfter: {
0: wei(testBalanceInitial),
1: wei(150),
},
},
},
{
name: "post_6780_create_destroy_to_self",
description: "Post-EIP-6780: Factory creates contract, contract selfdestructs to itself. Balance IS burnt and contract destroyed (EIP-6780 exception for same-tx creation).",
targetContract: factorySelfDestructBalanceCheck,
genesis: &core.Genesis{
Config: params.AllDevChainProtocolChanges,
Alloc: types.GenesisAlloc{
caller: {Balance: big.NewInt(params.Ether)},
factorySelfDestructBalanceCheck: {
Balance: wei(testBalanceFactory),
Code: factorySelfDestructBalanceCheckCode,
},
},
},
useBeacon: true,
expectedResults: map[common.Address]accountState{
createdContractAddrSelfBalanceCheck: {
Balance: wei(0),
Nonce: 0,
Code: []byte{},
Exists: false,
},
factorySelfDestructBalanceCheck: {
Balance: wei(testBalanceSent),
Nonce: 1,
Code: factorySelfDestructBalanceCheckCode,
Exists: true,
},
},
expectedStorage: map[common.Address]map[uint64]*big.Int{
factorySelfDestructBalanceCheck: {
0: wei(0),
1: wei(0),
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
var (
signer = types.HomesteadSigner{}
tx *types.Transaction
err error
)
tx, err = types.SignTx(types.NewTx(&types.LegacyTx{
Nonce: 0,
To: &tt.targetContract,
Value: big.NewInt(0),
Gas: testGasLimit,
GasPrice: big.NewInt(params.InitialBaseFee * 2),
Data: nil,
}), signer, key)
if err != nil {
t.Fatalf("failed to sign transaction: %v", err)
}
blockchain, block, statedb := setupTestBlockchain(t, tt.genesis, tx, tt.useBeacon)
defer blockchain.Stop()
tracer := newSelfdestructStateTracer()
hookedState := state.NewHookedState(statedb, tracer.Hooks())
msg, err := core.TransactionToMessage(tx, signer, nil)
if err != nil {
t.Fatalf("failed to prepare transaction for tracing: %v", err)
}
context := core.NewEVMBlockContext(block.Header(), blockchain, nil)
evm := vm.NewEVM(context, hookedState, tt.genesis.Config, vm.Config{Tracer: tracer.Hooks()})
usedGas := uint64(0)
_, err = core.ApplyTransactionWithEVM(msg, new(core.GasPool).AddGas(tx.Gas()), statedb, block.Number(), block.Hash(), block.Time(), tx, &usedGas, evm)
if err != nil {
t.Fatalf("failed to execute transaction: %v", err)
}
results := tracer.Accounts()
// Verify storage
for addr, expectedSlots := range tt.expectedStorage {
for slot, expectedValue := range expectedSlots {
actualValue := statedb.GetState(addr, common.BigToHash(big.NewInt(int64(slot))))
if actualValue.Big().Cmp(expectedValue) != 0 {
t.Errorf("address %s slot %d: storage mismatch: have %s, want %s",
addr.Hex(), slot, actualValue.Big(), expectedValue)
}
}
}
// Verify results
for addr, expected := range tt.expectedResults {
actual, ok := results[addr]
if !ok {
t.Errorf("address %s missing from results", addr.Hex())
continue
}
verifyAccountState(t, addr, actual, &expected)
}
})
}
}

View file

@ -0,0 +1,18 @@
object "ContractA" {
code {
datacopy(0, dataoffset("Runtime"), datasize("Runtime"))
return(0, datasize("Runtime"))
}
object "Runtime" {
code {
// If receiving funds (msg.value > 0), just accept them and return
if gt(callvalue(), 0) {
stop()
}
// Otherwise, selfdestruct to B (transfers balance immediately, then stops execution)
let contractB := 0x00000000000000000000000000000000000000bb
selfdestruct(contractB)
}
}
}

View file

@ -0,0 +1,14 @@
object "ContractB" {
code {
datacopy(0, dataoffset("Runtime"), datasize("Runtime"))
return(0, datasize("Runtime"))
}
object "Runtime" {
code {
// Send 50 wei back to contract A
let contractA := 0x00000000000000000000000000000000000000aa
let success := call(gas(), contractA, 50, 0, 0, 0, 0)
stop()
}
}
}

View file

@ -0,0 +1,12 @@
object "ContractSelfDestruct" {
code {
datacopy(0, dataoffset("Runtime"), datasize("Runtime"))
return(0, datasize("Runtime"))
}
object "Runtime" {
code {
// Simply selfdestruct to self
selfdestruct(address())
}
}
}

View file

@ -0,0 +1,20 @@
object "Coordinator" {
code {
datacopy(0, dataoffset("Runtime"), datasize("Runtime"))
return(0, datasize("Runtime"))
}
object "Runtime" {
code {
let contractA := 0x00000000000000000000000000000000000000aa
let contractB := 0x00000000000000000000000000000000000000bb
// First, call A (A will selfdestruct to B)
pop(call(gas(), contractA, 0, 0, 0, 0, 0))
// Then, call B (B will send funds back to A)
pop(call(gas(), contractB, 0, 0, 0, 0, 0))
stop()
}
}
}

View file

@ -0,0 +1,27 @@
object "CoordinatorSendAfter" {
code {
datacopy(0, dataoffset("Runtime"), datasize("Runtime"))
return(0, datasize("Runtime"))
}
object "Runtime" {
code {
let contractAddr := 0x00000000000000000000000000000000000000aa
// Call contract (triggers selfdestruct to self, burning its balance)
pop(call(gas(), contractAddr, 0, 0, 0, 0, 0))
// Check contract's balance immediately after selfdestruct
// Store in slot 0 to verify it's 0 (proving immediate burn)
sstore(0, balance(contractAddr))
// Send 50 wei to the contract (after it selfdestructed)
pop(call(gas(), contractAddr, 50, 0, 0, 0, 0))
// Check balance again after sending funds
// Store in slot 1 to verify it's 50 (new funds not burnt)
sstore(1, balance(contractAddr))
stop()
}
}
}

View file

@ -0,0 +1,28 @@
object "FactoryRefund" {
code {
datacopy(0, dataoffset("Runtime"), datasize("Runtime"))
return(0, datasize("Runtime"))
}
object "Runtime" {
code {
let contractB := 0x00000000000000000000000000000000000000bb
// Store the deploy bytecode for contract A in memory
// Full deploy bytecode from: solc --strict-assembly --evm-version paris contractA.yul --bin
// Including the 0xfe separator: 600c600d600039600c6000f3fe60003411600a5760bbff5b00
// That's 25 bytes, padded to 32 bytes with 7 zero bytes at the front
mstore(0, 0x0000000000000000000000000000600c600d600039600c6000f3fe60003411600a5760bbff5b00)
// CREATE contract A with 100 wei, using 25 bytes starting at position 7
let contractA := create(100, 7, 25)
// Call contract A (triggers selfdestruct to B)
pop(call(gas(), contractA, 0, 0, 0, 0, 0))
// Call contract B (B sends 50 wei back to A)
pop(call(gas(), contractB, 0, 0, 0, 0, 0))
stop()
}
}
}

View file

@ -0,0 +1,35 @@
object "FactorySelfDestructBalanceCheck" {
code {
datacopy(0, dataoffset("Runtime"), datasize("Runtime"))
return(0, datasize("Runtime"))
}
object "Runtime" {
code {
// Get the full deploy bytecode for ContractSelfDestruct
// Compiled with: solc --strict-assembly --evm-version paris contractSelfDestruct.yul --bin
// Full bytecode: 6002600d60003960026000f3fe30ff
// That's 15 bytes total, padded to 32 bytes with 17 zero bytes at front
mstore(0, 0x0000000000000000000000000000000000000000006002600d60003960026000f3fe30ff)
// CREATE contract with 100 wei, using deploy bytecode
// The bytecode is 15 bytes, starts at position 17 in the 32-byte word
let contractAddr := create(100, 17, 15)
// Call the created contract (triggers selfdestruct to self)
pop(call(gas(), contractAddr, 0, 0, 0, 0, 0))
// Check contract's balance immediately after selfdestruct
// Store in slot 0 to verify it's 0 (proving immediate burn)
sstore(0, balance(contractAddr))
// Send 50 wei to the contract (after it selfdestructed)
pop(call(gas(), contractAddr, 50, 0, 0, 0, 0))
// Check balance again after sending funds
// Store in slot 1 to verify it's 0 (funds sent to destroyed contract are burnt)
sstore(1, balance(contractAddr))
stop()
}
}
}

View file

@ -94,7 +94,8 @@
"0xe1c52dc63b719ade82e8bea94cc41a0d5d28e4aaf536adb5e9cccc9ff8c1aeda" "0xe1c52dc63b719ade82e8bea94cc41a0d5d28e4aaf536adb5e9cccc9ff8c1aeda"
], ],
"data": "0x0000000000000000000000004f5777744b500616697cb655dcb02ee6cd51deb5be96016bb57376da7a6d296e0a405ee1501778227dfa604df0a81cb1ae018598", "data": "0x0000000000000000000000004f5777744b500616697cb655dcb02ee6cd51deb5be96016bb57376da7a6d296e0a405ee1501778227dfa604df0a81cb1ae018598",
"position": "0x0" "position": "0x0",
"index": "0x0"
}, },
{ {
"address": "0x200edd17f30485a8735878661960cd7a9a95733f", "address": "0x200edd17f30485a8735878661960cd7a9a95733f",
@ -102,7 +103,8 @@
"0xacbdb084c721332ac59f9b8e392196c9eb0e4932862da8eb9beaf0dad4f550da" "0xacbdb084c721332ac59f9b8e392196c9eb0e4932862da8eb9beaf0dad4f550da"
], ],
"data": "0x0000000000000000000000000000000000000000000000000000000000000000", "data": "0x0000000000000000000000000000000000000000000000000000000000000000",
"position": "0x0" "position": "0x0",
"index": "0x1"
} }
], ],
"value": "0x8ac7230489e80000", "value": "0x8ac7230489e80000",
@ -112,4 +114,4 @@
"value": "0x8ac7230489e80000", "value": "0x8ac7230489e80000",
"type": "CALL" "type": "CALL"
} }
} }

View file

@ -256,6 +256,7 @@
"0x0000000000000000000000006ca7f214ab2ddbb9a8e1a1e2c8550e3164e9dba5" "0x0000000000000000000000006ca7f214ab2ddbb9a8e1a1e2c8550e3164e9dba5"
], ],
"data": "0x00000000000000000000000000000000000000000000000080d29fa5cccfadac", "data": "0x00000000000000000000000000000000000000000000000080d29fa5cccfadac",
"index": "0x0",
"position": "0x0" "position": "0x0"
} }
], ],
@ -278,6 +279,7 @@
"0x0000000000000000000000005aae5c59d642e5fd45b427df6ed478b49d55fefd" "0x0000000000000000000000005aae5c59d642e5fd45b427df6ed478b49d55fefd"
], ],
"data": "0x00000000000000000000000000000000000000000000000080d29fa5cccfadac", "data": "0x00000000000000000000000000000000000000000000000080d29fa5cccfadac",
"index": "0x1",
"position": "0x0" "position": "0x0"
} }
], ],
@ -308,6 +310,7 @@
"0x0000000000000000000000005aae5c59d642e5fd45b427df6ed478b49d55fefd" "0x0000000000000000000000005aae5c59d642e5fd45b427df6ed478b49d55fefd"
], ],
"data": "0x00000000000000000000000000000000000000000000000080d29fa5cccfadac", "data": "0x00000000000000000000000000000000000000000000000080d29fa5cccfadac",
"index": "0x2",
"position": "0x0" "position": "0x0"
} }
], ],
@ -330,6 +333,7 @@
"0x000000000000000000000000950ca4a06c78934a148b7a3ff3ea8fc366f77a06" "0x000000000000000000000000950ca4a06c78934a148b7a3ff3ea8fc366f77a06"
], ],
"data": "0x0000000000000000000000000000000000000000000000000041f50e27d56848", "data": "0x0000000000000000000000000000000000000000000000000041f50e27d56848",
"index": "0x3",
"position": "0x0" "position": "0x0"
} }
], ],
@ -394,6 +398,7 @@
"0x0000000000000000000000003de712784baf97260455ae25fb74f574ec9c1add" "0x0000000000000000000000003de712784baf97260455ae25fb74f574ec9c1add"
], ],
"data": "0x000000000000000000000000000000000000000000000000de0b6b3a76400000", "data": "0x000000000000000000000000000000000000000000000000de0b6b3a76400000",
"index": "0x4",
"position": "0x0" "position": "0x0"
} }
], ],
@ -408,4 +413,4 @@
"value": "0x0", "value": "0x0",
"type": "CALL" "type": "CALL"
} }
} }

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