Merge branch 'master' into rpc-otel-cli

This commit is contained in:
jonny rhea 2026-02-13 17:07:37 -06:00
commit 20c6ad3aab
107 changed files with 10146 additions and 840 deletions

View file

@ -24,8 +24,8 @@ import (
"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto/keccak"
"github.com/ethereum/go-ethereum/event"
"golang.org/x/crypto/sha3"
)
// 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.
func TextAndHash(data []byte) ([]byte, string) {
msg := fmt.Sprintf("\x19Ethereum Signed Message:\n%d%s", len(data), data)
hasher := sha3.NewLegacyKeccak256()
hasher := keccak.NewLegacyKeccak256()
hasher.Write([]byte(msg))
return hasher.Sum(nil), msg
}

View file

@ -43,6 +43,14 @@ const refreshCycle = time.Second
// trashing.
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.
type Hub struct {
scheme string // Protocol scheme prefixing account and wallet URLs.
@ -82,6 +90,7 @@ func NewLedgerHub() (*Hub, error) {
0x0005, /* Ledger Nano S Plus */
0x0006, /* Ledger Nano FTS */
0x0007, /* Ledger Flex */
0x0008, /* Ledger Nano Gen5 */
0x0000, /* WebUSB Ledger Blue */
0x1000, /* WebUSB Ledger Nano S */
@ -89,7 +98,8 @@ func NewLedgerHub() (*Hub, error) {
0x5000, /* WebUSB Ledger Nano S Plus */
0x6000, /* WebUSB Ledger Nano FTS */
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.

View file

@ -30,6 +30,8 @@ import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"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/flags"
"github.com/ethereum/go-ethereum/params"
@ -53,7 +55,7 @@ var (
eraSizeFlag = &cli.IntFlag{
Name: "size",
Usage: "number of blocks per era",
Value: era.MaxEra1Size,
Value: era.MaxSize,
}
txsFlag = &cli.BoolFlag{
Name: "txs",
@ -131,7 +133,7 @@ func block(ctx *cli.Context) error {
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 {
epoch, err := strconv.ParseUint(ctx.Args().First(), 10, 64)
if err != nil {
@ -142,33 +144,34 @@ func info(ctx *cli.Context) error {
return err
}
defer e.Close()
acc, err := e.Accumulator()
if err != nil {
return fmt.Errorf("error reading accumulator: %w", err)
var (
accHex string
tdStr string
)
if acc, err := e.Accumulator(); err == nil {
accHex = acc.Hex()
}
td, err := e.InitialTD()
if err != nil {
return fmt.Errorf("error reading total difficulty: %w", err)
if td, err := e.InitialTD(); err == nil {
tdStr = td.String()
}
info := struct {
Accumulator common.Hash `json:"accumulator"`
TotalDifficulty *big.Int `json:"totalDifficulty"`
Accumulator string `json:"accumulator,omitempty"`
TotalDifficulty string `json:"totalDifficulty,omitempty"`
StartBlock uint64 `json:"startBlock"`
Count uint64 `json:"count"`
}{
acc, td, e.Start(), e.Count(),
accHex, tdStr, e.Start(), e.Count(),
}
b, _ := json.MarshalIndent(info, "", " ")
fmt.Println(string(b))
return nil
}
// open opens an era1 file at a certain epoch.
func open(ctx *cli.Context, epoch uint64) (*era.Era, error) {
var (
dir = ctx.String(dirFlag.Name)
network = ctx.String(networkFlag.Name)
)
// open opens an era file at a certain epoch.
func open(ctx *cli.Context, epoch uint64) (era.Era, error) {
dir := ctx.String(dirFlag.Name)
network := ctx.String(networkFlag.Name)
entries, err := era.ReadDir(dir, network)
if err != nil {
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)) {
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
@ -203,18 +227,58 @@ func verify(ctx *cli.Context) error {
return fmt.Errorf("error reading %s: %w", dir, err)
}
if len(entries) != len(roots) {
return errors.New("number of era1 files should match the number of accumulator hashes")
// Build the verification list respecting the rule:
// 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.
for i, want := range roots {
// Wrap in function so defers don't stack.
err := func() error {
name := entries[i]
e, err := era.Open(filepath.Join(dir, name))
path := verify[i]
name := filepath.Base(path)
e, err := openByPath(path)
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()
// 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.
func checkAccumulator(e *era.Era) error {
func checkAccumulator(e era.Era) error {
var (
err error
want common.Hash
@ -257,7 +321,7 @@ func checkAccumulator(e *era.Era) error {
if td, err = e.InitialTD(); err != nil {
return fmt.Errorf("error reading total difficulty: %w", err)
}
it, err := era.NewIterator(e)
it, err := e.Iterator()
if err != nil {
return fmt.Errorf("error making era iterator: %w", err)
}
@ -290,10 +354,14 @@ func checkAccumulator(e *era.Era) error {
if rr != block.ReceiptHash() {
return fmt.Errorf("receipt root in block %d mismatch: want %s, got %s", block.NumberU64(), block.ReceiptHash(), rr)
}
// Only include pre-merge blocks in accumulator calculation.
// Post-merge blocks have difficulty == 0.
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 {
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/types"
"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/log"
"github.com/ethereum/go-ethereum/params"
@ -40,7 +41,6 @@ import (
"github.com/ethereum/go-ethereum/trie"
"github.com/ethereum/go-ethereum/triedb"
"github.com/holiman/uint256"
"golang.org/x/crypto/sha3"
)
type Prestate struct {
@ -415,7 +415,7 @@ func MakePreState(db ethdb.Database, accounts types.GenesisAlloc, isBintrie bool
}
func rlpHash(x any) (h common.Hash) {
hw := sha3.NewLegacyKeccak256()
hw := keccak.NewLegacyKeccak256()
rlp.Encode(hw, x)
hw.Sum(h[:0])
return h

View file

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

View file

@ -20,6 +20,7 @@ import (
"encoding/json"
"errors"
"fmt"
"io"
"os"
"path/filepath"
"regexp"
@ -43,6 +44,8 @@ import (
"github.com/ethereum/go-ethereum/internal/debug"
"github.com/ethereum/go-ethereum/internal/era"
"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/log"
"github.com/ethereum/go-ethereum/node"
@ -153,7 +156,7 @@ be gzipped.`,
Name: "import-history",
Usage: "Import an Era archive",
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: `
The import-history command will import blocks and their corresponding receipts
from Era archives.
@ -164,7 +167,7 @@ from Era archives.
Name: "export-history",
Usage: "Export blockchain history to Era archives",
ArgsUsage: "<dir> <first> <last>",
Flags: utils.DatabaseFlags,
Flags: slices.Concat([]cli.Flag{utils.EraFormatFlag}, utils.DatabaseFlags),
Description: `
The export-history command will export blocks and their corresponding receipts
into Era archives. Eras are typically packaged in steps of 8192 blocks.
@ -516,15 +519,27 @@ func importHistory(ctx *cli.Context) error {
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
}
fmt.Printf("Import done in %v\n", time.Since(start))
return nil
}
// exportHistory exports chain history in Era archives at a specified
// directory.
// exportHistory exports chain history in Era archives at a specified directory.
func exportHistory(ctx *cli.Context) error {
if ctx.Args().Len() != 3 {
utils.Fatalf("usage: %s", ctx.Command.ArgsUsage)
@ -550,10 +565,26 @@ func exportHistory(ctx *cli.Context) error {
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())
}
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)
}
fmt.Printf("Export done in %v\n", time.Since(start))
return nil
}

View file

@ -405,9 +405,9 @@ func applyMetricConfig(ctx *cli.Context, cfg *gethConfig) {
ctx.IsSet(utils.MetricsInfluxDBBucketFlag.Name)
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 {
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

@ -57,6 +57,8 @@ const (
importBatchSize = 2500
)
type EraFileFormat int
// ErrImportInterrupted is returned when the user interrupts the import process.
var ErrImportInterrupted = errors.New("interrupted")
@ -250,7 +252,7 @@ func readList(filename string) ([]string, error) {
// ImportHistory imports Era1 files containing historical block information,
// starting from genesis. The assumption is held that the provided chain
// 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 {
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)
}
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 (
start = time.Now()
reported = time.Now()
imported = 0
h = sha256.New()
buf = bytes.NewBuffer(nil)
scratch = bytes.NewBuffer(nil)
)
for i, filename := range entries {
for i, file := range entries {
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 {
return fmt.Errorf("unable to open era: %w", err)
return fmt.Errorf("open %s: %w", path, err)
}
defer f.Close()
// Validate checksum.
if _, err := io.Copy(h, f); err != nil {
return fmt.Errorf("unable to recalculate checksum: %w", 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)
return fmt.Errorf("checksum %s: %w", path, err)
}
got := common.BytesToHash(h.Sum(scratch.Bytes()[:])).Hex()
want := checksums[i]
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.
e, err := era.From(f)
e, err := from(f)
if err != nil {
return fmt.Errorf("error opening era: %w", err)
}
it, err := era.NewIterator(e)
it, err := e.Iterator()
if err != nil {
return fmt.Errorf("error making era reader: %w", err)
return fmt.Errorf("error creating iterator: %w", err)
}
for it.Next() {
block, err := it.Block()
if err != nil {
@ -311,26 +320,28 @@ func ImportHistory(chain *core.BlockChain, dir string, network string) error {
if err != nil {
return fmt.Errorf("error reading receipts %d: %w", it.Number(), err)
}
encReceipts := types.EncodeBlockReceiptLists([]types.Receipts{receipts})
if _, err := chain.InsertReceiptChain([]*types.Block{block}, encReceipts, math.MaxUint64); err != nil {
enc := types.EncodeBlockReceiptLists([]types.Receipts{receipts})
if _, err := chain.InsertReceiptChain([]*types.Block{block}, enc, math.MaxUint64); err != nil {
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 {
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
reported = time.Now()
}
}
if err := it.Error(); err != nil {
return err
}
return nil
}()
if err != nil {
return err
}
}
return nil
}
@ -389,7 +400,6 @@ func ExportAppendChain(blockchain *core.BlockChain, fn string, first uint64, las
return err
}
defer fh.Close()
var writer io.Writer = fh
if strings.HasSuffix(fn, ".gz") {
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,
// 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)
if head := bc.CurrentBlock().Number.Uint64(); head < 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 {
return fmt.Errorf("error creating output directory: %w", err)
}
var (
start = time.Now()
reported = time.Now()
h = sha256.New()
buf = bytes.NewBuffer(nil)
td = new(big.Int)
checksums []string
)
td := new(big.Int)
for i := uint64(0); i < first; i++ {
td.Add(td, bc.GetHeaderByNumber(i).Difficulty)
// Compute initial TD by accumulating difficulty from genesis to first-1.
// 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 {
err := func() error {
filename := filepath.Join(dir, era.Filename(network, int(i/step), common.Hash{}))
f, err := os.Create(filename)
if first > 0 && b.Difficulty().Sign() != 0 {
log.Info("Computing initial total difficulty", "from", 0, "to", first-1)
for i := uint64(0); i < first; i++ {
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 {
return fmt.Errorf("could not create era file: %w", err)
return err
}
defer f.Close()
w := era.NewBuilder(f)
for j := uint64(0); j < step && j <= last-i; j++ {
var (
n = i + j
block = bc.GetBlockByNumber(n)
)
builder := newBuilder(f)
for j := uint64(0); j < uint64(era.MaxSize) && batch+j <= last; j++ {
n := batch + j
block := bc.GetBlockByNumber(n)
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())
if receipts == nil {
return fmt.Errorf("export failed on #%d: receipts not found", n)
receipt := bc.GetReceiptsByHash(block.Hash())
if receipt == nil {
return fmt.Errorf("receipts for #%d missing", n)
}
// 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())
if err := w.Add(block, receipts, new(big.Int).Set(td)); err != nil {
blockTD = new(big.Int).Set(td)
}
if err := builder.Add(block, receipt, blockTD); err != nil {
return err
}
}
root, err := w.Finalize()
id, err := builder.Finalize()
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 {
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()
buf.Reset()
return nil
}()
if err != nil {
if _, err := io.Copy(h, f); err != nil {
return err
}
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
}
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()
}
}
os.WriteFile(filepath.Join(dir, "checksums.txt"), []byte(strings.Join(checksums, "\n")), os.ModePerm)
log.Info("Exported blockchain to", "dir", dir)
_ = os.WriteFile(filepath.Join(dir, "checksums.txt"), []byte(strings.Join(checksums, "\n")), os.ModePerm)
return nil
}

View file

@ -1086,6 +1086,11 @@ Please note that --` + MetricsHTTPFlag.Name + ` must be set to start the server.
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 (

View file

@ -33,6 +33,8 @@ import (
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"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/trie"
"github.com/ethereum/go-ethereum/triedb"
@ -44,6 +46,16 @@ var (
)
func TestHistoryImportAndExport(t *testing.T) {
for _, tt := range []struct {
name string
builder func(io.Writer) era.Builder
filename func(network string, epoch int, root common.Hash) string
from func(f era.ReadAtSeekCloser) (era.Era, error)
}{
{"era1", onedb.NewBuilder, onedb.Filename, onedb.From},
{"erae", execdb.NewBuilder, execdb.Filename, execdb.From},
} {
t.Run(tt.name, func(t *testing.T) {
var (
key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
address = crypto.PubkeyToAddress(key.PublicKey)
@ -89,7 +101,7 @@ func TestHistoryImportAndExport(t *testing.T) {
dir := t.TempDir()
// Export history to temp directory.
if err := ExportHistory(chain, dir, 0, count, step); err != nil {
if err := ExportHistory(chain, dir, 0, count, tt.builder, tt.filename); err != nil {
t.Fatalf("error exporting history: %v", err)
}
@ -118,12 +130,12 @@ func TestHistoryImportAndExport(t *testing.T) {
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 := era.From(f)
e, err := tt.from(f)
if err != nil {
t.Fatalf("error opening era: %v", err)
}
defer e.Close()
it, err := era.NewIterator(e)
it, err := e.Iterator()
if err != nil {
t.Fatalf("error making era reader: %v", err)
}
@ -170,10 +182,12 @@ func TestHistoryImportAndExport(t *testing.T) {
if err != nil {
t.Fatalf("unable to initialize chain: %v", err)
}
if err := ImportHistory(imported, dir, "mainnet"); err != nil {
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"
"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.
@ -271,7 +271,7 @@ func (a *Address) checksumHex() []byte {
buf := a.hex()
// compute checksum
sha := sha3.NewLegacyKeccak256()
sha := keccak.NewLegacyKeccak256()
sha.Write(buf[2:])
hash := sha.Sum(nil)
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/vm"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/crypto/keccak"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/trie"
"golang.org/x/crypto/sha3"
)
const (
@ -642,7 +642,7 @@ func (c *Clique) Close() error {
// SealHash returns the hash of a block prior to it being sealed.
func SealHash(header *types.Header) (hash common.Hash) {
hasher := sha3.NewLegacyKeccak256()
hasher := keccak.NewLegacyKeccak256()
encodeSigHeader(hasher, header)
hasher.(crypto.KeccakState).Read(hash[:])
return hash

View file

@ -31,11 +31,11 @@ import (
"github.com/ethereum/go-ethereum/core/tracing"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/crypto/keccak"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/trie"
"github.com/holiman/uint256"
"golang.org/x/crypto/sha3"
)
// 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.
func (ethash *Ethash) SealHash(header *types.Header) (hash common.Hash) {
hasher := sha3.NewLegacyKeccak256()
hasher := keccak.NewLegacyKeccak256()
enc := []interface{}{
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))
}
func latestBlobConfig(cfg *params.ChainConfig, time uint64) *BlobConfig {
func latestBlobConfig(cfg *params.ChainConfig, time uint64) (BlobConfig, error) {
if cfg.BlobScheduleConfig == nil {
return nil
return BlobConfig{}, errors.New("no blob config")
}
var (
london = cfg.LondonBlock
@ -80,14 +80,14 @@ func latestBlobConfig(cfg *params.ChainConfig, time uint64) *BlobConfig {
case cfg.IsCancun(london, time) && s.Cancun != nil:
bc = s.Cancun
default:
return nil
return BlobConfig{}, errors.New("no blob config")
}
return &BlobConfig{
return BlobConfig{
Target: bc.Target,
Max: bc.Max,
UpdateFraction: bc.UpdateFraction,
}
}, nil
}
// 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")
}
bcfg := latestBlobConfig(config, header.Time)
if bcfg == nil {
bcfg, err := latestBlobConfig(config, header.Time)
if err != nil {
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.
func CalcExcessBlobGas(config *params.ChainConfig, parent *types.Header, headTimestamp uint64) uint64 {
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)
}
func calcExcessBlobGas(isOsaka bool, bcfg *BlobConfig, parent *types.Header) uint64 {
func calcExcessBlobGas(isOsaka bool, bcfg BlobConfig, parent *types.Header) uint64 {
var parentExcessBlobGas, parentBlobGasUsed uint64
if parent.ExcessBlobGas != nil {
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.
func CalcBlobFee(config *params.ChainConfig, header *types.Header) *big.Int {
blobConfig := latestBlobConfig(config, header.Time)
if blobConfig == nil {
blobConfig, err := latestBlobConfig(config, header.Time)
if err != nil {
panic("calculating blob fee on unsupported fork")
}
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.
func MaxBlobsPerBlock(cfg *params.ChainConfig, time uint64) int {
blobConfig := latestBlobConfig(cfg, time)
if blobConfig == nil {
blobConfig, err := latestBlobConfig(cfg, time)
if err != nil {
return 0
}
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
// configuration, regardless of the currently active fork.
func LatestMaxBlobsPerBlock(cfg *params.ChainConfig) int {
bcfg := latestBlobConfig(cfg, math.MaxUint64)
if bcfg == nil {
bcfg, err := latestBlobConfig(cfg, math.MaxUint64)
if err != nil {
return 0
}
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.
func TargetBlobsPerBlock(cfg *params.ChainConfig, time uint64) int {
blobConfig := latestBlobConfig(cfg, time)
if blobConfig == nil {
blobConfig, err := latestBlobConfig(cfg, time)
if err != nil {
return 0
}
return blobConfig.Target

View file

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

View file

@ -424,8 +424,14 @@ func WriteBodyRLP(db ethdb.KeyValueWriter, hash common.Hash, number uint64, rlp
// HasBody verifies the existence of a block body corresponding to the hash.
func HasBody(db ethdb.Reader, hash common.Hash, number uint64) bool {
if isCanon(db, number, hash) {
// 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 {
return false
}
@ -466,8 +472,14 @@ func DeleteBody(db ethdb.KeyValueWriter, hash common.Hash, number uint64) {
// to a block.
func HasReceipts(db ethdb.Reader, hash common.Hash, number uint64) bool {
if isCanon(db, number, hash) {
// 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 {
return false
}

View file

@ -28,9 +28,9 @@ import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/crypto/keccak"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rlp"
"golang.org/x/crypto/sha3"
)
// 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 {
t.Fatalf("Stored header RLP not found")
} else {
hasher := sha3.NewLegacyKeccak256()
hasher.Write(entry)
if hash := common.BytesToHash(hasher.Sum(nil)); hash != header.Hash() {
if hash := crypto.Keccak256Hash(entry); hash != header.Hash() {
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
body := &types.Body{Uncles: []*types.Header{{Extra: []byte("test header")}}}
hasher := sha3.NewLegacyKeccak256()
hasher := keccak.NewLegacyKeccak256()
rlp.Encode(hasher, body)
hash := common.BytesToHash(hasher.Sum(nil))
@ -90,10 +86,7 @@ func TestBodyStorage(t *testing.T) {
if entry := ReadBodyRLP(db, hash, 0); entry == nil {
t.Fatalf("Stored body RLP not found")
} else {
hasher := sha3.NewLegacyKeccak256()
hasher.Write(entry)
if calc := common.BytesToHash(hasher.Sum(nil)); calc != hash {
if calc := crypto.Keccak256Hash(entry); calc != hash {
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)
for iter.Next() {
if iter.Err() != nil {
return nil, 0, iter.Err()
}
// The preimage for the hash calculation of legacy transactions
// is just their RLP encoding. For typed (EIP-2718) transactions,
// 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++
}
if iter.Err() != nil {
return nil, 0, iter.Err()
}
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 {
return err
}
defer table.Close()
table.dumpIndexStdout(start, end)
return nil
}

View file

@ -357,9 +357,9 @@ func unindexTransactions(db ethdb.Database, from uint64, to uint64, interrupt ch
}
select {
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:
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

@ -27,6 +27,7 @@ import (
"github.com/ethereum/go-ethereum/common/lru"
"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/rlp"
)
@ -51,7 +52,7 @@ type Store struct {
type fileCacheEntry struct {
refcount int // reference count. This is protected by Store.mu!
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
}
@ -102,7 +103,7 @@ func (db *Store) Close() {
// GetRawBody returns the raw body for a given block number.
func (db *Store) GetRawBody(number uint64) ([]byte, error) {
epoch := number / uint64(era.MaxEra1Size)
epoch := number / uint64(era.MaxSize)
entry := db.getEraByEpoch(epoch)
if entry.err != nil {
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.
func (db *Store) GetRawReceipts(number uint64) ([]byte, error) {
epoch := number / uint64(era.MaxEra1Size)
epoch := number / uint64(era.MaxSize)
entry := db.getEraByEpoch(epoch)
if entry.err != nil {
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.
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()
defer db.mu.Unlock()
@ -282,7 +283,7 @@ func (db *Store) fileFailedToOpen(epoch uint64, entry *fileCacheEntry, err error
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>.
glob := fmt.Sprintf("*-%05d-*.era1", epoch)
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]
e, err := era.Open(filename)
e, err := onedb.Open(filename)
if err != nil {
return nil, err
}
// Sanity-check start block.
if e.Start()%uint64(era.MaxEra1Size) != 0 {
if e.Start()%uint64(era.MaxSize) != 0 {
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)
return e, nil
return e.(*onedb.Era), nil
}
// doneWithFile signals that the caller has finished using a file.

View file

@ -221,13 +221,12 @@ func cleanup(path string) error {
if err != nil {
return err
}
defer dir.Close()
names, err := dir.Readdirnames(0)
if err != nil {
return err
}
if cerr := dir.Close(); cerr != nil {
return cerr
}
for _, name := range names {
if name == filepath.Base(path)+tmpSuffix {
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/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/crypto/keccak"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rlp"
"golang.org/x/crypto/sha3"
)
func getBlock(transactions int, uncles int, dataSize int) *types.Block {
@ -147,7 +147,7 @@ func BenchmarkHashing(b *testing.B) {
blockRlp, _ = rlp.EncodeToBytes(block)
}
var got common.Hash
var hasher = sha3.NewLegacyKeccak256()
var hasher = keccak.NewLegacyKeccak256()
b.Run("iteratorhashing", func(b *testing.B) {
for b.Loop() {
var hash common.Hash

View file

@ -39,7 +39,8 @@ type journalEntry interface {
revert(*StateDB)
// 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() 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.
func (j *journal) append(entry journalEntry) {
j.entries = append(j.entries, entry)
if addr := entry.dirtied(); addr != nil {
j.dirties[*addr]++
if addr, dirty := entry.dirtied(); dirty {
j.dirties[addr]++
}
}
@ -113,9 +114,9 @@ func (j *journal) revert(statedb *StateDB, snapshot int) {
j.entries[i].revert(statedb)
// Drop any dirty tracking induced by the change
if addr := j.entries[i].dirtied(); addr != nil {
if j.dirties[*addr]--; j.dirties[*addr] == 0 {
delete(j.dirties, *addr)
if addr, dirty := j.entries[i].dirtied(); dirty {
if j.dirties[addr]--; j.dirties[addr] == 0 {
delete(j.dirties, addr)
}
}
}
@ -294,8 +295,8 @@ func (ch createObjectChange) revert(s *StateDB) {
delete(s.stateObjects, ch.account)
}
func (ch createObjectChange) dirtied() *common.Address {
return &ch.account
func (ch createObjectChange) dirtied() (common.Address, bool) {
return ch.account, true
}
func (ch createObjectChange) copy() journalEntry {
@ -308,8 +309,8 @@ func (ch createContractChange) revert(s *StateDB) {
s.getStateObject(ch.account).newContract = false
}
func (ch createContractChange) dirtied() *common.Address {
return nil
func (ch createContractChange) dirtied() (common.Address, bool) {
return common.Address{}, false
}
func (ch createContractChange) copy() journalEntry {
@ -325,8 +326,8 @@ func (ch selfDestructChange) revert(s *StateDB) {
}
}
func (ch selfDestructChange) dirtied() *common.Address {
return &ch.account
func (ch selfDestructChange) dirtied() (common.Address, bool) {
return ch.account, true
}
func (ch selfDestructChange) copy() journalEntry {
@ -340,8 +341,8 @@ var ripemd = common.HexToAddress("0000000000000000000000000000000000000003")
func (ch touchChange) revert(s *StateDB) {
}
func (ch touchChange) dirtied() *common.Address {
return &ch.account
func (ch touchChange) dirtied() (common.Address, bool) {
return ch.account, true
}
func (ch touchChange) copy() journalEntry {
@ -354,8 +355,8 @@ func (ch balanceChange) revert(s *StateDB) {
s.getStateObject(ch.account).setBalance(ch.prev)
}
func (ch balanceChange) dirtied() *common.Address {
return &ch.account
func (ch balanceChange) dirtied() (common.Address, bool) {
return ch.account, true
}
func (ch balanceChange) copy() journalEntry {
@ -369,8 +370,8 @@ func (ch nonceChange) revert(s *StateDB) {
s.getStateObject(ch.account).setNonce(ch.prev)
}
func (ch nonceChange) dirtied() *common.Address {
return &ch.account
func (ch nonceChange) dirtied() (common.Address, bool) {
return ch.account, true
}
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)
}
func (ch codeChange) dirtied() *common.Address {
return &ch.account
func (ch codeChange) dirtied() (common.Address, bool) {
return ch.account, true
}
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)
}
func (ch storageChange) dirtied() *common.Address {
return &ch.account
func (ch storageChange) dirtied() (common.Address, bool) {
return ch.account, true
}
func (ch storageChange) copy() journalEntry {
@ -416,8 +417,8 @@ func (ch transientStorageChange) revert(s *StateDB) {
s.setTransientState(ch.account, ch.key, ch.prevalue)
}
func (ch transientStorageChange) dirtied() *common.Address {
return nil
func (ch transientStorageChange) dirtied() (common.Address, bool) {
return common.Address{}, false
}
func (ch transientStorageChange) copy() journalEntry {
@ -432,8 +433,8 @@ func (ch refundChange) revert(s *StateDB) {
s.refund = ch.prev
}
func (ch refundChange) dirtied() *common.Address {
return nil
func (ch refundChange) dirtied() (common.Address, bool) {
return common.Address{}, false
}
func (ch refundChange) copy() journalEntry {
@ -452,8 +453,8 @@ func (ch addLogChange) revert(s *StateDB) {
s.logSize--
}
func (ch addLogChange) dirtied() *common.Address {
return nil
func (ch addLogChange) dirtied() (common.Address, bool) {
return common.Address{}, false
}
func (ch addLogChange) copy() journalEntry {
@ -475,8 +476,8 @@ func (ch accessListAddAccountChange) revert(s *StateDB) {
s.accessList.DeleteAddress(ch.address)
}
func (ch accessListAddAccountChange) dirtied() *common.Address {
return nil
func (ch accessListAddAccountChange) dirtied() (common.Address, bool) {
return common.Address{}, false
}
func (ch accessListAddAccountChange) copy() journalEntry {
@ -489,8 +490,8 @@ func (ch accessListAddSlotChange) revert(s *StateDB) {
s.accessList.DeleteSlot(ch.address, ch.slot)
}
func (ch accessListAddSlotChange) dirtied() *common.Address {
return nil
func (ch accessListAddSlotChange) dirtied() (common.Address, bool) {
return common.Address{}, false
}
func (ch accessListAddSlotChange) copy() journalEntry {

View file

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

View file

@ -50,7 +50,7 @@ func (s Storage) Copy() Storage {
type stateObject struct {
db *StateDB
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
data types.StateAccount // Account data with all mutations applied in the scope of block
@ -102,7 +102,6 @@ func newObject(db *StateDB, address common.Address, acct *types.StateAccount) *s
return &stateObject{
db: db,
address: address,
addrHash: crypto.Keccak256Hash(address[:]),
origin: origin,
data: *acct,
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() {
s.selfDestructed = true
}
@ -151,7 +158,7 @@ func (s *stateObject) getPrefetchedTrie() Trie {
return nil
}
// 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.
@ -203,7 +210,7 @@ func (s *stateObject) GetCommittedState(key common.Hash) common.Hash {
// Schedule the resolved storage slots for prefetching if it's enabled.
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)
}
}
@ -264,7 +271,7 @@ func (s *stateObject) finalise() {
s.pendingStorage[key] = value
}
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)
}
}
@ -359,7 +366,7 @@ func (s *stateObject) updateTrie() (Trie, error) {
s.db.StorageDeleted.Add(1)
}
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
return tr, nil
@ -491,7 +498,7 @@ func (s *stateObject) deepCopy(db *StateDB) *stateObject {
obj := &stateObject{
db: db,
address: s.address,
addrHash: s.addrHash,
addressHash: nil,
origin: s.origin,
data: s.data,
code: s.code,

View file

@ -867,13 +867,13 @@ func (s *StateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash {
witness := trie.Witness()
s.witness.AddState(witness)
if s.witnessStats != nil {
s.witnessStats.Add(witness, obj.addrHash)
s.witnessStats.Add(witness, obj.addrHash())
}
} else if obj.trie != nil {
witness := obj.trie.Witness()
s.witness.AddState(witness)
if s.witnessStats != nil {
s.witnessStats.Add(witness, obj.addrHash)
s.witnessStats.Add(witness, obj.addrHash())
}
}
}
@ -891,13 +891,13 @@ func (s *StateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash {
witness := trie.Witness()
s.witness.AddState(witness)
if s.witnessStats != nil {
s.witnessStats.Add(witness, obj.addrHash)
s.witnessStats.Add(witness, obj.addrHash())
}
} else if obj.trie != nil {
witness := obj.trie.Witness()
s.witness.AddState(witness)
if s.witnessStats != nil {
s.witnessStats.Add(witness, obj.addrHash)
s.witnessStats.Add(witness, obj.addrHash())
}
}
}
@ -1284,7 +1284,7 @@ func (s *StateDB) commit(deleteEmptyObjects bool, noStorageWiping bool, blockNum
return err
}
lock.Lock()
updates[obj.addrHash] = update
updates[obj.addrHash()] = update
s.StorageCommits = time.Since(start) // overwrite with the longest storage commit runtime
lock.Unlock()
return nil

View file

@ -31,10 +31,10 @@ import (
"github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/crypto/keccak"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/trie"
"github.com/holiman/uint256"
"golang.org/x/crypto/sha3"
)
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
// 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
hasher := sha3.NewLegacyKeccak256()
hasher := keccak.NewLegacyKeccak256()
hasher.Write(header.Number.Bytes())
var cumulativeGas uint64
var nBlobs int

View file

@ -2096,6 +2096,11 @@ func (p *BlobPool) Clear() {
p.index = make(map[common.Address][]*blobTxMeta)
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 (
basefee = uint256.MustFromBig(eip1559.CalcBaseFee(p.chain.Config(), p.head.Load()))
blobfee = uint256.NewInt(params.BlobTxMinBlobGasprice)

View file

@ -908,8 +908,8 @@ func (pool *LegacyPool) addRemoteSync(tx *types.Transaction) error {
func (pool *LegacyPool) Add(txs []*types.Transaction, sync bool) []error {
// Filter out known ones without obtaining the pool lock or recovering signatures
var (
hasValid bool
errs = make([]error, len(txs))
news = make([]*types.Transaction, 0, len(txs))
)
for i, tx := range txs {
// If the transaction is known, pre-set the error slot
@ -927,26 +927,17 @@ func (pool *LegacyPool) Add(txs []*types.Transaction, sync bool) []error {
invalidTxMeter.Mark(1)
continue
}
// Accumulate all unknown transactions for deeper processing
news = append(news, tx)
hasValid = true
}
if len(news) == 0 {
if !hasValid {
return errs
}
// Process all the new transaction and merge any errors into the original slice
pool.mu.Lock()
newErrs, dirtyAddrs := pool.addTxsLocked(news)
dirtyAddrs := pool.addTxsLocked(txs, errs)
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
done := pool.requestPromoteExecutables(dirtyAddrs)
if sync {
@ -957,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.
// The transaction pool lock must be held.
// Returns the error for each tx, and the set of accounts that might became promotable.
func (pool *LegacyPool) addTxsLocked(txs []*types.Transaction) ([]error, *accountSet) {
// Sets the error for each tx, and the set of accounts that might became promotable.
// 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 (
dirty = newAccountSet(pool.signer)
errs = make([]error, len(txs))
valid int64
)
for i, tx := range txs {
if errs[i] != nil {
continue
}
replaced, err := pool.add(tx)
errs[i] = err
if err == nil {
@ -975,7 +971,7 @@ func (pool *LegacyPool) addTxsLocked(txs []*types.Transaction) ([]error, *accoun
}
}
validTxMeter.Mark(valid)
return errs, dirty
return dirty
}
// Status returns the status (unknown/pending/queued) of a batch of transactions
@ -1397,7 +1393,7 @@ func (pool *LegacyPool) reset(oldHead, newHead *types.Header) {
// Inject any transactions discarded due to reorgs
log.Debug("Reinjecting stale transactions", "count", len(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

View file

@ -424,3 +424,33 @@ func EncodeBlockReceiptLists(receipts []Receipts) []rlp.RawValue {
}
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 {
r := make([]*Receipt, len(receipts))
for i, receipt := range receipts {

View file

@ -330,7 +330,7 @@ func NewEIP2930Signer(chainId *big.Int) Signer {
// are replay-protected as well as unprotected homestead transactions.
// Deprecated: always use the Signer interface type
type EIP155Signer struct {
chainId, chainIdMul *big.Int
chainId *big.Int
}
func NewEIP155Signer(chainId *big.Int) EIP155Signer {
@ -339,7 +339,6 @@ func NewEIP155Signer(chainId *big.Int) EIP155Signer {
}
return EIP155Signer{
chainId: chainId,
chainIdMul: new(big.Int).Mul(chainId, big.NewInt(2)),
}
}
@ -365,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)
}
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)
return recoverPlain(s.Hash(tx), R, S, V, true)
}
@ -382,7 +382,8 @@ func (s EIP155Signer) SignatureValues(tx *Transaction, sig []byte) (R, S, V *big
}
if s.chainId.Sign() != 0 {
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
}

View file

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

View file

@ -73,9 +73,8 @@ type BlockContext struct {
type TxContext struct {
// Message information
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
BlobFeeCap *big.Int // Is used to zero the blobbasefee if NoBaseFee is set
AccessEvents *state.AccessEvents // Capture all state accesses for this tx
}
@ -125,9 +124,6 @@ type EVM struct {
// jumpDests stores results of JUMPDEST analysis.
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
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,
chainRules: chainConfig.Rules(blockCtx.BlockNumber, blockCtx.Random != nil, blockCtx.Time),
jumpDests: newMapJumpDests(),
hasher: crypto.NewKeccakState(),
}
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:]
// 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) {
inithash := crypto.HashData(evm.hasher, code)
inithash := crypto.Keccak256Hash(code)
contractAddr = crypto.CreateAddress2(caller, salt.Bytes32(), inithash[:])
return evm.create(caller, code, gas, endowment, contractAddr, CREATE2)
}

View file

@ -22,6 +22,7 @@ import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/tracing"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/params"
"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()
data := scope.Memory.GetPtr(offset.Uint64(), size.Uint64())
evm.hasher.Reset()
evm.hasher.Write(data)
evm.hasher.Read(evm.hasherBuf[:])
hash := crypto.Keccak256Hash(data)
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
}
@ -417,8 +416,7 @@ func opExtCodeHash(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(v)
scope.Stack.push(evm.GasPrice.Clone())
return nil, nil
}

View file

@ -19,6 +19,7 @@ package vm
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"math/big"
"os"
@ -1015,7 +1016,8 @@ func TestEIP8024_Execution(t *testing.T) {
tests := []struct {
name string
codeHex string
wantErr bool
wantErr error
wantOpcode OpCode
wantVals []uint64
}{
{
@ -1071,53 +1073,68 @@ func TestEIP8024_Execution(t *testing.T) {
{
name: "INVALID_SWAPN_LOW",
codeHex: "e75b",
wantErr: true,
wantErr: &ErrInvalidOpCode{},
wantOpcode: SWAPN,
},
{
name: "JUMP over INVALID_DUPN",
codeHex: "600456e65b",
wantErr: false,
wantErr: nil,
},
{
name: "UNDERFLOW_DUPN_1",
codeHex: "6000808080808080808080808080808080e600",
wantErr: &ErrStackUnderflow{},
wantOpcode: DUPN,
},
// Additional test cases
{
name: "INVALID_DUPN_LOW",
codeHex: "e65b",
wantErr: true,
wantErr: &ErrInvalidOpCode{},
wantOpcode: DUPN,
},
{
name: "INVALID_EXCHANGE_LOW",
codeHex: "e850",
wantErr: true,
wantErr: &ErrInvalidOpCode{},
wantOpcode: EXCHANGE,
},
{
name: "INVALID_DUPN_HIGH",
codeHex: "e67f",
wantErr: true,
wantErr: &ErrInvalidOpCode{},
wantOpcode: DUPN,
},
{
name: "INVALID_SWAPN_HIGH",
codeHex: "e77f",
wantErr: true,
wantErr: &ErrInvalidOpCode{},
wantOpcode: SWAPN,
},
{
name: "INVALID_EXCHANGE_HIGH",
codeHex: "e87f",
wantErr: true,
wantErr: &ErrInvalidOpCode{},
wantOpcode: EXCHANGE,
},
{
name: "UNDERFLOW_DUPN",
name: "UNDERFLOW_DUPN_2",
codeHex: "5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5fe600", // (n=17, need 17 items, have 16)
wantErr: true,
wantErr: &ErrStackUnderflow{},
wantOpcode: DUPN,
},
{
name: "UNDERFLOW_SWAPN",
codeHex: "5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5fe700", // (n=17, need 18 items, have 17)
wantErr: true,
wantErr: &ErrStackUnderflow{},
wantOpcode: SWAPN,
},
{
name: "UNDERFLOW_EXCHANGE",
codeHex: "60016002e801", // (n,m)=(1,2), need 3 items, have 2
wantErr: true,
wantErr: &ErrStackUnderflow{},
wantOpcode: EXCHANGE,
},
{
name: "PC_INCREMENT",
@ -1133,6 +1150,7 @@ func TestEIP8024_Execution(t *testing.T) {
pc := uint64(0)
scope := &ScopeContext{Stack: stack, Contract: &Contract{Code: code}}
var err error
var errOp OpCode
for pc < uint64(len(code)) && err == nil {
op := code[pc]
switch OpCode(op) {
@ -1149,6 +1167,8 @@ func TestEIP8024_Execution(t *testing.T) {
_, err = opJumpdest(&pc, evm, scope)
case ISZERO:
_, err = opIszero(&pc, evm, scope)
case PUSH0:
_, err = opPush0(&pc, evm, scope)
case DUPN:
_, err = opDupN(&pc, evm, scope)
case SWAPN:
@ -1156,14 +1176,37 @@ func TestEIP8024_Execution(t *testing.T) {
case EXCHANGE:
_, err = opExchange(&pc, evm, scope)
default:
err = &ErrInvalidOpCode{opcode: OpCode(op)}
t.Fatalf("unexpected opcode %s at pc=%d", OpCode(op), pc)
}
if err != nil {
errOp = OpCode(op)
}
pc++
}
if tc.wantErr {
if tc.wantErr != nil {
// Fail because we wanted an error, but didn't get one.
if err == 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
}
if err != nil {

View file

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

View file

@ -22,17 +22,17 @@ import (
"sync"
"github.com/ethereum/go-ethereum/common"
"golang.org/x/crypto/sha3"
"github.com/ethereum/go-ethereum/crypto/keccak"
)
// NewKeccakState creates a new KeccakState
func NewKeccakState() KeccakState {
return sha3.NewLegacyKeccak256().(KeccakState)
return keccak.NewLegacyKeccak256().(KeccakState)
}
var hasherPool = sync.Pool{
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

@ -495,6 +495,9 @@ func (s *Ethereum) updateFilterMapsHeads() {
if head == nil || newHead.Hash() != head.Hash() {
head = newHead
chainView := s.newChainView(head)
if chainView == nil {
return
}
historyCutoff, _ := s.blockchain.HistoryPruningCutoff()
var finalBlock uint64
if fb := s.blockchain.CurrentFinalBlock(); fb != nil {

View file

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

@ -46,7 +46,7 @@ type vmContext struct {
}
func testCtx() *vmContext {
return &vmContext{blockCtx: vm.BlockContext{BlockNumber: big.NewInt(1), BaseFee: big.NewInt(0)}, txCtx: vm.TxContext{GasPrice: big.NewInt(100000)}}
return &vmContext{blockCtx: vm.BlockContext{BlockNumber: big.NewInt(1), BaseFee: big.NewInt(0)}, txCtx: vm.TxContext{GasPrice: uint256.NewInt(100000)}}
}
func runTrace(tracer *tracers.Tracer, vmctx *vmContext, chaincfg *params.ChainConfig, contractCode []byte) (json.RawMessage, error) {
@ -63,7 +63,7 @@ func runTrace(tracer *tracers.Tracer, vmctx *vmContext, chaincfg *params.ChainCo
contract.Code = contractCode
}
tracer.OnTxStart(evm.GetVMContext(), types.NewTx(&types.LegacyTx{Gas: gasLimit, GasPrice: vmctx.txCtx.GasPrice}), contract.Caller())
tracer.OnTxStart(evm.GetVMContext(), types.NewTx(&types.LegacyTx{Gas: gasLimit, GasPrice: vmctx.txCtx.GasPrice.ToBig()}), contract.Caller())
tracer.OnEnter(0, byte(vm.CALL), contract.Caller(), contract.Address(), []byte{}, startGas, value.ToBig())
ret, err := evm.Run(contract, []byte{}, false)
tracer.OnExit(0, ret, startGas-contract.Gas, err, true)
@ -186,7 +186,7 @@ func TestHaltBetweenSteps(t *testing.T) {
Contract: vm.NewContract(common.Address{}, common.Address{}, uint256.NewInt(0), 0, nil),
}
evm := vm.NewEVM(vm.BlockContext{BlockNumber: big.NewInt(1)}, &dummyStatedb{}, chainConfig, vm.Config{Tracer: tracer.Hooks})
evm.SetTxContext(vm.TxContext{GasPrice: big.NewInt(1)})
evm.SetTxContext(vm.TxContext{GasPrice: uint256.NewInt(1)})
tracer.OnTxStart(evm.GetVMContext(), types.NewTx(&types.LegacyTx{}), common.Address{})
tracer.OnEnter(0, byte(vm.CALL), common.Address{}, common.Address{}, []byte{}, 0, big.NewInt(0))
tracer.OnOpcode(0, 0, 0, 0, scope, nil, 0, nil)
@ -210,7 +210,7 @@ func TestNoStepExec(t *testing.T) {
t.Fatal(err)
}
evm := vm.NewEVM(vm.BlockContext{BlockNumber: big.NewInt(1)}, &dummyStatedb{}, chainConfig, vm.Config{Tracer: tracer.Hooks})
evm.SetTxContext(vm.TxContext{GasPrice: big.NewInt(100)})
evm.SetTxContext(vm.TxContext{GasPrice: uint256.NewInt(100)})
tracer.OnTxStart(evm.GetVMContext(), types.NewTx(&types.LegacyTx{}), common.Address{})
tracer.OnEnter(0, byte(vm.CALL), common.Address{}, common.Address{}, []byte{}, 1000, big.NewInt(0))
tracer.OnExit(0, nil, 0, nil, false)
@ -240,7 +240,7 @@ func TestIsPrecompile(t *testing.T) {
chaincfg.ByzantiumBlock = big.NewInt(100)
chaincfg.IstanbulBlock = big.NewInt(200)
chaincfg.BerlinBlock = big.NewInt(300)
txCtx := vm.TxContext{GasPrice: big.NewInt(100000)}
txCtx := vm.TxContext{GasPrice: uint256.NewInt(100000)}
tracer, err := newJsTracer("{addr: toAddress('0000000000000000000000000000000000000009'), res: null, step: function() { this.res = isPrecompiled(this.addr); }, fault: function() {}, result: function() { return this.res; }}", nil, nil, chaincfg)
if err != nil {
t.Fatal(err)

View file

@ -0,0 +1,104 @@
// Code generated by github.com/fjl/gencodec. DO NOT EDIT.
package gethclient
import (
"encoding/json"
"math/big"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
)
var _ = (*callFrameMarshaling)(nil)
// MarshalJSON marshals as JSON.
func (c CallFrame) MarshalJSON() ([]byte, error) {
type CallFrame0 struct {
Type string `json:"type"`
From common.Address `json:"from"`
Gas hexutil.Uint64 `json:"gas"`
GasUsed hexutil.Uint64 `json:"gasUsed"`
To *common.Address `json:"to,omitempty"`
Input hexutil.Bytes `json:"input"`
Output hexutil.Bytes `json:"output,omitempty"`
Error string `json:"error,omitempty"`
RevertReason string `json:"revertReason,omitempty"`
Calls []CallFrame `json:"calls,omitempty"`
Logs []CallLog `json:"logs,omitempty"`
Value *hexutil.Big `json:"value,omitempty"`
}
var enc CallFrame0
enc.Type = c.Type
enc.From = c.From
enc.Gas = hexutil.Uint64(c.Gas)
enc.GasUsed = hexutil.Uint64(c.GasUsed)
enc.To = c.To
enc.Input = c.Input
enc.Output = c.Output
enc.Error = c.Error
enc.RevertReason = c.RevertReason
enc.Calls = c.Calls
enc.Logs = c.Logs
enc.Value = (*hexutil.Big)(c.Value)
return json.Marshal(&enc)
}
// UnmarshalJSON unmarshals from JSON.
func (c *CallFrame) UnmarshalJSON(input []byte) error {
type CallFrame0 struct {
Type *string `json:"type"`
From *common.Address `json:"from"`
Gas *hexutil.Uint64 `json:"gas"`
GasUsed *hexutil.Uint64 `json:"gasUsed"`
To *common.Address `json:"to,omitempty"`
Input *hexutil.Bytes `json:"input"`
Output *hexutil.Bytes `json:"output,omitempty"`
Error *string `json:"error,omitempty"`
RevertReason *string `json:"revertReason,omitempty"`
Calls []CallFrame `json:"calls,omitempty"`
Logs []CallLog `json:"logs,omitempty"`
Value *hexutil.Big `json:"value,omitempty"`
}
var dec CallFrame0
if err := json.Unmarshal(input, &dec); err != nil {
return err
}
if dec.Type != nil {
c.Type = *dec.Type
}
if dec.From != nil {
c.From = *dec.From
}
if dec.Gas != nil {
c.Gas = uint64(*dec.Gas)
}
if dec.GasUsed != nil {
c.GasUsed = uint64(*dec.GasUsed)
}
if dec.To != nil {
c.To = dec.To
}
if dec.Input != nil {
c.Input = *dec.Input
}
if dec.Output != nil {
c.Output = *dec.Output
}
if dec.Error != nil {
c.Error = *dec.Error
}
if dec.RevertReason != nil {
c.RevertReason = *dec.RevertReason
}
if dec.Calls != nil {
c.Calls = dec.Calls
}
if dec.Logs != nil {
c.Logs = dec.Logs
}
if dec.Value != nil {
c.Value = (*big.Int)(dec.Value)
}
return nil
}

View file

@ -0,0 +1,61 @@
// Code generated by github.com/fjl/gencodec. DO NOT EDIT.
package gethclient
import (
"encoding/json"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
)
var _ = (*callLogMarshaling)(nil)
// MarshalJSON marshals as JSON.
func (c CallLog) MarshalJSON() ([]byte, error) {
type CallLog struct {
Address common.Address `json:"address"`
Topics []common.Hash `json:"topics"`
Data hexutil.Bytes `json:"data"`
Index hexutil.Uint `json:"index"`
Position hexutil.Uint `json:"position"`
}
var enc CallLog
enc.Address = c.Address
enc.Topics = c.Topics
enc.Data = c.Data
enc.Index = hexutil.Uint(c.Index)
enc.Position = hexutil.Uint(c.Position)
return json.Marshal(&enc)
}
// UnmarshalJSON unmarshals from JSON.
func (c *CallLog) UnmarshalJSON(input []byte) error {
type CallLog struct {
Address *common.Address `json:"address"`
Topics []common.Hash `json:"topics"`
Data *hexutil.Bytes `json:"data"`
Index *hexutil.Uint `json:"index"`
Position *hexutil.Uint `json:"position"`
}
var dec CallLog
if err := json.Unmarshal(input, &dec); err != nil {
return err
}
if dec.Address != nil {
c.Address = *dec.Address
}
if dec.Topics != nil {
c.Topics = dec.Topics
}
if dec.Data != nil {
c.Data = *dec.Data
}
if dec.Index != nil {
c.Index = uint(*dec.Index)
}
if dec.Position != nil {
c.Position = uint(*dec.Position)
}
return nil
}

View file

@ -19,10 +19,12 @@ package gethclient
import (
"context"
"encoding/json"
"fmt"
"math/big"
"runtime"
"runtime/debug"
"time"
"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/common"
@ -229,6 +231,124 @@ func (ec *Client) TraceBlock(ctx context.Context, hash common.Hash, config *trac
return result, nil
}
// CallTracerConfig configures the call tracer for
// TraceTransactionWithCallTracer and TraceCallWithCallTracer.
type CallTracerConfig struct {
// OnlyTopCall, when true, limits tracing to the main (top-level) call only.
OnlyTopCall bool
// WithLog, when true, includes log emissions in the trace output.
WithLog bool
// Timeout is the maximum duration the tracer may run.
// Zero means the server default (5s).
Timeout time.Duration
}
//go:generate go run github.com/fjl/gencodec -type CallLog -field-override callLogMarshaling -out gen_calllog_json.go
// CallLog represents a log emitted during a traced call.
type CallLog struct {
Address common.Address `json:"address"`
Topics []common.Hash `json:"topics"`
Data []byte `json:"data"`
Index uint `json:"index"`
Position uint `json:"position"`
}
type callLogMarshaling struct {
Data hexutil.Bytes
Index hexutil.Uint
Position hexutil.Uint
}
//go:generate go run github.com/fjl/gencodec -type CallFrame -field-override callFrameMarshaling -out gen_callframe_json.go
// CallFrame contains the result of a call tracer run.
type CallFrame struct {
Type string `json:"type"`
From common.Address `json:"from"`
Gas uint64 `json:"gas"`
GasUsed uint64 `json:"gasUsed"`
To *common.Address `json:"to,omitempty"`
Input []byte `json:"input"`
Output []byte `json:"output,omitempty"`
Error string `json:"error,omitempty"`
RevertReason string `json:"revertReason,omitempty"`
Calls []CallFrame `json:"calls,omitempty"`
Logs []CallLog `json:"logs,omitempty"`
Value *big.Int `json:"value,omitempty"`
}
type callFrameMarshaling struct {
Gas hexutil.Uint64
GasUsed hexutil.Uint64
Input hexutil.Bytes
Output hexutil.Bytes
Value *hexutil.Big
}
// TraceTransactionWithCallTracer traces a transaction with the call tracer
// and returns a typed CallFrame. If config is nil, defaults are used.
func (ec *Client) TraceTransactionWithCallTracer(ctx context.Context, txHash common.Hash, config *CallTracerConfig) (*CallFrame, error) {
var result CallFrame
err := ec.c.CallContext(ctx, &result, "debug_traceTransaction", txHash, callTracerConfig(config))
if err != nil {
return nil, err
}
return &result, nil
}
// TraceCallWithCallTracer executes a call with the call tracer and returns
// a typed CallFrame. blockNrOrHash selects the block context for the call.
// overrides specifies state overrides (nil for none), blockOverrides specifies
// block header overrides (nil for none), and config configures the tracer
// (nil for defaults).
func (ec *Client) TraceCallWithCallTracer(ctx context.Context, msg ethereum.CallMsg, blockNrOrHash rpc.BlockNumberOrHash, overrides map[common.Address]OverrideAccount, blockOverrides *BlockOverrides, config *CallTracerConfig) (*CallFrame, error) {
var result CallFrame
err := ec.c.CallContext(ctx, &result, "debug_traceCall", toCallArg(msg), blockNrOrHash, callTraceCallConfig(config, overrides, blockOverrides))
if err != nil {
return nil, err
}
return &result, nil
}
// callTracerConfig converts a CallTracerConfig to the wire-format TraceConfig.
func callTracerConfig(config *CallTracerConfig) *tracers.TraceConfig {
tracer := "callTracer"
tc := &tracers.TraceConfig{Tracer: &tracer}
if config != nil {
if config.OnlyTopCall || config.WithLog {
cfg, _ := json.Marshal(struct {
OnlyTopCall bool `json:"onlyTopCall"`
WithLog bool `json:"withLog"`
}{config.OnlyTopCall, config.WithLog})
tc.TracerConfig = cfg
}
if config.Timeout != 0 {
s := config.Timeout.String()
tc.Timeout = &s
}
}
return tc
}
// callTraceCallConfig builds the wire-format TraceCallConfig for debug_traceCall,
// bundling tracer settings with optional state and block overrides.
func callTraceCallConfig(config *CallTracerConfig, overrides map[common.Address]OverrideAccount, blockOverrides *BlockOverrides) interface{} {
tc := callTracerConfig(config)
// debug_traceCall expects a single config object that includes both
// tracer settings and any state/block overrides.
type traceCallConfig struct {
*tracers.TraceConfig
StateOverrides map[common.Address]OverrideAccount `json:"stateOverrides,omitempty"`
BlockOverrides *BlockOverrides `json:"blockOverrides,omitempty"`
}
return &traceCallConfig{
TraceConfig: tc,
StateOverrides: overrides,
BlockOverrides: blockOverrides,
}
}
func toBlockNumArg(number *big.Int) string {
if number == nil {
return "latest"

View file

@ -34,6 +34,7 @@ import (
"github.com/ethereum/go-ethereum/eth/ethconfig"
"github.com/ethereum/go-ethereum/eth/filters"
"github.com/ethereum/go-ethereum/eth/tracers"
_ "github.com/ethereum/go-ethereum/eth/tracers/native"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/ethereum/go-ethereum/node"
"github.com/ethereum/go-ethereum/params"
@ -161,6 +162,12 @@ func TestGethClient(t *testing.T) {
}, {
"TestCallContractWithBlockOverrides",
func(t *testing.T) { testCallContractWithBlockOverrides(t, client) },
}, {
"TestTraceTransactionWithCallTracer",
func(t *testing.T) { testTraceTransactionWithCallTracer(t, client, txHashes) },
}, {
"TestTraceCallWithCallTracer",
func(t *testing.T) { testTraceCallWithCallTracer(t, client) },
},
// The testaccesslist is a bit time-sensitive: the newTestBackend imports
// one block. The `testAccessList` fails if the miner has not yet created a
@ -620,3 +627,60 @@ func testCallContractWithBlockOverrides(t *testing.T, client *rpc.Client) {
t.Fatalf("unexpected result: %x", res)
}
}
func testTraceTransactionWithCallTracer(t *testing.T, client *rpc.Client, txHashes []common.Hash) {
ec := New(client)
for _, txHash := range txHashes {
// With nil config (defaults).
result, err := ec.TraceTransactionWithCallTracer(context.Background(), txHash, nil)
if err != nil {
t.Fatalf("nil config: %v", err)
}
if result.Type != "CALL" {
t.Fatalf("unexpected type: %s", result.Type)
}
if result.From == (common.Address{}) {
t.Fatal("from is zero")
}
if result.Gas == 0 {
t.Fatal("gas is zero")
}
// With explicit config.
result, err = ec.TraceTransactionWithCallTracer(context.Background(), txHash,
&CallTracerConfig{},
)
if err != nil {
t.Fatalf("explicit config: %v", err)
}
if result.Type != "CALL" {
t.Fatalf("unexpected type: %s", result.Type)
}
}
}
func testTraceCallWithCallTracer(t *testing.T, client *rpc.Client) {
ec := New(client)
msg := ethereum.CallMsg{
From: testAddr,
To: &common.Address{},
Gas: 21000,
GasPrice: big.NewInt(1000000000),
Value: big.NewInt(1),
}
result, err := ec.TraceCallWithCallTracer(context.Background(), msg,
rpc.BlockNumberOrHashWithNumber(rpc.LatestBlockNumber), nil, nil, nil,
)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if result.Type != "CALL" {
t.Fatalf("unexpected type: %s", result.Type)
}
if result.From == (common.Address{}) {
t.Fatal("from is zero")
}
if result.Gas == 0 {
t.Fatal("gas is zero")
}
}

View file

@ -307,7 +307,7 @@ func New(file string, cache int, handles int, namespace string, readonly bool) (
// These two settings define the conditions under which compaction concurrency
// is increased. Specifically, one additional compaction job will be enabled when:
// - there is one more overlapping sub-level0;
// - there is an additional 512 MB of compaction debt;
// - there is an additional 256 MB of compaction debt;
//
// The maximum concurrency is still capped by MaxConcurrentCompactions, but with
// these settings compactions can scale up more readily.

2
go.mod
View file

@ -44,6 +44,7 @@ require (
github.com/jackpal/go-nat-pmp v1.0.2
github.com/jedisct1/go-minisign v0.0.0-20230811132847-661be99b8267
github.com/karalabe/hid v1.0.1-0.20240306101548-573246063e52
github.com/klauspost/compress v1.17.8
github.com/kylelemons/godebug v1.1.0
github.com/mattn/go-colorable v0.1.13
github.com/mattn/go-isatty v0.0.20
@ -134,7 +135,6 @@ require (
github.com/influxdata/line-protocol v0.0.0-20200327222509-2487e7298839 // indirect
github.com/jmespath/go-jmespath v0.4.0 // indirect
github.com/kilic/bls12-381 v0.1.0 // indirect
github.com/klauspost/compress v1.17.8 // indirect
github.com/klauspost/cpuid/v2 v2.0.9 // indirect
github.com/kr/pretty v0.3.1 // indirect
github.com/kr/text v0.2.0 // indirect

View file

@ -27,7 +27,7 @@ import (
"hash"
"github.com/ethereum/go-ethereum/common"
"golang.org/x/crypto/sha3"
"github.com/ethereum/go-ethereum/crypto/keccak"
)
// testHasher is the helper tool for transaction/receipt list hashing.
@ -39,7 +39,7 @@ type testHasher struct {
// NewHasher returns a new testHasher instance.
func NewHasher() *testHasher {
return &testHasher{hasher: sha3.NewLegacyKeccak256()}
return &testHasher{hasher: keccak.NewLegacyKeccak256()}
}
// Reset resets the hash state.

View file

@ -20,6 +20,7 @@ import (
"errors"
"fmt"
"math/big"
"slices"
"github.com/ethereum/go-ethereum/common"
ssz "github.com/ferranbt/fastssz"
@ -31,8 +32,8 @@ func ComputeAccumulator(hashes []common.Hash, tds []*big.Int) (common.Hash, erro
if len(hashes) != len(tds) {
return common.Hash{}, errors.New("must have equal number hashes as td values")
}
if len(hashes) > MaxEra1Size {
return common.Hash{}, fmt.Errorf("too many records: have %d, max %d", len(hashes), MaxEra1Size)
if len(hashes) > MaxSize {
return common.Hash{}, fmt.Errorf("too many records: have %d, max %d", len(hashes), MaxSize)
}
hh := ssz.NewHasher()
for i := range hashes {
@ -43,7 +44,7 @@ func ComputeAccumulator(hashes []common.Hash, tds []*big.Int) (common.Hash, erro
}
hh.Append(root[:])
}
hh.MerkleizeWithMixin(0, uint64(len(hashes)), uint64(MaxEra1Size))
hh.MerkleizeWithMixin(0, uint64(len(hashes)), uint64(MaxSize))
return hh.HashRoot()
}
@ -69,23 +70,15 @@ func (h *headerRecord) HashTreeRoot() ([32]byte, error) {
// HashTreeRootWith ssz hashes the headerRecord object with a hasher.
func (h *headerRecord) HashTreeRootWith(hh ssz.HashWalker) (err error) {
hh.PutBytes(h.Hash[:])
td := bigToBytes32(h.TotalDifficulty)
td := BigToBytes32(h.TotalDifficulty)
hh.PutBytes(td[:])
hh.Merkleize(0)
return
}
// bigToBytes32 converts a big.Int into a little-endian 32-byte array.
func bigToBytes32(n *big.Int) (b [32]byte) {
func BigToBytes32(n *big.Int) (b [32]byte) {
n.FillBytes(b[:])
reverseOrder(b[:])
slices.Reverse(b[:])
return
}
// reverseOrder reverses the byte order of a slice.
func reverseOrder(b []byte) []byte {
for i := 0; i < 16; i++ {
b[i], b[32-i-1] = b[32-i-1], b[i]
}
return b
}

View file

@ -1,4 +1,4 @@
// Copyright 2024 The go-ethereum Authors
// 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
@ -17,7 +17,6 @@
package era
import (
"encoding/binary"
"fmt"
"io"
"math/big"
@ -25,15 +24,12 @@ import (
"path"
"strconv"
"strings"
"sync"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/internal/era/e2store"
"github.com/ethereum/go-ethereum/rlp"
"github.com/golang/snappy"
)
// Type constants for the e2store entries in the Era1 and EraE formats.
var (
TypeVersion uint16 = 0x3265
TypeCompressedHeader uint16 = 0x03
@ -41,277 +37,119 @@ var (
TypeCompressedReceipts uint16 = 0x05
TypeTotalDifficulty uint16 = 0x06
TypeAccumulator uint16 = 0x07
TypeCompressedSlimReceipts uint16 = 0x0a // uses eth/69 encoding
TypeProof uint16 = 0x0b
TypeBlockIndex uint16 = 0x3266
TypeComponentIndex uint16 = 0x3267
MaxEra1Size = 8192
MaxSize = 8192
// headerSize uint64 = 8
)
// Filename returns a recognizable Era1-formatted file name for the specified
// epoch and network.
func Filename(network string, epoch int, root common.Hash) string {
return fmt.Sprintf("%s-%05d-%s.era1", network, epoch, root.Hex()[2:10])
}
// ReadDir reads all the era1 files in a directory for a given network.
// Format: <network>-<epoch>-<hexroot>.era1
func ReadDir(dir, network string) ([]string, error) {
entries, err := os.ReadDir(dir)
if err != nil {
return nil, fmt.Errorf("error reading directory %s: %w", dir, err)
}
var (
next = uint64(0)
eras []string
)
for _, entry := range entries {
if path.Ext(entry.Name()) != ".era1" {
continue
}
parts := strings.Split(entry.Name(), "-")
if len(parts) != 3 || parts[0] != network {
// Invalid era1 filename, skip.
continue
}
if epoch, err := strconv.ParseUint(parts[1], 10, 64); err != nil {
return nil, fmt.Errorf("malformed era1 filename: %s", entry.Name())
} else if epoch != next {
return nil, fmt.Errorf("missing epoch %d", next)
}
next += 1
eras = append(eras, entry.Name())
}
return eras, nil
}
type ReadAtSeekCloser interface {
io.ReaderAt
io.Seeker
io.Closer
}
// Era reads and Era1 file.
type Era struct {
f ReadAtSeekCloser // backing era1 file
s *e2store.Reader // e2store reader over f
m metadata // start, count, length info
mu *sync.Mutex // lock for buf
buf [8]byte // buffer reading entry offsets
// Iterator provides sequential access to blocks in an era file.
type Iterator interface {
// Next advances to the next block. Returns true if a block is available,
// false when iteration is complete or an error occurred.
Next() bool
// Number returns the block number of the current block.
Number() uint64
// Block returns the current block.
Block() (*types.Block, error)
// BlockAndReceipts returns the current block and its receipts.
BlockAndReceipts() (*types.Block, types.Receipts, error)
// Receipts returns the receipts for the current block.
Receipts() (types.Receipts, error)
// Error returns any error encountered during iteration.
Error() error
}
// From returns an Era backed by f.
func From(f ReadAtSeekCloser) (*Era, error) {
m, err := readMetadata(f)
if err != nil {
return nil, err
}
return &Era{
f: f,
s: e2store.NewReader(f),
m: m,
mu: new(sync.Mutex),
}, nil
// Builder constructs era files from blocks and receipts.
//
// Builders handle three epoch types automatically:
// - Pre-merge: all blocks have difficulty > 0, TD is stored for each block
// - Transition: starts pre-merge, ends post-merge; TD stored for all blocks
// - Post-merge: all blocks have difficulty == 0, no TD stored
type Builder interface {
// Add appends a block and its receipts to the era file.
// For pre-merge blocks, td must be provided.
// For post-merge blocks, td should be nil.
Add(block *types.Block, receipts types.Receipts, td *big.Int) error
// AddRLP appends RLP-encoded block components to the era file.
// For pre-merge blocks, td and difficulty must be provided.
// For post-merge blocks, td and difficulty should be nil.
AddRLP(header, body, receipts []byte, number uint64, hash common.Hash, td, difficulty *big.Int) error
// Finalize writes all collected entries and returns the epoch identifier.
// For Era1 (onedb): returns the accumulator root.
// For EraE (execdb): returns the last block hash.
Finalize() (common.Hash, error)
// Accumulator returns the accumulator root after Finalize has been called.
// Returns nil for post-merge epochs where no accumulator exists.
Accumulator() *common.Hash
}
// Open returns an Era backed by the given filename.
func Open(filename string) (*Era, error) {
f, err := os.Open(filename)
if err != nil {
return nil, err
}
return From(f)
// Era represents the interface for reading era data.
type Era interface {
Close() error
Start() uint64
Count() uint64
Iterator() (Iterator, error)
GetBlockByNumber(num uint64) (*types.Block, error)
GetRawBodyByNumber(num uint64) ([]byte, error)
GetRawReceiptsByNumber(num uint64) ([]byte, error)
InitialTD() (*big.Int, error)
Accumulator() (common.Hash, error)
}
func (e *Era) Close() error {
return e.f.Close()
}
// ReadDir reads all the era files in a directory for a given network.
// Format: <network>-<epoch>-<hexroot>.erae or <network>-<epoch>-<hexroot>.era1
func ReadDir(dir, network string) ([]string, error) {
entries, err := os.ReadDir(dir)
// GetBlockByNumber returns the block for the given block number.
func (e *Era) GetBlockByNumber(num uint64) (*types.Block, error) {
if e.m.start > num || e.m.start+e.m.count <= num {
return nil, fmt.Errorf("out-of-bounds: %d not in [%d, %d)", num, e.m.start, e.m.start+e.m.count)
}
off, err := e.readOffset(num)
if err != nil {
return nil, err
return nil, fmt.Errorf("error reading directory %s: %w", dir, err)
}
r, n, err := newSnappyReader(e.s, TypeCompressedHeader, off)
if err != nil {
return nil, err
}
var header types.Header
if err := rlp.Decode(r, &header); err != nil {
return nil, err
}
off += n
r, _, err = newSnappyReader(e.s, TypeCompressedBody, off)
if err != nil {
return nil, err
}
var body types.Body
if err := rlp.Decode(r, &body); err != nil {
return nil, err
}
return types.NewBlockWithHeader(&header).WithBody(body), nil
}
// GetRawBodyByNumber returns the RLP-encoded body for the given block number.
func (e *Era) GetRawBodyByNumber(num uint64) ([]byte, error) {
if e.m.start > num || e.m.start+e.m.count <= num {
return nil, fmt.Errorf("out-of-bounds: %d not in [%d, %d)", num, e.m.start, e.m.start+e.m.count)
}
off, err := e.readOffset(num)
if err != nil {
return nil, err
}
off, err = e.s.SkipN(off, 1)
if err != nil {
return nil, err
}
r, _, err := newSnappyReader(e.s, TypeCompressedBody, off)
if err != nil {
return nil, err
}
return io.ReadAll(r)
}
// GetRawReceiptsByNumber returns the RLP-encoded receipts for the given block number.
func (e *Era) GetRawReceiptsByNumber(num uint64) ([]byte, error) {
if e.m.start > num || e.m.start+e.m.count <= num {
return nil, fmt.Errorf("out-of-bounds: %d not in [%d, %d)", num, e.m.start, e.m.start+e.m.count)
}
off, err := e.readOffset(num)
if err != nil {
return nil, err
}
// Skip over header and body.
off, err = e.s.SkipN(off, 2)
if err != nil {
return nil, err
}
r, _, err := newSnappyReader(e.s, TypeCompressedReceipts, off)
if err != nil {
return nil, err
}
return io.ReadAll(r)
}
// Accumulator reads the accumulator entry in the Era1 file.
func (e *Era) Accumulator() (common.Hash, error) {
entry, err := e.s.Find(TypeAccumulator)
if err != nil {
return common.Hash{}, err
}
return common.BytesToHash(entry.Value), nil
}
// InitialTD returns initial total difficulty before the difficulty of the
// first block of the Era1 is applied.
func (e *Era) InitialTD() (*big.Int, error) {
var (
r io.Reader
header types.Header
rawTd []byte
n int64
off int64
err error
next = uint64(0)
eras []string
dirType string
)
// Read first header.
if off, err = e.readOffset(e.m.start); err != nil {
return nil, err
for _, entry := range entries {
ext := path.Ext(entry.Name())
if ext != ".erae" && ext != ".era1" {
continue
}
if r, n, err = newSnappyReader(e.s, TypeCompressedHeader, off); err != nil {
return nil, err
if dirType == "" {
dirType = ext
}
if err := rlp.Decode(r, &header); err != nil {
return nil, err
parts := strings.Split(entry.Name(), "-")
if len(parts) != 3 || parts[0] != network {
// Invalid era filename, skip.
continue
}
off += n
// Skip over header and body.
off, err = e.s.SkipN(off, 2)
if err != nil {
return nil, err
if epoch, err := strconv.ParseUint(parts[1], 10, 64); err != nil {
return nil, fmt.Errorf("malformed era filenames: %s", entry.Name())
} else if epoch != next {
return nil, fmt.Errorf("missing epoch %d", next)
}
// Read total difficulty after first block.
if r, _, err = e.s.ReaderAt(TypeTotalDifficulty, off); err != nil {
return nil, err
if dirType != ext {
return nil, fmt.Errorf("directory %s contains mixed era file formats: want %s, have %s", dir, dirType, ext)
}
rawTd, err = io.ReadAll(r)
if err != nil {
return nil, err
next += 1
eras = append(eras, entry.Name())
}
td := new(big.Int).SetBytes(reverseOrder(rawTd))
return td.Sub(td, header.Difficulty), nil
}
// Start returns the listed start block.
func (e *Era) Start() uint64 {
return e.m.start
}
// Count returns the total number of blocks in the Era1.
func (e *Era) Count() uint64 {
return e.m.count
}
// readOffset reads a specific block's offset from the block index. The value n
// is the absolute block number desired.
func (e *Era) readOffset(n uint64) (int64, error) {
var (
blockIndexRecordOffset = e.m.length - 24 - int64(e.m.count)*8 // skips start, count, and header
firstIndex = blockIndexRecordOffset + 16 // first index after header / start-num
indexOffset = int64(n-e.m.start) * 8 // desired index * size of indexes
offOffset = firstIndex + indexOffset // offset of block offset
)
e.mu.Lock()
defer e.mu.Unlock()
clear(e.buf[:])
if _, err := e.f.ReadAt(e.buf[:], offOffset); err != nil {
return 0, err
}
// Since the block offset is relative from the start of the block index record
// we need to add the record offset to it's offset to get the block's absolute
// offset.
return blockIndexRecordOffset + int64(binary.LittleEndian.Uint64(e.buf[:])), nil
}
// newSnappyReader returns a snappy.Reader for the e2store entry value at off.
func newSnappyReader(e *e2store.Reader, expectedType uint16, off int64) (io.Reader, int64, error) {
r, n, err := e.ReaderAt(expectedType, off)
if err != nil {
return nil, 0, err
}
return snappy.NewReader(r), int64(n), err
}
// metadata wraps the metadata in the block index.
type metadata struct {
start uint64
count uint64
length int64
}
// readMetadata reads the metadata stored in an Era1 file's block index.
func readMetadata(f ReadAtSeekCloser) (m metadata, err error) {
// Determine length of reader.
if m.length, err = f.Seek(0, io.SeekEnd); err != nil {
return
}
b := make([]byte, 16)
// Read count. It's the last 8 bytes of the file.
if _, err = f.ReadAt(b[:8], m.length-8); err != nil {
return
}
m.count = binary.LittleEndian.Uint64(b)
// Read start. It's at the offset -sizeof(m.count) -
// count*sizeof(indexEntry) - sizeof(m.start)
if _, err = f.ReadAt(b[8:], m.length-16-int64(m.count*8)); err != nil {
return
}
m.start = binary.LittleEndian.Uint64(b[8:])
return
return eras, nil
}

View file

@ -86,8 +86,8 @@ func (l *Loader) DownloadAll(destDir string) error {
// DownloadBlockRange fetches the era1 files for the given block range.
func (l *Loader) DownloadBlockRange(start, end uint64, destDir string) error {
startEpoch := start / uint64(era.MaxEra1Size)
endEpoch := end / uint64(era.MaxEra1Size)
startEpoch := start / uint64(era.MaxSize)
endEpoch := end / uint64(era.MaxSize)
return l.DownloadEpochRange(startEpoch, endEpoch, destDir)
}

View file

@ -0,0 +1,332 @@
// 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 execdb
// EraE file format specification.
//
// The format can be summarized with the following expression:
//
// eraE := Version | CompressedHeader* | CompressedBody* | CompressedSlimReceipts* | TotalDifficulty* | other-entries* | Accumulator? | ComponentIndex
//
// Each basic element is its own e2store entry:
//
// Version = { type: 0x3265, data: nil }
// CompressedHeader = { type: 0x03, data: snappyFramed(rlp(header)) }
// CompressedBody = { type: 0x04, data: snappyFramed(rlp(body)) }
// CompressedSlimReceipts = { type: 0x0a, data: snappyFramed(rlp([tx-type, post-state-or-status, cumulative-gas, logs])) }
// TotalDifficulty = { type: 0x06, data: uint256 (header.total_difficulty) }
// AccumulatorRoot = { type: 0x07, data: hash_tree_root(List(HeaderRecord, 8192)) }
// ComponentIndex = { type: 0x3267, data: component-index }
//
// Notes:
// - TotalDifficulty is present for pre-merge and merge transition epochs.
// For pure post-merge epochs, TotalDifficulty is omitted entirely.
// - In merge transition epochs, post-merge blocks store the final total
// difficulty (the TD at which the merge occurred).
// - AccumulatorRoot is only written for pre-merge epochs.
// - HeaderRecord is defined in the Portal Network specification.
// - Proofs (type 0x09) are defined in the spec but not yet supported in this implementation.
//
// ComponentIndex stores relative offsets to each block's components:
//
// component-index := starting-number | indexes | indexes | ... | component-count | count
// indexes := header-offset | body-offset | receipts-offset | td-offset?
//
// All values are little-endian uint64.
//
// Due to the accumulator size limit of 8192, the maximum number of blocks in an
// EraE file is also 8192.
import (
"bytes"
"encoding/binary"
"errors"
"fmt"
"io"
"math/big"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/internal/era"
"github.com/ethereum/go-ethereum/internal/era/e2store"
"github.com/ethereum/go-ethereum/rlp"
"github.com/golang/snappy"
)
// Builder is used to build an EraE e2store file. It collects block entries and
// writes them to the underlying e2store.Writer.
type Builder struct {
w *e2store.Writer
headers [][]byte
hashes []common.Hash // only pre-merge block hashes, for accumulator
bodies [][]byte
receipts [][]byte
tds []*big.Int
startNum *uint64
ttd *big.Int // terminal total difficulty
last common.Hash // hash of last block added
accumulator *common.Hash // accumulator root, set by Finalize (nil for post-merge)
written uint64
buf *bytes.Buffer
snappy *snappy.Writer
}
// NewBuilder returns a new Builder instance.
func NewBuilder(w io.Writer) era.Builder {
return &Builder{
w: e2store.NewWriter(w),
}
}
// Add writes a block entry and its receipts into the e2store file.
func (b *Builder) Add(block *types.Block, receipts types.Receipts, td *big.Int) error {
eh, err := rlp.EncodeToBytes(block.Header())
if err != nil {
return fmt.Errorf("encode header: %w", err)
}
eb, err := rlp.EncodeToBytes(block.Body())
if err != nil {
return fmt.Errorf("encode body: %w", err)
}
rs := make([]*types.SlimReceipt, len(receipts))
for i, receipt := range receipts {
rs[i] = (*types.SlimReceipt)(receipt)
}
er, err := rlp.EncodeToBytes(rs)
if err != nil {
return fmt.Errorf("encode receipts: %w", err)
}
return b.AddRLP(eh, eb, er, block.Number().Uint64(), block.Hash(), td, block.Difficulty())
}
// AddRLP takes the RLP encoded block components and writes them to the underlying e2store file.
// The builder automatically handles transition epochs where both pre and post-merge blocks exist.
func (b *Builder) AddRLP(header, body, receipts []byte, number uint64, blockHash common.Hash, td, difficulty *big.Int) error {
if len(b.headers) >= era.MaxSize {
return fmt.Errorf("exceeds max size %d", era.MaxSize)
}
// Set starting block number on first add.
if b.startNum == nil {
b.startNum = new(uint64)
*b.startNum = number
}
if difficulty == nil {
return fmt.Errorf("invalid block: difficulty is nil")
}
hasDifficulty := difficulty.Sign() > 0
// Expect td to be nil for post-merge blocks
// and non-nil for pre-merge blocks.
if hasDifficulty != (td != nil) {
return fmt.Errorf("TD and difficulty mismatch: expected both nil or both non-nil")
}
// After the merge, difficulty must be nil.
post := (b.tds == nil && len(b.headers) > 0) || b.ttd != nil
if post && hasDifficulty {
return fmt.Errorf("post-merge epoch: cannot accept total difficulty for block %d", number)
}
// If this marks the start of the transition, record final total
// difficulty value.
if b.ttd == nil && len(b.tds) > 0 && !hasDifficulty {
b.ttd = new(big.Int).Set(b.tds[len(b.tds)-1])
}
// Record block data.
b.headers = append(b.headers, header)
b.bodies = append(b.bodies, body)
b.receipts = append(b.receipts, receipts)
b.last = blockHash
// Conditionally write the total difficulty and block hashes.
// - Pre-merge: store total difficulty and block hashes.
// - Transition: only store total difficulty.
// - Post-merge: store neither.
if hasDifficulty {
b.hashes = append(b.hashes, blockHash)
b.tds = append(b.tds, new(big.Int).Set(td))
} else if b.ttd != nil {
b.tds = append(b.tds, new(big.Int).Set(b.ttd))
} else {
// Post-merge: no TD or block hashes stored.
}
return nil
}
// Accumulator returns the accumulator root after Finalize has been called.
// Returns nil for post-merge epochs where no accumulator exists.
func (b *Builder) Accumulator() *common.Hash {
return b.accumulator
}
type offsets struct {
headers []uint64
bodies []uint64
receipts []uint64
tds []uint64
}
// Finalize writes all collected block entries to the e2store file.
// For pre-merge or transition epochs, the accumulator root is computed over
// pre-merge blocks and written. For pure post-merge epochs, no accumulator
// is written. Always returns the last block hash as the epoch identifier.
func (b *Builder) Finalize() (common.Hash, error) {
if b.startNum == nil {
return common.Hash{}, errors.New("no blocks added, cannot finalize")
}
// Write version before writing any blocks.
if n, err := b.w.Write(era.TypeVersion, nil); err != nil {
return common.Hash{}, fmt.Errorf("write version entry: %w", err)
} else {
b.written += uint64(n)
}
// Convert TD values to byte-level LE representation.
var tds [][]byte
for _, td := range b.tds {
tds = append(tds, uint256LE(td))
}
// Create snappy writer.
b.buf = bytes.NewBuffer(nil)
b.snappy = snappy.NewBufferedWriter(b.buf)
var o offsets
for _, section := range []struct {
typ uint16
data [][]byte
compressed bool
offsets *[]uint64
}{
{era.TypeCompressedHeader, b.headers, true, &o.headers},
{era.TypeCompressedBody, b.bodies, true, &o.bodies},
{era.TypeCompressedSlimReceipts, b.receipts, true, &o.receipts},
{era.TypeTotalDifficulty, tds, false, &o.tds},
} {
for _, data := range section.data {
*section.offsets = append(*section.offsets, b.written)
if section.compressed {
// Write snappy compressed data.
if err := b.snappyWrite(section.typ, data); err != nil {
return common.Hash{}, err
}
} else {
// Directly write uncompressed data.
n, err := b.w.Write(section.typ, data)
if err != nil {
return common.Hash{}, err
}
b.written += uint64(n)
}
}
}
// Compute and write accumulator root only for epochs that started pre-merge.
// The accumulator is computed over only the pre-merge blocks (b.hashes).
// Pure post-merge epochs have no accumulator.
if len(b.tds) > 0 {
accRoot, err := era.ComputeAccumulator(b.hashes, b.tds[:len(b.hashes)])
if err != nil {
return common.Hash{}, fmt.Errorf("compute accumulator: %w", err)
}
if n, err := b.w.Write(era.TypeAccumulator, accRoot[:]); err != nil {
return common.Hash{}, fmt.Errorf("write accumulator: %w", err)
} else {
b.written += uint64(n)
}
b.accumulator = &accRoot
if err := b.writeIndex(&o); err != nil {
return common.Hash{}, err
}
return b.last, nil
}
// Pure post-merge epoch: no accumulator.
if err := b.writeIndex(&o); err != nil {
return common.Hash{}, err
}
return b.last, nil
}
// uin256LE writes 32 byte big integers to little endian.
func uint256LE(v *big.Int) []byte {
b := v.FillBytes(make([]byte, 32))
for i := 0; i < 16; i++ {
b[i], b[31-i] = b[31-i], b[i]
}
return b
}
// SnappyWrite compresses the input data using snappy and writes it to the e2store file.
func (b *Builder) snappyWrite(typ uint16, in []byte) error {
b.buf.Reset()
b.snappy.Reset(b.buf)
if _, err := b.snappy.Write(in); err != nil {
return fmt.Errorf("error snappy encoding: %w", err)
}
if err := b.snappy.Flush(); err != nil {
return fmt.Errorf("error flushing snappy encoding: %w", err)
}
n, err := b.w.Write(typ, b.buf.Bytes())
b.written += uint64(n)
if err != nil {
return fmt.Errorf("error writing e2store entry: %w", err)
}
return nil
}
// writeIndex writes the component index to the file.
func (b *Builder) writeIndex(o *offsets) error {
count := len(o.headers)
// Post-merge, we only index headers, bodies, and receipts. Pre-merge, we also
// need to index the total difficulties.
componentCount := 3
if len(o.tds) > 0 {
componentCount++
}
// Offsets are stored relative to the index position (negative, stored as uint64).
base := int64(b.written)
rel := func(abs uint64) uint64 { return uint64(int64(abs) - base) }
var buf bytes.Buffer
write := func(v uint64) { binary.Write(&buf, binary.LittleEndian, v) }
write(*b.startNum)
for i := range o.headers {
write(rel(o.headers[i]))
write(rel(o.bodies[i]))
write(rel(o.receipts[i]))
if len(o.tds) > 0 {
write(rel(o.tds[i]))
}
}
write(uint64(componentCount))
write(uint64(count))
n, err := b.w.Write(era.TypeComponentIndex, buf.Bytes())
b.written += uint64(n)
return err
}

View file

@ -0,0 +1,348 @@
// 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 execdb
import (
"bytes"
"fmt"
"io"
"math/big"
"os"
"slices"
"testing"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/rlp"
)
func TestEraE(t *testing.T) {
t.Parallel()
tests := []struct {
name string
start uint64
preMerge int
postMerge int
accumulator bool // whether accumulator should exist
}{
{
name: "pre-merge",
start: 0,
preMerge: 128,
postMerge: 0,
accumulator: true,
},
{
name: "post-merge",
start: 0,
preMerge: 0,
postMerge: 64,
accumulator: false,
},
{
name: "transition",
start: 0,
preMerge: 32,
postMerge: 32,
accumulator: true,
},
{
name: "non-zero-start",
start: 8192,
preMerge: 64,
postMerge: 0,
accumulator: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
f, err := os.CreateTemp(t.TempDir(), "erae-test")
if err != nil {
t.Fatalf("error creating temp file: %v", err)
}
defer f.Close()
// Build test data.
type blockData struct {
header, body, receipts []byte
hash common.Hash
td *big.Int
difficulty *big.Int
}
var (
builder = NewBuilder(f)
blocks []blockData
totalBlocks = tt.preMerge + tt.postMerge
finalTD = big.NewInt(int64(tt.preMerge))
)
// Add pre-merge blocks.
for i := 0; i < tt.preMerge; i++ {
num := tt.start + uint64(i)
blk := blockData{
header: mustEncode(&types.Header{Number: big.NewInt(int64(num)), Difficulty: big.NewInt(1)}),
body: mustEncode(&types.Body{Transactions: []*types.Transaction{types.NewTransaction(0, common.Address{byte(i)}, nil, 0, nil, nil)}}),
receipts: mustEncode([]types.SlimReceipt{{CumulativeGasUsed: uint64(i)}}),
hash: common.Hash{byte(i)},
td: big.NewInt(int64(i + 1)),
difficulty: big.NewInt(1),
}
blocks = append(blocks, blk)
if err := builder.AddRLP(blk.header, blk.body, blk.receipts, num, blk.hash, blk.td, blk.difficulty); err != nil {
t.Fatalf("error adding pre-merge block %d: %v", i, err)
}
}
// Add post-merge blocks.
for i := 0; i < tt.postMerge; i++ {
idx := tt.preMerge + i
num := tt.start + uint64(idx)
blk := blockData{
header: mustEncode(&types.Header{Number: big.NewInt(int64(num)), Difficulty: big.NewInt(0)}),
body: mustEncode(&types.Body{}),
receipts: mustEncode([]types.SlimReceipt{}),
hash: common.Hash{byte(idx)},
difficulty: big.NewInt(0),
}
blocks = append(blocks, blk)
if err := builder.AddRLP(blk.header, blk.body, blk.receipts, num, blk.hash, nil, big.NewInt(0)); err != nil {
t.Fatalf("error adding post-merge block %d: %v", idx, err)
}
}
// Finalize and check return values.
epochID, err := builder.Finalize()
if err != nil {
t.Fatalf("error finalizing: %v", err)
}
// Verify epoch ID is always the last block hash.
expectedLastHash := blocks[len(blocks)-1].hash
if epochID != expectedLastHash {
t.Fatalf("wrong epoch ID: want %s, got %s", expectedLastHash.Hex(), epochID.Hex())
}
// Verify accumulator presence.
if tt.accumulator {
if builder.Accumulator() == nil {
t.Fatal("expected non-nil accumulator")
}
} else {
if builder.Accumulator() != nil {
t.Fatalf("expected nil accumulator, got %s", builder.Accumulator().Hex())
}
}
// Open and verify the era file.
e, err := Open(f.Name())
if err != nil {
t.Fatalf("failed to open era: %v", err)
}
defer e.Close()
// Verify metadata.
if e.Start() != tt.start {
t.Fatalf("wrong start block: want %d, got %d", tt.start, e.Start())
}
if e.Count() != uint64(totalBlocks) {
t.Fatalf("wrong block count: want %d, got %d", totalBlocks, e.Count())
}
// Verify accumulator in file.
if tt.accumulator {
accRoot, err := e.Accumulator()
if err != nil {
t.Fatalf("error getting accumulator: %v", err)
}
if accRoot != *builder.Accumulator() {
t.Fatalf("accumulator mismatch: builder has %s, file contains %s",
builder.Accumulator().Hex(), accRoot.Hex())
}
} else {
if _, err := e.Accumulator(); err == nil {
t.Fatal("expected error when reading accumulator from post-merge epoch")
}
}
// Verify blocks via raw iterator.
it, err := NewRawIterator(e)
if err != nil {
t.Fatalf("failed to make iterator: %v", err)
}
for i := 0; i < totalBlocks; i++ {
if !it.Next() {
t.Fatalf("expected more entries at %d", i)
}
if it.Error() != nil {
t.Fatalf("unexpected error: %v", it.Error())
}
// Check header.
rawHeader, err := io.ReadAll(it.Header)
if err != nil {
t.Fatalf("error reading header: %v", err)
}
if !bytes.Equal(rawHeader, blocks[i].header) {
t.Fatalf("mismatched header at %d", i)
}
// Check body.
rawBody, err := io.ReadAll(it.Body)
if err != nil {
t.Fatalf("error reading body: %v", err)
}
if !bytes.Equal(rawBody, blocks[i].body) {
t.Fatalf("mismatched body at %d", i)
}
// Check receipts.
rawReceipts, err := io.ReadAll(it.Receipts)
if err != nil {
t.Fatalf("error reading receipts: %v", err)
}
if !bytes.Equal(rawReceipts, blocks[i].receipts) {
t.Fatalf("mismatched receipts at %d", i)
}
// Check TD (only for epochs that have TD stored).
if tt.preMerge > 0 && it.TotalDifficulty != nil {
rawTd, err := io.ReadAll(it.TotalDifficulty)
if err != nil {
t.Fatalf("error reading TD: %v", err)
}
slices.Reverse(rawTd)
td := new(big.Int).SetBytes(rawTd)
var expectedTD *big.Int
if i < tt.preMerge {
expectedTD = blocks[i].td
} else {
// Post-merge blocks in transition epoch use final TD.
expectedTD = finalTD
}
if td.Cmp(expectedTD) != 0 {
t.Fatalf("mismatched TD at %d: want %s, got %s", i, expectedTD, td)
}
}
}
// Verify random access.
for _, blockNum := range []uint64{tt.start, tt.start + uint64(totalBlocks) - 1} {
blk, err := e.GetBlockByNumber(blockNum)
if err != nil {
t.Fatalf("error getting block %d: %v", blockNum, err)
}
if blk.Number().Uint64() != blockNum {
t.Fatalf("wrong block number: want %d, got %d", blockNum, blk.Number().Uint64())
}
}
// Verify out-of-range access fails.
if _, err := e.GetBlockByNumber(tt.start + uint64(totalBlocks)); err == nil {
t.Fatal("expected error for out-of-range block")
}
if tt.start > 0 {
if _, err := e.GetBlockByNumber(tt.start - 1); err == nil {
t.Fatal("expected error for block before start")
}
}
// Verify high-level iterator.
hlIt, err := e.Iterator()
if err != nil {
t.Fatalf("failed to create iterator: %v", err)
}
count := 0
for hlIt.Next() {
blk, err := hlIt.Block()
if err != nil {
t.Fatalf("error getting block: %v", err)
}
if blk.Number().Uint64() != tt.start+uint64(count) {
t.Fatalf("wrong block number: want %d, got %d", tt.start+uint64(count), blk.Number().Uint64())
}
count++
}
if hlIt.Error() != nil {
t.Fatalf("iterator error: %v", hlIt.Error())
}
if count != totalBlocks {
t.Fatalf("wrong iteration count: want %d, got %d", totalBlocks, count)
}
})
}
}
// TestInitialTD tests the InitialTD calculation separately since it requires
// specific TD/difficulty values.
func TestInitialTD(t *testing.T) {
t.Parallel()
f, err := os.CreateTemp(t.TempDir(), "erae-initial-td-test")
if err != nil {
t.Fatalf("error creating temp file: %v", err)
}
defer f.Close()
builder := NewBuilder(f)
// First block: difficulty=5, TD=10, so initial TD = 10-5 = 5.
header := mustEncode(&types.Header{Number: big.NewInt(0), Difficulty: big.NewInt(5)})
body := mustEncode(&types.Body{})
receipts := mustEncode([]types.SlimReceipt{})
if err := builder.AddRLP(header, body, receipts, 0, common.Hash{0}, big.NewInt(10), big.NewInt(5)); err != nil {
t.Fatalf("error adding block: %v", err)
}
// Second block: difficulty=3, TD=13.
header2 := mustEncode(&types.Header{Number: big.NewInt(1), Difficulty: big.NewInt(3)})
if err := builder.AddRLP(header2, body, receipts, 1, common.Hash{1}, big.NewInt(13), big.NewInt(3)); err != nil {
t.Fatalf("error adding block: %v", err)
}
if _, err := builder.Finalize(); err != nil {
t.Fatalf("error finalizing: %v", err)
}
e, err := Open(f.Name())
if err != nil {
t.Fatalf("failed to open era: %v", err)
}
defer e.Close()
initialTD, err := e.InitialTD()
if err != nil {
t.Fatalf("error getting initial TD: %v", err)
}
// Initial TD should be TD[0] - Difficulty[0] = 10 - 5 = 5.
if initialTD.Cmp(big.NewInt(5)) != 0 {
t.Fatalf("wrong initial TD: want 5, got %s", initialTD)
}
}
func mustEncode(obj any) []byte {
b, err := rlp.EncodeToBytes(obj)
if err != nil {
panic(fmt.Sprintf("failed to encode obj: %v", err))
}
return b
}

View file

@ -0,0 +1,240 @@
// 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 execdb
import (
"errors"
"io"
"math/big"
"slices"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/internal/era"
"github.com/ethereum/go-ethereum/internal/era/e2store"
"github.com/ethereum/go-ethereum/rlp"
"github.com/klauspost/compress/snappy"
)
type Iterator struct {
inner *RawIterator
block *types.Block // cache for decoded block
}
// NewIterator returns a header/body/receipt iterator over the archive.
// Call Next immediately to position on the first block.
func NewIterator(e era.Era) (era.Iterator, error) {
inner, err := NewRawIterator(e.(*Era))
if err != nil {
return nil, err
}
return &Iterator{inner: inner}, nil
}
// Next advances to the next block entry.
func (it *Iterator) Next() bool {
it.block = nil
return it.inner.Next()
}
// Number is the number of the block currently loaded.
func (it *Iterator) Number() uint64 { return it.inner.next - 1 }
// Error returns any iteration error (EOF is reported as nil, identical
// to the Era1 iterator behaviour).
func (it *Iterator) Error() error { return it.inner.Error() }
// Block decodes the current header+body into a *types.Block.
func (it *Iterator) Block() (*types.Block, error) {
if it.block != nil {
return it.block, nil
}
if it.inner.Header == nil || it.inner.Body == nil {
return nil, errors.New("header and body must be nonnil")
}
var (
h types.Header
b types.Body
)
if err := rlp.Decode(it.inner.Header, &h); err != nil {
return nil, err
}
if err := rlp.Decode(it.inner.Body, &b); err != nil {
return nil, err
}
it.block = types.NewBlockWithHeader(&h).WithBody(b)
return it.block, nil
}
// Receipts decodes receipts for the current block.
func (it *Iterator) Receipts() (types.Receipts, error) {
block, err := it.Block()
if err != nil {
return nil, err
}
if it.inner.Receipts == nil {
return nil, errors.New("receipts must be nonnil")
}
var rs []*types.SlimReceipt
if err := rlp.Decode(it.inner.Receipts, &rs); err != nil {
return nil, err
}
if len(rs) != len(block.Transactions()) {
return nil, errors.New("number of txs does not match receipts")
}
receipts := make([]*types.Receipt, len(rs))
for i, receipt := range rs {
receipts[i] = (*types.Receipt)(receipt)
receipts[i].Bloom = types.CreateBloom(receipts[i])
}
return receipts, nil
}
// BlockAndReceipts is a convenience wrapper.
func (it *Iterator) BlockAndReceipts() (*types.Block, types.Receipts, error) {
b, err := it.Block()
if err != nil {
return nil, nil, err
}
r, err := it.Receipts()
if err != nil {
return nil, nil, err
}
return b, r, nil
}
// TotalDifficulty returns the TD at the current position (if present).
func (it *Iterator) TotalDifficulty() (*big.Int, error) {
if it.inner.TotalDifficulty == nil {
return nil, errors.New("totaldifficulty stream is nil")
}
tdBytes, err := io.ReadAll(it.inner.TotalDifficulty)
if err != nil {
return nil, err
}
slices.Reverse(tdBytes)
return new(big.Int).SetBytes(tdBytes), nil
}
// -----------------------------------------------------------------------------
// Lowlevel iterator (raw TLV/offset handling, no decoding)
// -----------------------------------------------------------------------------
type RawIterator struct {
e *Era
next uint64 // next block to pull
err error
Header io.Reader
Body io.Reader
Receipts io.Reader
TotalDifficulty io.Reader // nil when archive omits TD
}
// NewRawIterator creates an iterator positioned *before* the first block.
func NewRawIterator(e *Era) (*RawIterator, error) {
return &RawIterator{e: e, next: e.m.start}, nil
}
// Next loads the next blocks components; returns false on EOF or error.
func (it *RawIterator) Next() bool {
it.err = nil // clear previous error
if it.next >= it.e.m.start+it.e.m.count {
it.clear()
return false
}
headerOffset, err := it.e.headerOff(it.next)
if err != nil {
it.setErr(err)
return false
}
it.Header, _, err = newSnappyReader(it.e.s, era.TypeCompressedHeader, headerOffset)
if err != nil {
it.setErr(err)
return false
}
bodyOffset, err := it.e.bodyOff(it.next)
if err != nil {
it.setErr(err)
return false
}
it.Body, _, err = newSnappyReader(it.e.s, era.TypeCompressedBody, bodyOffset)
if err != nil {
it.setErr(err)
return false
}
receiptsOffset, err := it.e.receiptOff(it.next)
if err != nil {
it.setErr(err)
return false
}
it.Receipts, _, err = newSnappyReader(it.e.s, era.TypeCompressedSlimReceipts, receiptsOffset)
if err != nil {
it.setErr(err)
return false
}
// Check if TD component is present in this file (pre-merge or merge-transition epoch).
if int(td) < int(it.e.m.components) {
tdOffset, err := it.e.tdOff(it.next)
if err != nil {
it.setErr(err)
return false
}
it.TotalDifficulty, _, err = it.e.s.ReaderAt(era.TypeTotalDifficulty, tdOffset)
if err != nil {
it.setErr(err)
return false
}
} else {
it.TotalDifficulty = nil
}
it.next++
return true
}
func (it *RawIterator) Number() uint64 { return it.next - 1 }
func (it *RawIterator) Error() error {
if it.err == io.EOF {
return nil
}
return it.err
}
func (it *RawIterator) setErr(err error) {
it.err = err
it.clear()
}
func (it *RawIterator) clear() {
it.Header, it.Body, it.Receipts, it.TotalDifficulty = nil, nil, nil, nil
}
// newSnappyReader behaves like era.newSnappyReader: returns a snappy.Reader
// plus the length of the underlying TLV payload so callers can advance offsets.
func newSnappyReader(r *e2store.Reader, typ uint16, off int64) (io.Reader, int64, error) {
raw, n, err := r.ReaderAt(typ, off)
if err != nil {
return nil, 0, err
}
return snappy.NewReader(raw), int64(n), nil
}

View file

@ -0,0 +1,296 @@
// 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 execdb
import (
"encoding/binary"
"fmt"
"io"
"math/big"
"os"
"slices"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/internal/era"
"github.com/ethereum/go-ethereum/internal/era/e2store"
"github.com/ethereum/go-ethereum/rlp"
"github.com/klauspost/compress/snappy"
)
// Era object represents an era file that contains blocks and their components.
type Era struct {
f era.ReadAtSeekCloser
s *e2store.Reader
m metadata // metadata for the Era file
}
// Filename returns a recognizable filename for an EraE file.
// The filename uses the last block hash to uniquely identify the epoch's content.
func Filename(network string, epoch int, lastBlockHash common.Hash) string {
return fmt.Sprintf("%s-%05d-%s.erae", network, epoch, lastBlockHash.Hex()[2:10])
}
// Open accesses the era file.
func Open(path string) (*Era, error) {
f, err := os.Open(path)
if err != nil {
return nil, err
}
e := &Era{f: f, s: e2store.NewReader(f)}
if err := e.loadIndex(); err != nil {
f.Close()
return nil, err
}
return e, nil
}
// Close closes the era file safely.
func (e *Era) Close() error {
if e.f == nil {
return nil
}
err := e.f.Close()
e.f = nil
return err
}
// From returns an Era backed by f.
func From(f era.ReadAtSeekCloser) (era.Era, error) {
e := &Era{f: f, s: e2store.NewReader(f)}
if err := e.loadIndex(); err != nil {
f.Close()
return nil, err
}
return e, nil
}
// Start retrieves the starting block number.
func (e *Era) Start() uint64 {
return e.m.start
}
// Count retrieves the count of blocks present.
func (e *Era) Count() uint64 {
return e.m.count
}
// Iterator returns an iterator over the era file.
func (e *Era) Iterator() (era.Iterator, error) {
return NewIterator(e)
}
// GetBlockByNumber retrieves the block if present within the era file.
func (e *Era) GetBlockByNumber(blockNum uint64) (*types.Block, error) {
h, err := e.GetHeader(blockNum)
if err != nil {
return nil, err
}
b, err := e.GetBody(blockNum)
if err != nil {
return nil, err
}
return types.NewBlockWithHeader(h).WithBody(*b), nil
}
// GetHeader retrieves the header from the era file through the cached offset table.
func (e *Era) GetHeader(num uint64) (*types.Header, error) {
off, err := e.headerOff(num)
if err != nil {
return nil, err
}
r, _, err := e.s.ReaderAt(era.TypeCompressedHeader, off)
if err != nil {
return nil, err
}
r = snappy.NewReader(r)
var h types.Header
return &h, rlp.Decode(r, &h)
}
// GetBody retrieves the body from the era file through cached offset table.
func (e *Era) GetBody(num uint64) (*types.Body, error) {
off, err := e.bodyOff(num)
if err != nil {
return nil, err
}
r, _, err := e.s.ReaderAt(era.TypeCompressedBody, off)
if err != nil {
return nil, err
}
r = snappy.NewReader(r)
var b types.Body
return &b, rlp.Decode(r, &b)
}
// GetTD retrieves the td from the era file through cached offset table.
func (e *Era) GetTD(blockNum uint64) (*big.Int, error) {
off, err := e.tdOff(blockNum)
if err != nil {
return nil, err
}
r, _, err := e.s.ReaderAt(era.TypeTotalDifficulty, off)
if err != nil {
return nil, err
}
buf, _ := io.ReadAll(r)
slices.Reverse(buf)
td := new(big.Int).SetBytes(buf)
return td, nil
}
// GetRawBodyByNumber returns the RLP-encoded body for the given block number.
func (e *Era) GetRawBodyByNumber(blockNum uint64) ([]byte, error) {
off, err := e.bodyOff(blockNum)
if err != nil {
return nil, err
}
r, _, err := e.s.ReaderAt(era.TypeCompressedBody, off)
if err != nil {
return nil, err
}
r = snappy.NewReader(r)
return io.ReadAll(r)
}
// GetRawReceiptsByNumber returns the RLP-encoded receipts for the given block number.
func (e *Era) GetRawReceiptsByNumber(blockNum uint64) ([]byte, error) {
off, err := e.receiptOff(blockNum)
if err != nil {
return nil, err
}
r, _, err := e.s.ReaderAt(era.TypeCompressedSlimReceipts, off)
if err != nil {
return nil, err
}
r = snappy.NewReader(r)
return io.ReadAll(r)
}
// InitialTD returns initial total difficulty before the difficulty of the
// first block of the Era is applied. Returns an error if TD is not available
// (e.g., post-merge epoch).
func (e *Era) InitialTD() (*big.Int, error) {
// Check if TD component exists.
if int(td) >= int(e.m.components) {
return nil, fmt.Errorf("total difficulty not available in this epoch")
}
// Get first header to read its difficulty.
header, err := e.GetHeader(e.m.start)
if err != nil {
return nil, fmt.Errorf("read first header: %w", err)
}
// Get TD after first block using the index.
firstTD, err := e.GetTD(e.m.start)
if err != nil {
return nil, fmt.Errorf("read first TD: %w", err)
}
// Initial TD = TD[0] - Difficulty[0]
return new(big.Int).Sub(firstTD, header.Difficulty), nil
}
// Accumulator reads the accumulator entry in the EraE file if it exists.
// Note that one premerge erae files will contain an accumulator entry.
func (e *Era) Accumulator() (common.Hash, error) {
entry, err := e.s.Find(era.TypeAccumulator)
if err != nil {
return common.Hash{}, err
}
return common.BytesToHash(entry.Value), nil
}
// loadIndex loads in the index table containing all offsets and caches it.
func (e *Era) loadIndex() error {
var err error
e.m.length, err = e.f.Seek(0, io.SeekEnd)
if err != nil {
return err
}
b := make([]byte, 16)
if _, err = e.f.ReadAt(b, e.m.length-16); err != nil {
return err
}
e.m.components = binary.LittleEndian.Uint64(b[0:8])
e.m.count = binary.LittleEndian.Uint64(b[8:16])
payloadlen := 8 + 8*e.m.count*e.m.components + 16 // 8 for start block, 8 per property per block, 16 for the number of properties and the number of blocks
tlvstart := e.m.length - int64(payloadlen) - 8
_, err = e.f.ReadAt(b[:8], tlvstart+8)
if err != nil {
return err
}
e.m.start = binary.LittleEndian.Uint64(b[:8])
return nil
}
// headerOff, bodyOff, receiptOff, and tdOff return the offsets of the respective components for a given block number.
func (e *Era) headerOff(num uint64) (int64, error) { return e.indexOffset(num, header) }
func (e *Era) bodyOff(num uint64) (int64, error) { return e.indexOffset(num, body) }
func (e *Era) receiptOff(num uint64) (int64, error) { return e.indexOffset(num, receipts) }
func (e *Era) tdOff(num uint64) (int64, error) { return e.indexOffset(num, td) }
// indexOffset calculates offset to a certain component for a block number within a file.
func (e *Era) indexOffset(n uint64, component componentType) (int64, error) {
if n < e.m.start || n >= e.m.start+e.m.count {
return 0, fmt.Errorf("block %d out of range [%d,%d)", n, e.m.start, e.m.start+e.m.count)
}
if int(component) >= int(e.m.components) {
return 0, fmt.Errorf("component %d not present", component)
}
payloadlen := 8 + 8*e.m.count*e.m.components + 16 // 8 for start block, 8 per property per block, 16 for the number of properties and the number of blocks
indstart := e.m.length - int64(payloadlen) - 8
rec := (n-e.m.start)*e.m.components + uint64(component)
pos := indstart + 8 + 8 + int64(rec*8)
var buf [8]byte
if _, err := e.f.ReadAt(buf[:], pos); err != nil {
return 0, err
}
rel := binary.LittleEndian.Uint64(buf[:])
return int64(rel) + indstart, nil
}
// metadata contains the information about the era file that is written into the file.
type metadata struct {
start uint64 // start block number
count uint64 // number of blocks in the era
components uint64 // number of properties
length int64 // length of the file in bytes
}
// componentType represents the integer form of a specific type that can be present in the era file.
type componentType int
// header, body, receipts, td, and proof are the different types of components that can be present in the era file.
const (
header componentType = iota
body
receipts
td
proof
)

View file

@ -14,7 +14,7 @@
// 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 era
package onedb
import (
"bytes"
@ -26,6 +26,7 @@ import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/internal/era"
"github.com/ethereum/go-ethereum/internal/era/e2store"
"github.com/ethereum/go-ethereum/rlp"
"github.com/golang/snappy"
@ -78,6 +79,8 @@ type Builder struct {
indexes []uint64
hashes []common.Hash
tds []*big.Int
accumulator *common.Hash // accumulator root, set by Finalize
written int
buf *bytes.Buffer
@ -85,7 +88,7 @@ type Builder struct {
}
// NewBuilder returns a new Builder instance.
func NewBuilder(w io.Writer) *Builder {
func NewBuilder(w io.Writer) era.Builder {
buf := bytes.NewBuffer(nil)
return &Builder{
w: e2store.NewWriter(w),
@ -117,7 +120,7 @@ func (b *Builder) Add(block *types.Block, receipts types.Receipts, td *big.Int)
func (b *Builder) AddRLP(header, body, receipts []byte, number uint64, hash common.Hash, td, difficulty *big.Int) error {
// Write Era1 version entry before first block.
if b.startNum == nil {
n, err := b.w.Write(TypeVersion, nil)
n, err := b.w.Write(era.TypeVersion, nil)
if err != nil {
return err
}
@ -126,8 +129,8 @@ func (b *Builder) AddRLP(header, body, receipts []byte, number uint64, hash comm
b.startTd = new(big.Int).Sub(td, difficulty)
b.written += n
}
if len(b.indexes) >= MaxEra1Size {
return fmt.Errorf("exceeds maximum batch size of %d", MaxEra1Size)
if len(b.indexes) >= era.MaxSize {
return fmt.Errorf("exceeds maximum batch size of %d", era.MaxSize)
}
b.indexes = append(b.indexes, uint64(b.written))
@ -135,19 +138,19 @@ func (b *Builder) AddRLP(header, body, receipts []byte, number uint64, hash comm
b.tds = append(b.tds, td)
// Write block data.
if err := b.snappyWrite(TypeCompressedHeader, header); err != nil {
if err := b.snappyWrite(era.TypeCompressedHeader, header); err != nil {
return err
}
if err := b.snappyWrite(TypeCompressedBody, body); err != nil {
if err := b.snappyWrite(era.TypeCompressedBody, body); err != nil {
return err
}
if err := b.snappyWrite(TypeCompressedReceipts, receipts); err != nil {
if err := b.snappyWrite(era.TypeCompressedReceipts, receipts); err != nil {
return err
}
// Also write total difficulty, but don't snappy encode.
btd := bigToBytes32(td)
n, err := b.w.Write(TypeTotalDifficulty, btd[:])
btd := era.BigToBytes32(td)
n, err := b.w.Write(era.TypeTotalDifficulty, btd[:])
b.written += n
if err != nil {
return err
@ -157,21 +160,24 @@ func (b *Builder) AddRLP(header, body, receipts []byte, number uint64, hash comm
}
// Finalize computes the accumulator and block index values, then writes the
// corresponding e2store entries.
// corresponding e2store entries. Era1 always has an accumulator, so this
// always returns a valid hash.
func (b *Builder) Finalize() (common.Hash, error) {
if b.startNum == nil {
return common.Hash{}, errors.New("finalize called on empty builder")
}
// Compute accumulator root and write entry.
root, err := ComputeAccumulator(b.hashes, b.tds)
root, err := era.ComputeAccumulator(b.hashes, b.tds)
if err != nil {
return common.Hash{}, fmt.Errorf("error calculating accumulator root: %w", err)
}
n, err := b.w.Write(TypeAccumulator, root[:])
n, err := b.w.Write(era.TypeAccumulator, root[:])
b.written += n
if err != nil {
return common.Hash{}, fmt.Errorf("error writing accumulator: %w", err)
}
b.accumulator = &root
// Get beginning of index entry to calculate block relative offset.
base := int64(b.written)
@ -196,13 +202,19 @@ func (b *Builder) Finalize() (common.Hash, error) {
binary.LittleEndian.PutUint64(index[8+count*8:], uint64(count))
// Finally, write the block index entry.
if _, err := b.w.Write(TypeBlockIndex, index); err != nil {
if _, err := b.w.Write(era.TypeBlockIndex, index); err != nil {
return common.Hash{}, fmt.Errorf("unable to write block index: %w", err)
}
return root, nil
}
// Accumulator returns the accumulator root after Finalize has been called.
// For Era1, this always returns a non-nil value since all blocks are pre-merge.
func (b *Builder) Accumulator() *common.Hash {
return b.accumulator
}
// snappyWrite is a small helper to take care snappy encoding and writing an e2store entry.
func (b *Builder) snappyWrite(typ uint16, in []byte) error {
var (

View file

@ -14,7 +14,7 @@
// 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 era
package onedb
import (
"bytes"
@ -22,6 +22,7 @@ import (
"io"
"math/big"
"os"
"slices"
"testing"
"github.com/ethereum/go-ethereum/common"
@ -82,7 +83,11 @@ func TestEra1Builder(t *testing.T) {
t.Fatalf("failed to open era: %v", err)
}
defer e.Close()
it, err := NewRawIterator(e)
eraPtr, ok := e.(*Era)
if !ok {
t.Fatalf("failed to assert *Era type")
}
it, err := NewRawIterator(eraPtr)
if err != nil {
t.Fatalf("failed to make iterator: %s", err)
}
@ -119,7 +124,7 @@ func TestEra1Builder(t *testing.T) {
if !bytes.Equal(rawReceipts, chain.receipts[i]) {
t.Fatalf("mismatched receipts: want %s, got %s", chain.receipts[i], rawReceipts)
}
receipts, err := getReceiptsByNumber(e, i)
receipts, err := getReceiptsByNumber(eraPtr, i)
if err != nil {
t.Fatalf("error reading receipts: %v", err)
}
@ -136,7 +141,8 @@ func TestEra1Builder(t *testing.T) {
if err != nil {
t.Fatalf("error reading td: %v", err)
}
td := new(big.Int).SetBytes(reverseOrder(rawTd))
slices.Reverse(rawTd)
td := new(big.Int).SetBytes(rawTd)
if td.Cmp(chain.tds[i]) != 0 {
t.Fatalf("mismatched tds: want %s, got %s", chain.tds[i], td)
}

View file

@ -14,14 +14,16 @@
// 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 era
package onedb
import (
"errors"
"io"
"math/big"
"slices"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/internal/era"
"github.com/ethereum/go-ethereum/rlp"
)
@ -32,8 +34,8 @@ type Iterator struct {
// NewIterator returns a new Iterator instance. Next must be immediately
// called on new iterators to load the first item.
func NewIterator(e *Era) (*Iterator, error) {
inner, err := NewRawIterator(e)
func NewIterator(e era.Era) (era.Iterator, error) {
inner, err := NewRawIterator(e.(*Era))
if err != nil {
return nil, err
}
@ -107,7 +109,8 @@ func (it *Iterator) TotalDifficulty() (*big.Int, error) {
if err != nil {
return nil, err
}
return new(big.Int).SetBytes(reverseOrder(td)), nil
slices.Reverse(td)
return new(big.Int).SetBytes(td), nil
}
// RawIterator reads an RLP-encode Era1 entries.
@ -151,22 +154,22 @@ func (it *RawIterator) Next() bool {
return false
}
var n int64
if it.Header, n, it.err = newSnappyReader(it.e.s, TypeCompressedHeader, off); it.err != nil {
if it.Header, n, it.err = newSnappyReader(it.e.s, era.TypeCompressedHeader, off); it.err != nil {
it.clear()
return true
}
off += n
if it.Body, n, it.err = newSnappyReader(it.e.s, TypeCompressedBody, off); it.err != nil {
if it.Body, n, it.err = newSnappyReader(it.e.s, era.TypeCompressedBody, off); it.err != nil {
it.clear()
return true
}
off += n
if it.Receipts, n, it.err = newSnappyReader(it.e.s, TypeCompressedReceipts, off); it.err != nil {
if it.Receipts, n, it.err = newSnappyReader(it.e.s, era.TypeCompressedReceipts, off); it.err != nil {
it.clear()
return true
}
off += n
if it.TotalDifficulty, _, it.err = it.e.s.ReaderAt(TypeTotalDifficulty, off); it.err != nil {
if it.TotalDifficulty, _, it.err = it.e.s.ReaderAt(era.TypeTotalDifficulty, off); it.err != nil {
it.clear()
return true
}

View file

@ -0,0 +1,279 @@
// 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 onedb
import (
"encoding/binary"
"fmt"
"io"
"math/big"
"os"
"slices"
"sync"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/internal/era"
"github.com/ethereum/go-ethereum/internal/era/e2store"
"github.com/ethereum/go-ethereum/rlp"
"github.com/golang/snappy"
)
// Filename returns a recognizable Era1-formatted file name for the specified
// epoch and network.
func Filename(network string, epoch int, root common.Hash) string {
return fmt.Sprintf("%s-%05d-%s.era1", network, epoch, root.Hex()[2:10])
}
type ReadAtSeekCloser interface {
io.ReaderAt
io.Seeker
io.Closer
}
// Era reads and Era1 file.
type Era struct {
f ReadAtSeekCloser // backing era1 file
s *e2store.Reader // e2store reader over f
m metadata // start, count, length info
mu *sync.Mutex // lock for buf
buf [8]byte // buffer reading entry offsets
}
// From returns an Era backed by f.
func From(f era.ReadAtSeekCloser) (era.Era, error) {
m, err := readMetadata(f)
if err != nil {
return nil, err
}
return &Era{
f: f,
s: e2store.NewReader(f),
m: m,
mu: new(sync.Mutex),
}, nil
}
// Open returns an Era backed by the given filename.
func Open(filename string) (era.Era, error) {
f, err := os.Open(filename)
if err != nil {
return nil, err
}
return From(f)
}
func (e *Era) Close() error {
return e.f.Close()
}
// Iterator returns an iterator over the era file.
func (e *Era) Iterator() (era.Iterator, error) {
return NewIterator(e)
}
// GetBlockByNumber returns the block for the given block number.
func (e *Era) GetBlockByNumber(num uint64) (*types.Block, error) {
if e.m.start > num || e.m.start+e.m.count <= num {
return nil, fmt.Errorf("out-of-bounds: %d not in [%d, %d)", num, e.m.start, e.m.start+e.m.count)
}
off, err := e.readOffset(num)
if err != nil {
return nil, err
}
r, n, err := newSnappyReader(e.s, era.TypeCompressedHeader, off)
if err != nil {
return nil, err
}
var header types.Header
if err := rlp.Decode(r, &header); err != nil {
return nil, err
}
off += n
r, _, err = newSnappyReader(e.s, era.TypeCompressedBody, off)
if err != nil {
return nil, err
}
var body types.Body
if err := rlp.Decode(r, &body); err != nil {
return nil, err
}
return types.NewBlockWithHeader(&header).WithBody(body), nil
}
// GetRawBodyByNumber returns the RLP-encoded body for the given block number.
func (e *Era) GetRawBodyByNumber(num uint64) ([]byte, error) {
if e.m.start > num || e.m.start+e.m.count <= num {
return nil, fmt.Errorf("out-of-bounds: %d not in [%d, %d)", num, e.m.start, e.m.start+e.m.count)
}
off, err := e.readOffset(num)
if err != nil {
return nil, err
}
off, err = e.s.SkipN(off, 1)
if err != nil {
return nil, err
}
r, _, err := newSnappyReader(e.s, era.TypeCompressedBody, off)
if err != nil {
return nil, err
}
return io.ReadAll(r)
}
// GetRawReceiptsByNumber returns the RLP-encoded receipts for the given block number.
func (e *Era) GetRawReceiptsByNumber(num uint64) ([]byte, error) {
if e.m.start > num || e.m.start+e.m.count <= num {
return nil, fmt.Errorf("out-of-bounds: %d not in [%d, %d)", num, e.m.start, e.m.start+e.m.count)
}
off, err := e.readOffset(num)
if err != nil {
return nil, err
}
// Skip over header and body.
off, err = e.s.SkipN(off, 2)
if err != nil {
return nil, err
}
r, _, err := newSnappyReader(e.s, era.TypeCompressedReceipts, off)
if err != nil {
return nil, err
}
return io.ReadAll(r)
}
// Accumulator reads the accumulator entry in the Era1 file.
func (e *Era) Accumulator() (common.Hash, error) {
entry, err := e.s.Find(era.TypeAccumulator)
if err != nil {
return common.Hash{}, err
}
return common.BytesToHash(entry.Value), nil
}
// InitialTD returns initial total difficulty before the difficulty of the
// first block of the Era1 is applied.
func (e *Era) InitialTD() (*big.Int, error) {
var (
r io.Reader
header types.Header
rawTd []byte
n int64
off int64
err error
)
// Read first header.
if off, err = e.readOffset(e.m.start); err != nil {
return nil, err
}
if r, n, err = newSnappyReader(e.s, era.TypeCompressedHeader, off); err != nil {
return nil, err
}
if err := rlp.Decode(r, &header); err != nil {
return nil, err
}
off += n
// Skip over header and body.
off, err = e.s.SkipN(off, 2)
if err != nil {
return nil, err
}
// Read total difficulty after first block.
if r, _, err = e.s.ReaderAt(era.TypeTotalDifficulty, off); err != nil {
return nil, err
}
rawTd, err = io.ReadAll(r)
if err != nil {
return nil, err
}
slices.Reverse(rawTd)
td := new(big.Int).SetBytes(rawTd)
return td.Sub(td, header.Difficulty), nil
}
// Start returns the listed start block.
func (e *Era) Start() uint64 {
return e.m.start
}
// Count returns the total number of blocks in the Era1.
func (e *Era) Count() uint64 {
return e.m.count
}
// readOffset reads a specific block's offset from the block index. The value n
// is the absolute block number desired.
func (e *Era) readOffset(n uint64) (int64, error) {
var (
blockIndexRecordOffset = e.m.length - 24 - int64(e.m.count)*8 // skips start, count, and header
firstIndex = blockIndexRecordOffset + 16 // first index after header / start-num
indexOffset = int64(n-e.m.start) * 8 // desired index * size of indexes
offOffset = firstIndex + indexOffset // offset of block offset
)
e.mu.Lock()
defer e.mu.Unlock()
clear(e.buf[:])
if _, err := e.f.ReadAt(e.buf[:], offOffset); err != nil {
return 0, err
}
// Since the block offset is relative from the start of the block index record
// we need to add the record offset to it's offset to get the block's absolute
// offset.
return blockIndexRecordOffset + int64(binary.LittleEndian.Uint64(e.buf[:])), nil
}
// newSnappyReader returns a snappy.Reader for the e2store entry value at off.
func newSnappyReader(e *e2store.Reader, expectedType uint16, off int64) (io.Reader, int64, error) {
r, n, err := e.ReaderAt(expectedType, off)
if err != nil {
return nil, 0, err
}
return snappy.NewReader(r), int64(n), err
}
// metadata wraps the metadata in the block index.
type metadata struct {
start uint64
count uint64
length int64
}
// readMetadata reads the metadata stored in an Era1 file's block index.
func readMetadata(f ReadAtSeekCloser) (m metadata, err error) {
// Determine length of reader.
if m.length, err = f.Seek(0, io.SeekEnd); err != nil {
return
}
b := make([]byte, 16)
// Read count. It's the last 8 bytes of the file.
if _, err = f.ReadAt(b[:8], m.length-8); err != nil {
return
}
m.count = binary.LittleEndian.Uint64(b)
// Read start. It's at the offset -sizeof(m.count) -
// count*sizeof(indexEntry) - sizeof(m.start)
if _, err = f.ReadAt(b[8:], m.length-16-int64(m.count*8)); err != nil {
return
}
m.start = binary.LittleEndian.Uint64(b[8:])
return
}

36
internal/era/proof.go Normal file
View file

@ -0,0 +1,36 @@
// 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 era
import (
"io"
"github.com/ethereum/go-ethereum/rlp"
)
type ProofVariant uint16
const (
ProofNone ProofVariant = iota
)
// Proof is the interface for all block proof types in the package.
// It's a stub for later integration into Era.
type Proof interface {
EncodeRLP(w io.Writer) error
DecodeRLP(s *rlp.Stream) error
Variant() ProofVariant
}

View file

@ -112,7 +112,6 @@ const (
errCodeClientLimitExceeded = -38026
errCodeInternalError = -32603
errCodeInvalidParams = -32602
errCodeReverted = -32000
errCodeVMError = -32015
errCodeTxSyncTimeout = 4
)

View file

@ -310,7 +310,7 @@ func (sim *simulator) processBlock(ctx context.Context, block *simBlock, header,
if errors.Is(result.Err, vm.ErrExecutionReverted) {
// If the result contains a revert reason, try to unpack it.
revertErr := newRevertError(result.Revert())
callRes.Error = &callError{Message: revertErr.Error(), Code: errCodeReverted, Data: revertErr.ErrorData().(string)}
callRes.Error = &callError{Message: revertErr.Error(), Code: revertErr.ErrorCode(), Data: revertErr.ErrorData().(string)}
} else {
callRes.Error = &callError{Message: result.Err.Error(), Code: errCodeVMError}
}

View file

@ -329,8 +329,8 @@ func (args *TransactionArgs) setBlobTxSidecar(ctx context.Context, config sideca
commitments = make([]kzg4844.Commitment, n)
proofs = make([]kzg4844.Proof, 0, proofLen)
)
for i, b := range args.Blobs {
c, err := kzg4844.BlobToCommitment(&b)
for i := range args.Blobs {
c, err := kzg4844.BlobToCommitment(&args.Blobs[i])
if err != nil {
return fmt.Errorf("blobs[%d]: error computing commitment: %v", i, err)
}
@ -338,13 +338,13 @@ func (args *TransactionArgs) setBlobTxSidecar(ctx context.Context, config sideca
switch config.blobSidecarVersion {
case types.BlobSidecarVersion0:
p, err := kzg4844.ComputeBlobProof(&b, c)
p, err := kzg4844.ComputeBlobProof(&args.Blobs[i], c)
if err != nil {
return fmt.Errorf("blobs[%d]: error computing proof: %v", i, err)
}
proofs = append(proofs, p)
case types.BlobSidecarVersion1:
ps, err := kzg4844.ComputeCellProofs(&b)
ps, err := kzg4844.ComputeCellProofs(&args.Blobs[i])
if err != nil {
return fmt.Errorf("blobs[%d]: error computing proof: %v", i, err)
}
@ -356,8 +356,8 @@ func (args *TransactionArgs) setBlobTxSidecar(ctx context.Context, config sideca
} else {
switch config.blobSidecarVersion {
case types.BlobSidecarVersion0:
for i, b := range args.Blobs {
if err := kzg4844.VerifyBlobProof(&b, args.Commitments[i], args.Proofs[i]); err != nil {
for i := range args.Blobs {
if err := kzg4844.VerifyBlobProof(&args.Blobs[i], args.Commitments[i], args.Proofs[i]); err != nil {
return fmt.Errorf("failed to verify blob proof: %v", err)
}
}

View file

@ -7,7 +7,10 @@ import (
// GetOrRegisterCounter returns an existing Counter or constructs and registers
// a new Counter.
func GetOrRegisterCounter(name string, r Registry) *Counter {
return getOrRegister(name, NewCounter, r)
if r == nil {
r = DefaultRegistry
}
return r.GetOrRegister(name, func() any { return NewCounter() }).(*Counter)
}
// NewCounter constructs a new Counter.

View file

@ -8,7 +8,10 @@ import (
// GetOrRegisterCounterFloat64 returns an existing *CounterFloat64 or constructs and registers
// a new CounterFloat64.
func GetOrRegisterCounterFloat64(name string, r Registry) *CounterFloat64 {
return getOrRegister(name, NewCounterFloat64, r)
if r == nil {
r = DefaultRegistry
}
return r.GetOrRegister(name, func() any { return NewCounterFloat64() }).(*CounterFloat64)
}
// NewCounterFloat64 constructs a new CounterFloat64.

View file

@ -11,7 +11,10 @@ func (g GaugeSnapshot) Value() int64 { return int64(g) }
// GetOrRegisterGauge returns an existing Gauge or constructs and registers a
// new Gauge.
func GetOrRegisterGauge(name string, r Registry) *Gauge {
return getOrRegister(name, NewGauge, r)
if r == nil {
r = DefaultRegistry
}
return r.GetOrRegister(name, func() any { return NewGauge() }).(*Gauge)
}
// NewGauge constructs a new Gauge.

View file

@ -8,7 +8,10 @@ import (
// GetOrRegisterGaugeFloat64 returns an existing GaugeFloat64 or constructs and registers a
// new GaugeFloat64.
func GetOrRegisterGaugeFloat64(name string, r Registry) *GaugeFloat64 {
return getOrRegister(name, NewGaugeFloat64, r)
if r == nil {
r = DefaultRegistry
}
return r.GetOrRegister(name, func() any { return NewGaugeFloat64() }).(*GaugeFloat64)
}
// GaugeFloat64Snapshot is a read-only copy of a GaugeFloat64.

View file

@ -16,7 +16,10 @@ func (val GaugeInfoValue) String() string {
// GetOrRegisterGaugeInfo returns an existing GaugeInfo or constructs and registers a
// new GaugeInfo.
func GetOrRegisterGaugeInfo(name string, r Registry) *GaugeInfo {
return getOrRegister(name, NewGaugeInfo, r)
if r == nil {
r = DefaultRegistry
}
return r.GetOrRegister(name, func() any { return NewGaugeInfo() }).(*GaugeInfo)
}
// NewGaugeInfo constructs a new GaugeInfo.

View file

@ -23,13 +23,19 @@ type Histogram interface {
// GetOrRegisterHistogram returns an existing Histogram or constructs and
// registers a new StandardHistogram.
func GetOrRegisterHistogram(name string, r Registry, s Sample) Histogram {
return getOrRegister(name, func() Histogram { return NewHistogram(s) }, r)
if r == nil {
r = DefaultRegistry
}
return r.GetOrRegister(name, func() any { return NewHistogram(s) }).(Histogram)
}
// GetOrRegisterHistogramLazy returns an existing Histogram or constructs and
// registers a new StandardHistogram.
func GetOrRegisterHistogramLazy(name string, r Registry, s func() Sample) Histogram {
return getOrRegister(name, func() Histogram { return NewHistogram(s()) }, r)
if r == nil {
r = DefaultRegistry
}
return r.GetOrRegister(name, func() any { return NewHistogram(s()) }).(Histogram)
}
// NewHistogram constructs a new StandardHistogram from a Sample.

View file

@ -12,7 +12,10 @@ import (
// Be sure to unregister the meter from the registry once it is of no use to
// allow for garbage collection.
func GetOrRegisterMeter(name string, r Registry) *Meter {
return getOrRegister(name, NewMeter, r)
if r == nil {
r = DefaultRegistry
}
return r.GetOrRegister(name, func() any { return NewMeter() }).(*Meter)
}
// NewMeter constructs a new Meter and launches a goroutine.

View file

@ -143,6 +143,8 @@ func (r *StandardRegistry) GetAll() map[string]map[string]interface{} {
values["value"] = metric.Snapshot().Value()
case *GaugeFloat64:
values["value"] = metric.Snapshot().Value()
case *GaugeInfo:
values["value"] = metric.Snapshot().Value()
case *Healthcheck:
values["error"] = nil
metric.Check()
@ -186,6 +188,18 @@ func (r *StandardRegistry) GetAll() map[string]map[string]interface{} {
values["5m.rate"] = t.Rate5()
values["15m.rate"] = t.Rate15()
values["mean.rate"] = t.RateMean()
case *ResettingTimer:
t := metric.Snapshot()
ps := t.Percentiles([]float64{0.5, 0.75, 0.95, 0.99, 0.999})
values["count"] = t.Count()
values["min"] = t.Min()
values["max"] = t.Max()
values["mean"] = t.Mean()
values["median"] = ps[0]
values["75%"] = ps[1]
values["95%"] = ps[2]
values["99%"] = ps[3]
values["99.9%"] = ps[4]
}
data[name] = values
})
@ -331,13 +345,6 @@ func GetOrRegister(name string, i func() interface{}) interface{} {
return DefaultRegistry.GetOrRegister(name, i)
}
func getOrRegister[T any](name string, ctor func() T, r Registry) T {
if r == nil {
r = DefaultRegistry
}
return r.GetOrRegister(name, func() any { return ctor() }).(T)
}
// Register the given metric under the given name. Returns a ErrDuplicateMetric
// if a metric by the given name is already registered.
func Register(name string, i interface{}) error {

View file

@ -14,6 +14,31 @@ func BenchmarkRegistry(b *testing.B) {
}
}
func BenchmarkRegistryGetOrRegister(b *testing.B) {
sample := func() Sample { return nil }
tests := []struct {
name string
ctor func() any
}{
{name: "counter", ctor: func() any { return GetOrRegisterCounter("counter", DefaultRegistry) }},
{name: "gauge", ctor: func() any { return GetOrRegisterGauge("gauge", DefaultRegistry) }},
{name: "gaugefloat64", ctor: func() any { return GetOrRegisterGaugeFloat64("gaugefloat64", DefaultRegistry) }},
{name: "histogram", ctor: func() any { return GetOrRegisterHistogram("histogram", DefaultRegistry, sample()) }},
{name: "meter", ctor: func() any { return GetOrRegisterMeter("meter", DefaultRegistry) }},
{name: "timer", ctor: func() any { return GetOrRegisterTimer("timer", DefaultRegistry) }},
{name: "gaugeinfo", ctor: func() any { return GetOrRegisterGaugeInfo("gaugeinfo", DefaultRegistry) }},
{name: "resettingtimer", ctor: func() any { return GetOrRegisterResettingTimer("resettingtimer", DefaultRegistry) }},
{name: "runtimehistogramlazy", ctor: func() any { return GetOrRegisterHistogramLazy("runtimehistogramlazy", DefaultRegistry, sample) }},
}
for _, test := range tests {
b.Run(test.name, func(b *testing.B) {
for i := 0; i < b.N; i++ {
test.ctor()
}
})
}
}
func BenchmarkRegistryGetOrRegisterParallel_8(b *testing.B) {
benchmarkRegistryGetOrRegisterParallel(b, 8)
}
@ -268,7 +293,7 @@ func TestPrefixedChildRegistryGet(t *testing.T) {
}
func TestChildPrefixedRegistryRegister(t *testing.T) {
r := NewPrefixedChildRegistry(DefaultRegistry, "prefix.")
r := NewPrefixedChildRegistry(NewRegistry(), "prefix.")
err := r.Register("foo", NewCounter())
c := NewCounter()
Register("bar", c)

View file

@ -8,7 +8,10 @@ import (
// GetOrRegisterResettingTimer returns an existing ResettingTimer or constructs and registers a
// new ResettingTimer.
func GetOrRegisterResettingTimer(name string, r Registry) *ResettingTimer {
return getOrRegister(name, NewResettingTimer, r)
if r == nil {
r = DefaultRegistry
}
return r.GetOrRegister(name, func() any { return NewResettingTimer() }).(*ResettingTimer)
}
// NewRegisteredResettingTimer constructs and registers a new ResettingTimer.

View file

@ -8,8 +8,10 @@ import (
)
func getOrRegisterRuntimeHistogram(name string, scale float64, r Registry) *runtimeHistogram {
constructor := func() Histogram { return newRuntimeHistogram(scale) }
return getOrRegister(name, constructor, r).(*runtimeHistogram)
if r == nil {
r = DefaultRegistry
}
return r.GetOrRegister(name, func() any { return newRuntimeHistogram(scale) }).(*runtimeHistogram)
}
// runtimeHistogram wraps a runtime/metrics histogram.

View file

@ -10,7 +10,10 @@ import (
// Be sure to unregister the meter from the registry once it is of no use to
// allow for garbage collection.
func GetOrRegisterTimer(name string, r Registry) *Timer {
return getOrRegister(name, NewTimer, r)
if r == nil {
r = DefaultRegistry
}
return r.GetOrRegister(name, func() any { return NewTimer() }).(*Timer)
}
// NewCustomTimer constructs a new Timer from a Histogram and a Meter.

View file

@ -138,6 +138,9 @@ func (h *httpServer) start() error {
// Initialize the server.
h.server = &http.Server{Handler: h}
h.server.Protocols = new(http.Protocols)
h.server.Protocols.SetHTTP1(true)
h.server.Protocols.SetUnencryptedHTTP2(true)
if h.timeouts != (rpc.HTTPTimeouts{}) {
CheckTimeouts(&h.timeouts)
h.server.ReadTimeout = h.timeouts.ReadTimeout

View file

@ -593,6 +593,38 @@ func TestHTTPWriteTimeout(t *testing.T) {
})
}
func TestHTTP2H2C(t *testing.T) {
srv := createAndStartServer(t, &httpConfig{}, false, &wsConfig{}, nil)
defer srv.stop()
// Create an HTTP/2 cleartext client.
transport := &http.Transport{}
transport.Protocols = new(http.Protocols)
transport.Protocols.SetUnencryptedHTTP2(true)
client := &http.Client{Transport: transport}
body := strings.NewReader(`{"jsonrpc":"2.0","id":1,"method":"rpc_modules","params":[]}`)
resp, err := client.Post("http://"+srv.listenAddr(), "application/json", body)
if err != nil {
t.Fatal(err)
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
t.Fatalf("expected status 200, got %d", resp.StatusCode)
}
if resp.Proto != "HTTP/2.0" {
t.Fatalf("expected HTTP/2.0, got %s", resp.Proto)
}
result, err := io.ReadAll(resp.Body)
if err != nil {
t.Fatal(err)
}
if !strings.Contains(string(result), "jsonrpc") {
t.Fatalf("unexpected response: %s", result)
}
}
func apis() []rpc.API {
return []rpc.API{
{

View file

@ -28,10 +28,10 @@ import (
"strings"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/crypto/keccak"
"github.com/ethereum/go-ethereum/p2p/enode"
"github.com/ethereum/go-ethereum/p2p/enr"
"github.com/ethereum/go-ethereum/rlp"
"golang.org/x/crypto/sha3"
)
// Tree is a merkle tree of node records.
@ -262,7 +262,7 @@ const (
)
func subdomain(e entry) string {
h := sha3.NewLegacyKeccak256()
h := keccak.NewLegacyKeccak256()
io.WriteString(h, e.String())
return b32format.EncodeToString(h.Sum(nil)[:16])
}
@ -272,7 +272,7 @@ func (e *rootEntry) String() string {
}
func (e *rootEntry) sigHash() []byte {
h := sha3.NewLegacyKeccak256()
h := keccak.NewLegacyKeccak256()
fmt.Fprintf(h, rootPrefix+" e=%s l=%s seq=%d", e.eroot, e.lroot, e.seq)
return h.Sum(nil)
}

View file

@ -23,9 +23,9 @@ import (
"github.com/ethereum/go-ethereum/common/math"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/crypto/keccak"
"github.com/ethereum/go-ethereum/p2p/enr"
"github.com/ethereum/go-ethereum/rlp"
"golang.org/x/crypto/sha3"
)
// ValidSchemes is a List of known secure identity schemes.
@ -49,7 +49,7 @@ func SignV4(r *enr.Record, privkey *ecdsa.PrivateKey) error {
cpy.Set(enr.ID("v4"))
cpy.Set(Secp256k1(privkey.PublicKey))
h := sha3.NewLegacyKeccak256()
h := keccak.NewLegacyKeccak256()
rlp.Encode(h, cpy.AppendElements(nil))
sig, err := crypto.Sign(h.Sum(nil), privkey)
if err != nil {
@ -70,7 +70,7 @@ func (V4ID) Verify(r *enr.Record, sig []byte) error {
return errors.New("invalid public key")
}
h := sha3.NewLegacyKeccak256()
h := keccak.NewLegacyKeccak256()
rlp.Encode(h, r.AppendElements(nil))
if !crypto.VerifySignature(entry, h.Sum(nil), sig) {
return enr.ErrInvalidSig

View file

@ -36,9 +36,9 @@ import (
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/crypto/ecies"
"github.com/ethereum/go-ethereum/crypto/keccak"
"github.com/ethereum/go-ethereum/rlp"
"github.com/golang/snappy"
"golang.org/x/crypto/sha3"
)
// Conn is an RLPx network connection. It wraps a low-level network connection. The
@ -486,10 +486,10 @@ func (h *handshakeState) secrets(auth, authResp []byte) (Secrets, error) {
}
// setup sha3 instances for the MACs
mac1 := sha3.NewLegacyKeccak256()
mac1 := keccak.NewLegacyKeccak256()
mac1.Write(xor(s.MAC, h.respNonce))
mac1.Write(auth)
mac2 := sha3.NewLegacyKeccak256()
mac2 := keccak.NewLegacyKeccak256()
mac2.Write(xor(s.MAC, h.initNonce))
mac2.Write(authResp)
if h.initiator {

View file

@ -1375,7 +1375,6 @@ func (err *ConfigCompatError) Error() string {
// Rules is a one time interface meaning that it shouldn't be used in between transition
// phases.
type Rules struct {
ChainID *big.Int
IsHomestead, IsEIP150, IsEIP155, IsEIP158 bool
IsEIP2929, IsEIP4762 bool
IsByzantium, IsConstantinople, IsPetersburg, IsIstanbul bool
@ -1386,15 +1385,10 @@ type Rules struct {
// Rules ensures c's ChainID is not nil.
func (c *ChainConfig) Rules(num *big.Int, isMerge bool, timestamp uint64) Rules {
chainID := c.ChainID
if chainID == nil {
chainID = new(big.Int)
}
// disallow setting Merge out of order
isMerge = isMerge && c.IsLondon(num)
isVerkle := isMerge && c.IsVerkle(num, timestamp)
return Rules{
ChainID: new(big.Int).Set(chainID),
IsHomestead: c.IsHomestead(num),
IsEIP150: c.IsEIP150(num),
IsEIP155: c.IsEIP155(num),

View file

@ -102,6 +102,29 @@ func EncodeToReader(val interface{}) (size int, r io.Reader, err error) {
return buf.size(), &encReader{buf: buf}, nil
}
// EncodeToRawList encodes val as an RLP list and returns it as a RawList.
func EncodeToRawList[T any](val []T) (RawList[T], error) {
if len(val) == 0 {
return RawList[T]{}, nil
}
// Encode the value to an internal buffer.
buf := getEncBuffer()
defer encBufferPool.Put(buf)
if err := buf.encode(val); err != nil {
return RawList[T]{}, err
}
// Create the RawList. RawList assumes the initial list header is padded
// 9 bytes, so we have to determine the offset where the value should be
// placed.
contentSize := buf.lheads[0].size
bytes := make([]byte, contentSize+9)
offset := 9 - headsize(uint64(contentSize))
buf.copyTo(bytes[offset:])
return RawList[T]{enc: bytes}, nil
}
type listhead struct {
offset int // index of this header in string data
size int // total size of encoded data (including list headers)

View file

@ -16,31 +16,35 @@
package rlp
type listIterator struct {
// Iterator is an iterator over the elements of an encoded container.
type Iterator struct {
data []byte
next []byte
offset int
err error
}
// NewListIterator creates an iterator for the (list) represented by data
func NewListIterator(data RawValue) (*listIterator, error) {
// NewListIterator creates an iterator for the (list) represented by data.
func NewListIterator(data RawValue) (Iterator, error) {
k, t, c, err := readKind(data)
if err != nil {
return nil, err
return Iterator{}, err
}
if k != List {
return nil, ErrExpectedList
}
it := &listIterator{
data: data[t : t+c],
return Iterator{}, ErrExpectedList
}
it := newIterator(data[t:t+c], int(t))
return it, nil
}
func newIterator(data []byte, initialOffset int) Iterator {
return Iterator{data: data, offset: initialOffset}
}
// Next forwards the iterator one step.
// Returns true if there is a next item or an error occurred on this step (check Err()).
// On parse error, the iterator is marked finished and subsequent calls return false.
func (it *listIterator) Next() bool {
func (it *Iterator) Next() bool {
if len(it.data) == 0 {
return false
}
@ -52,17 +56,34 @@ func (it *listIterator) Next() bool {
it.data = nil
return true
}
it.next = it.data[:t+c]
it.data = it.data[t+c:]
length := t + c
it.next = it.data[:length]
it.data = it.data[length:]
it.offset += int(length)
it.err = nil
return true
}
// Value returns the current value
func (it *listIterator) Value() []byte {
// Count returns the remaining number of items.
// Note this is O(n) and the result may be incorrect if the list data is invalid.
// The returned count is always an upper bound on the remaining items
// that will be visited by the iterator.
func (it *Iterator) Count() int {
count, _ := CountValues(it.data)
return count
}
// Value returns the current value.
func (it *Iterator) Value() []byte {
return it.next
}
func (it *listIterator) Err() error {
// Offset returns the offset of the current value into the list data.
func (it *Iterator) Offset() int {
return it.offset - len(it.next)
}
// Err returns the error that caused Next to return false, if any.
func (it *Iterator) Err() error {
return it.err
}

View file

@ -38,10 +38,18 @@ func TestIterator(t *testing.T) {
t.Fatal("expected two elems, got zero")
}
txs := it.Value()
if offset := it.Offset(); offset != 3 {
t.Fatal("wrong offset", offset, "want 3")
}
// Check that uncles exist
if !it.Next() {
t.Fatal("expected two elems, got one")
}
if offset := it.Offset(); offset != 219 {
t.Fatal("wrong offset", offset, "want 219")
}
txit, err := NewListIterator(txs)
if err != nil {
t.Fatal(err)

View file

@ -17,8 +17,10 @@
package rlp
import (
"fmt"
"io"
"reflect"
"slices"
)
// RawValue represents an encoded RLP value and can be used to delay
@ -28,6 +30,129 @@ type RawValue []byte
var rawValueType = reflect.TypeFor[RawValue]()
// RawList represents an encoded RLP list.
type RawList[T any] struct {
// The list is stored in encoded form.
// Note this buffer has some special properties:
//
// - if the buffer is nil, it's the zero value, representing
// an empty list.
// - if the buffer is non-nil, it must have a length of at least
// 9 bytes, which is reserved padding for the encoded list header.
// The remaining bytes, enc[9:], store the content bytes of the list.
//
// The implementation code mostly works with the Content method because it
// returns something valid either way.
enc []byte
}
// Content returns the RLP-encoded data of the list.
// This does not include the list-header.
// The return value is a direct reference to the internal buffer, not a copy.
func (r *RawList[T]) Content() []byte {
if r.enc == nil {
return nil
} else {
return r.enc[9:]
}
}
// EncodeRLP writes the encoded list to the writer.
func (r RawList[T]) EncodeRLP(w io.Writer) error {
_, err := w.Write(r.Bytes())
return err
}
// Bytes returns the RLP encoding of the list.
// Note the return value aliases the internal buffer.
func (r *RawList[T]) Bytes() []byte {
if r == nil || r.enc == nil {
return []byte{0xC0} // zero value encodes as empty list
}
n := puthead(r.enc, 0xC0, 0xF7, uint64(len(r.Content())))
copy(r.enc[9-n:], r.enc[:n])
return r.enc[9-n:]
}
// DecodeRLP decodes the list. This does not perform validation of the items!
func (r *RawList[T]) DecodeRLP(s *Stream) error {
k, size, err := s.Kind()
if err != nil {
return err
}
if k != List {
return fmt.Errorf("%w for %T", ErrExpectedList, r)
}
enc := make([]byte, 9+size)
if err := s.readFull(enc[9:]); err != nil {
return err
}
*r = RawList[T]{enc: enc}
return nil
}
// Items decodes and returns all items in the list.
func (r *RawList[T]) Items() ([]T, error) {
items := make([]T, r.Len())
it := r.ContentIterator()
for i := 0; it.Next(); i++ {
if err := DecodeBytes(it.Value(), &items[i]); err != nil {
return items[:i], err
}
}
return items, nil
}
// Len returns the number of items in the list.
func (r *RawList[T]) Len() int {
len, _ := CountValues(r.Content())
return len
}
// Size returns the encoded size of the list.
func (r *RawList[T]) Size() uint64 {
return ListSize(uint64(len(r.Content())))
}
// Empty returns true if the list contains no items.
func (r *RawList[T]) Empty() bool {
return len(r.Content()) == 0
}
// ContentIterator returns an iterator over the content of the list.
// Note the offsets returned by iterator.Offset are relative to the
// Content bytes of the list.
func (r *RawList[T]) ContentIterator() Iterator {
return newIterator(r.Content(), 0)
}
// Append adds an item to the end of the list.
func (r *RawList[T]) Append(item T) error {
if r.enc == nil {
r.enc = make([]byte, 9)
}
eb := getEncBuffer()
defer encBufferPool.Put(eb)
if err := eb.encode(item); err != nil {
return err
}
prevEnd := len(r.enc)
end := prevEnd + eb.size()
r.enc = slices.Grow(r.enc, eb.size())[:end]
eb.copyTo(r.enc[prevEnd:end])
return nil
}
// AppendRaw adds an encoded item to the list.
func (r *RawList[T]) AppendRaw(b []byte) {
if r.enc == nil {
r.enc = make([]byte, 9)
}
r.enc = append(r.enc, b...)
}
// StringSize returns the encoded size of a string.
func StringSize(s string) uint64 {
switch n := len(s); n {

View file

@ -19,11 +19,213 @@ package rlp
import (
"bytes"
"errors"
"fmt"
"io"
"reflect"
"testing"
"testing/quick"
)
type rawListTest[T any] struct {
input string
content string
items []T
length int
}
func (test rawListTest[T]) name() string {
return fmt.Sprintf("%T-%d", *new(T), test.length)
}
func (test rawListTest[T]) run(t *testing.T) {
// check decoding and properties
input := unhex(test.input)
inputSize := len(input)
var rl RawList[T]
if err := DecodeBytes(input, &rl); err != nil {
t.Fatal("decode failed:", err)
}
if l := rl.Len(); l != test.length {
t.Fatalf("wrong Len %d, want %d", l, test.length)
}
if sz := rl.Size(); sz != uint64(inputSize) {
t.Fatalf("wrong Size %d, want %d", sz, inputSize)
}
items, err := rl.Items()
if err != nil {
t.Fatal("Items failed:", err)
}
if !reflect.DeepEqual(items, test.items) {
t.Fatal("wrong items:", items)
}
if !bytes.Equal(rl.Content(), unhex(test.content)) {
t.Fatalf("wrong Content %x, want %s", rl.Content(), test.content)
}
if !bytes.Equal(rl.Bytes(), unhex(test.input)) {
t.Fatalf("wrong Bytes %x, want %s", rl.Bytes(), test.input)
}
// check iterator
it := rl.ContentIterator()
i := 0
if count := it.Count(); count != test.length {
t.Fatalf("iterator has wrong Count %d, want %d", count, test.length)
}
for it.Next() {
var item T
if err := DecodeBytes(it.Value(), &item); err != nil {
t.Fatalf("item %d decode error: %v", i, err)
}
if !reflect.DeepEqual(item, items[i]) {
t.Fatalf("iterator has wrong item %v at %d", item, i)
}
i++
}
if i != test.length {
t.Fatalf("iterator produced %d values, want %d", i, test.length)
}
if it.Err() != nil {
t.Fatalf("iterator error: %v", it.Err())
}
// check encoding round trip
output, err := EncodeToBytes(&rl)
if err != nil {
t.Fatal("encode error:", err)
}
if !bytes.Equal(output, unhex(test.input)) {
t.Fatalf("encoding does not round trip: %x", output)
}
// check EncodeToRawList on items produces same bytes
encRL, err := EncodeToRawList(test.items)
if err != nil {
t.Fatal("EncodeToRawList error:", err)
}
encRLOutput, err := EncodeToBytes(&encRL)
if err != nil {
t.Fatal("EncodeToBytes of encoded list failed:", err)
}
if !bytes.Equal(encRLOutput, output) {
t.Fatalf("wrong encoding of EncodeToRawList result: %x", encRLOutput)
}
}
func TestRawList(t *testing.T) {
tests := []interface {
name() string
run(t *testing.T)
}{
rawListTest[uint64]{
input: "C0",
content: "",
items: []uint64{},
length: 0,
},
rawListTest[uint64]{
input: "C3010203",
content: "010203",
items: []uint64{1, 2, 3},
length: 3,
},
rawListTest[simplestruct]{
input: "C6C20102C20304",
content: "C20102C20304",
items: []simplestruct{{1, "\x02"}, {3, "\x04"}},
length: 2,
},
rawListTest[string]{
input: "F83C836161618362626283636363836464648365656583666666836767678368686883696969836A6A6A836B6B6B836C6C6C836D6D6D836E6E6E836F6F6F",
content: "836161618362626283636363836464648365656583666666836767678368686883696969836A6A6A836B6B6B836C6C6C836D6D6D836E6E6E836F6F6F",
items: []string{"aaa", "bbb", "ccc", "ddd", "eee", "fff", "ggg", "hhh", "iii", "jjj", "kkk", "lll", "mmm", "nnn", "ooo"},
length: 15,
},
}
for _, test := range tests {
t.Run(test.name(), test.run)
}
}
func TestRawListEmpty(t *testing.T) {
// zero value list
var rl RawList[uint64]
b, _ := EncodeToBytes(&rl)
if !bytes.Equal(b, unhex("C0")) {
t.Fatalf("empty RawList has wrong encoding %x", b)
}
if !rl.Empty() {
t.Fatal("list should be Empty")
}
if rl.Len() != 0 {
t.Fatalf("empty list has Len %d", rl.Len())
}
if rl.Size() != 1 {
t.Fatalf("empty list has Size %d", rl.Size())
}
if len(rl.Content()) > 0 {
t.Fatalf("empty list has non-empty Content")
}
if !bytes.Equal(rl.Bytes(), []byte{0xC0}) {
t.Fatalf("empty list has wrong encoding")
}
// nil pointer
var nilptr *RawList[uint64]
b, _ = EncodeToBytes(nilptr)
if !bytes.Equal(b, unhex("C0")) {
t.Fatalf("nil pointer to RawList has wrong encoding %x", b)
}
}
// This checks that *RawList works in an 'optional' context.
func TestRawListOptional(t *testing.T) {
type foo struct {
L *RawList[uint64] `rlp:"optional"`
}
// nil pointer encoding
var empty foo
b, _ := EncodeToBytes(empty)
if !bytes.Equal(b, unhex("C0")) {
t.Fatalf("nil pointer to RawList has wrong encoding %x", b)
}
// decoding
var dec foo
if err := DecodeBytes(unhex("C0"), &dec); err != nil {
t.Fatal(err)
}
if dec.L != nil {
t.Fatal("rawlist was decoded as non-nil")
}
}
func TestRawListAppend(t *testing.T) {
var rl RawList[simplestruct]
v1 := simplestruct{1, "one"}
v2 := simplestruct{2, "two"}
if err := rl.Append(v1); err != nil {
t.Fatal("append 1 failed:", err)
}
if err := rl.Append(v2); err != nil {
t.Fatal("append 2 failed:", err)
}
if rl.Len() != 2 {
t.Fatalf("wrong Len %d", rl.Len())
}
if rl.Size() != 13 {
t.Fatalf("wrong Size %d", rl.Size())
}
if !bytes.Equal(rl.Content(), unhex("C501836F6E65 C5028374776F")) {
t.Fatalf("wrong Content %x", rl.Content())
}
encoded, _ := EncodeToBytes(&rl)
if !bytes.Equal(encoded, unhex("CC C501836F6E65 C5028374776F")) {
t.Fatalf("wrong encoding %x", encoded)
}
}
func TestCountValues(t *testing.T) {
tests := []struct {
input string // note: spaces in input are stripped by unhex

View file

@ -242,8 +242,8 @@ func (args *SendTxArgs) validateTxSidecar() error {
if args.Proofs != nil {
if len(args.Proofs) == n {
// v1 transaction
for i, b := range args.Blobs {
if err := kzg4844.VerifyBlobProof(&b, args.Commitments[i], args.Proofs[i]); err != nil {
for i := range args.Blobs {
if err := kzg4844.VerifyBlobProof(&args.Blobs[i], args.Commitments[i], args.Proofs[i]); err != nil {
return fmt.Errorf("failed to verify blob proof: %v", err)
}
}
@ -259,8 +259,8 @@ func (args *SendTxArgs) validateTxSidecar() error {
if args.Commitments == nil {
// Generate commitment and proof.
commitments := make([]kzg4844.Commitment, n)
for i, b := range args.Blobs {
c, err := kzg4844.BlobToCommitment(&b)
for i := range args.Blobs {
c, err := kzg4844.BlobToCommitment(&args.Blobs[i])
if err != nil {
return fmt.Errorf("blobs[%d]: error computing commitment: %v", i, err)
}
@ -269,8 +269,8 @@ func (args *SendTxArgs) validateTxSidecar() error {
var proofs []kzg4844.Proof
if args.BlobVersion == types.BlobSidecarVersion1 {
proofs = make([]kzg4844.Proof, 0, n*kzg4844.CellProofsPerBlob)
for i, b := range args.Blobs {
p, err := kzg4844.ComputeCellProofs(&b)
for i := range args.Blobs {
p, err := kzg4844.ComputeCellProofs(&args.Blobs[i])
if err != nil {
return fmt.Errorf("blobs[%d]: error computing cell proof: %v", i, err)
}
@ -278,8 +278,8 @@ func (args *SendTxArgs) validateTxSidecar() error {
}
} else {
proofs = make([]kzg4844.Proof, 0, n)
for i, b := range args.Blobs {
p, err := kzg4844.ComputeBlobProof(&b, commitments[i])
for i := range args.Blobs {
p, err := kzg4844.ComputeBlobProof(&args.Blobs[i], commitments[i])
if err != nil {
return fmt.Errorf("blobs[%d]: error computing proof: %v", i, err)
}

View file

@ -38,6 +38,7 @@ import (
"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/crypto/keccak"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rlp"
@ -45,7 +46,6 @@ import (
"github.com/ethereum/go-ethereum/triedb/hashdb"
"github.com/ethereum/go-ethereum/triedb/pathdb"
"github.com/holiman/uint256"
"golang.org/x/crypto/sha3"
)
// StateTest checks transaction processing without block context.
@ -496,7 +496,7 @@ func (tx *stTransaction) toMessage(ps stPostState, baseFee *big.Int) (*core.Mess
}
func rlpHash(x interface{}) (h common.Hash) {
hw := sha3.NewLegacyKeccak256()
hw := keccak.NewLegacyKeccak256()
rlp.Encode(hw, x)
hw.Sum(h[:0])
return h

View file

@ -236,7 +236,7 @@ func (t *BinaryTrie) GetAccount(addr common.Address) (*types.StateAccount, error
// not be modified by the caller. If a node was not found in the database, a
// trie.MissingNodeError is returned.
func (t *BinaryTrie) GetStorage(addr common.Address, key []byte) ([]byte, error) {
return t.root.Get(GetBinaryTreeKey(addr, key), t.nodeResolver)
return t.root.Get(GetBinaryTreeKeyStorageSlot(addr, key), t.nodeResolver)
}
// UpdateAccount updates the account information for the given address.
@ -302,7 +302,7 @@ func (t *BinaryTrie) DeleteAccount(addr common.Address) error {
// DeleteStorage removes any existing value for key from the trie. If a node was not
// found in the database, a trie.MissingNodeError is returned.
func (t *BinaryTrie) DeleteStorage(addr common.Address, key []byte) error {
k := GetBinaryTreeKey(addr, key)
k := GetBinaryTreeKeyStorageSlot(addr, key)
var zero [HashSize]byte
root, err := t.root.Insert(k, zero[:], t.nodeResolver, 0)
if err != nil {
@ -424,5 +424,5 @@ func (t *BinaryTrie) PrefetchStorage(addr common.Address, keys [][]byte) error {
// Witness returns a set containing all trie nodes that have been accessed.
func (t *BinaryTrie) Witness() map[string][]byte {
panic("not implemented")
return t.tracer.Values()
}

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