mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-06-23 07:04:35 +00:00
Merge branch 'ethereum:master' into master
This commit is contained in:
commit
c70e1a3caa
64 changed files with 2628 additions and 274 deletions
|
|
@ -16,7 +16,7 @@
|
|||
|
||||
// This file contains the implementation for interacting with the Trezor hardware
|
||||
// wallets. The wire protocol spec can be found on the SatoshiLabs website:
|
||||
// https://wiki.trezor.io/Developers_guide-Message_Workflows
|
||||
// https://docs.trezor.io/trezor-firmware/common/message-workflows.html
|
||||
|
||||
// !!! STAHP !!!
|
||||
//
|
||||
|
|
|
|||
|
|
@ -121,14 +121,13 @@ var (
|
|||
// Note: vivid is unsupported because there is no golang-1.6 package for it.
|
||||
// Note: the following Ubuntu releases have been officially deprecated on Launchpad:
|
||||
// wily, yakkety, zesty, artful, cosmic, disco, eoan, groovy, hirsuite, impish,
|
||||
// kinetic
|
||||
// kinetic, lunar
|
||||
debDistroGoBoots = map[string]string{
|
||||
"trusty": "golang-1.11", // 14.04, EOL: 04/2024
|
||||
"xenial": "golang-go", // 16.04, EOL: 04/2026
|
||||
"bionic": "golang-go", // 18.04, EOL: 04/2028
|
||||
"focal": "golang-go", // 20.04, EOL: 04/2030
|
||||
"jammy": "golang-go", // 22.04, EOL: 04/2032
|
||||
"lunar": "golang-go", // 23.04, EOL: 01/2024
|
||||
"mantic": "golang-go", // 23.10, EOL: 07/2024
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -166,7 +166,7 @@ func (c *Conn) ReadEth() (any, error) {
|
|||
case eth.TransactionsMsg:
|
||||
msg = new(eth.TransactionsPacket)
|
||||
case eth.NewPooledTransactionHashesMsg:
|
||||
msg = new(eth.NewPooledTransactionHashesPacket68)
|
||||
msg = new(eth.NewPooledTransactionHashesPacket)
|
||||
case eth.GetPooledTransactionsMsg:
|
||||
msg = new(eth.GetPooledTransactionsPacket)
|
||||
case eth.PooledTransactionsMsg:
|
||||
|
|
|
|||
|
|
@ -710,7 +710,7 @@ func (s *Suite) TestNewPooledTxs(t *utesting.T) {
|
|||
}
|
||||
|
||||
// Send announcement.
|
||||
ann := eth.NewPooledTransactionHashesPacket68{Types: txTypes, Sizes: sizes, Hashes: hashes}
|
||||
ann := eth.NewPooledTransactionHashesPacket{Types: txTypes, Sizes: sizes, Hashes: hashes}
|
||||
err = conn.Write(ethProto, eth.NewPooledTransactionHashesMsg, ann)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to write to connection: %v", err)
|
||||
|
|
@ -728,7 +728,7 @@ func (s *Suite) TestNewPooledTxs(t *utesting.T) {
|
|||
t.Fatalf("unexpected number of txs requested: wanted %d, got %d", len(hashes), len(msg.GetPooledTransactionsRequest))
|
||||
}
|
||||
return
|
||||
case *eth.NewPooledTransactionHashesPacket68:
|
||||
case *eth.NewPooledTransactionHashesPacket:
|
||||
continue
|
||||
case *eth.TransactionsPacket:
|
||||
continue
|
||||
|
|
@ -796,12 +796,12 @@ func (s *Suite) TestBlobViolations(t *utesting.T) {
|
|||
t2 = s.makeBlobTxs(2, 3, 0x2)
|
||||
)
|
||||
for _, test := range []struct {
|
||||
ann eth.NewPooledTransactionHashesPacket68
|
||||
ann eth.NewPooledTransactionHashesPacket
|
||||
resp eth.PooledTransactionsResponse
|
||||
}{
|
||||
// Invalid tx size.
|
||||
{
|
||||
ann: eth.NewPooledTransactionHashesPacket68{
|
||||
ann: eth.NewPooledTransactionHashesPacket{
|
||||
Types: []byte{types.BlobTxType, types.BlobTxType},
|
||||
Sizes: []uint32{uint32(t1[0].Size()), uint32(t1[1].Size() + 10)},
|
||||
Hashes: []common.Hash{t1[0].Hash(), t1[1].Hash()},
|
||||
|
|
@ -810,7 +810,7 @@ func (s *Suite) TestBlobViolations(t *utesting.T) {
|
|||
},
|
||||
// Wrong tx type.
|
||||
{
|
||||
ann: eth.NewPooledTransactionHashesPacket68{
|
||||
ann: eth.NewPooledTransactionHashesPacket{
|
||||
Types: []byte{types.DynamicFeeTxType, types.BlobTxType},
|
||||
Sizes: []uint32{uint32(t2[0].Size()), uint32(t2[1].Size())},
|
||||
Hashes: []common.Hash{t2[0].Hash(), t2[1].Hash()},
|
||||
|
|
|
|||
|
|
@ -70,7 +70,7 @@ func (s *Suite) sendTxs(txs []*types.Transaction) error {
|
|||
for _, tx := range *msg {
|
||||
got[tx.Hash()] = true
|
||||
}
|
||||
case *eth.NewPooledTransactionHashesPacket68:
|
||||
case *eth.NewPooledTransactionHashesPacket:
|
||||
for _, hash := range msg.Hashes {
|
||||
got[hash] = true
|
||||
}
|
||||
|
|
@ -146,7 +146,7 @@ func (s *Suite) sendInvalidTxs(txs []*types.Transaction) error {
|
|||
return fmt.Errorf("received bad tx: %s", tx.Hash())
|
||||
}
|
||||
}
|
||||
case *eth.NewPooledTransactionHashesPacket68:
|
||||
case *eth.NewPooledTransactionHashesPacket:
|
||||
for _, hash := range msg.Hashes {
|
||||
if _, ok := invalids[hash]; ok {
|
||||
return fmt.Errorf("received bad tx: %s", hash)
|
||||
|
|
|
|||
324
cmd/era/main.go
Normal file
324
cmd/era/main.go
Normal file
|
|
@ -0,0 +1,324 @@
|
|||
// Copyright 2023 The go-ethereum Authors
|
||||
// This file is part of go-ethereum.
|
||||
//
|
||||
// go-ethereum is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// go-ethereum is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"os"
|
||||
"path"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"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/ethapi"
|
||||
"github.com/ethereum/go-ethereum/internal/flags"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"github.com/ethereum/go-ethereum/trie"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
var app = flags.NewApp("go-ethereum era tool")
|
||||
|
||||
var (
|
||||
dirFlag = &cli.StringFlag{
|
||||
Name: "dir",
|
||||
Usage: "directory storing all relevant era1 files",
|
||||
Value: "eras",
|
||||
}
|
||||
networkFlag = &cli.StringFlag{
|
||||
Name: "network",
|
||||
Usage: "network name associated with era1 files",
|
||||
Value: "mainnet",
|
||||
}
|
||||
eraSizeFlag = &cli.IntFlag{
|
||||
Name: "size",
|
||||
Usage: "number of blocks per era",
|
||||
Value: era.MaxEra1Size,
|
||||
}
|
||||
txsFlag = &cli.BoolFlag{
|
||||
Name: "txs",
|
||||
Usage: "print full transaction values",
|
||||
}
|
||||
)
|
||||
|
||||
var (
|
||||
blockCommand = &cli.Command{
|
||||
Name: "block",
|
||||
Usage: "get block data",
|
||||
ArgsUsage: "<number>",
|
||||
Action: block,
|
||||
Flags: []cli.Flag{
|
||||
txsFlag,
|
||||
},
|
||||
}
|
||||
infoCommand = &cli.Command{
|
||||
Name: "info",
|
||||
ArgsUsage: "<epoch>",
|
||||
Usage: "get epoch information",
|
||||
Action: info,
|
||||
}
|
||||
verifyCommand = &cli.Command{
|
||||
Name: "verify",
|
||||
ArgsUsage: "<expected>",
|
||||
Usage: "verifies each era1 against expected accumulator root",
|
||||
Action: verify,
|
||||
}
|
||||
)
|
||||
|
||||
func init() {
|
||||
app.Commands = []*cli.Command{
|
||||
blockCommand,
|
||||
infoCommand,
|
||||
verifyCommand,
|
||||
}
|
||||
app.Flags = []cli.Flag{
|
||||
dirFlag,
|
||||
networkFlag,
|
||||
eraSizeFlag,
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
if err := app.Run(os.Args); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "%v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
// block prints the specified block from an era1 store.
|
||||
func block(ctx *cli.Context) error {
|
||||
num, err := strconv.ParseUint(ctx.Args().First(), 10, 64)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid block number: %w", err)
|
||||
}
|
||||
e, err := open(ctx, num/uint64(ctx.Int(eraSizeFlag.Name)))
|
||||
if err != nil {
|
||||
return fmt.Errorf("error opening era1: %w", err)
|
||||
}
|
||||
defer e.Close()
|
||||
// Read block with number.
|
||||
block, err := e.GetBlockByNumber(num)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error reading block %d: %w", num, err)
|
||||
}
|
||||
// Convert block to JSON and print.
|
||||
val := ethapi.RPCMarshalBlock(block, ctx.Bool(txsFlag.Name), ctx.Bool(txsFlag.Name), params.MainnetChainConfig)
|
||||
b, err := json.MarshalIndent(val, "", " ")
|
||||
if err != nil {
|
||||
return fmt.Errorf("error marshaling json: %w", err)
|
||||
}
|
||||
fmt.Println(string(b))
|
||||
return nil
|
||||
}
|
||||
|
||||
// info prints some high-level information about the era1 file.
|
||||
func info(ctx *cli.Context) error {
|
||||
epoch, err := strconv.ParseUint(ctx.Args().First(), 10, 64)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid epoch number: %w", err)
|
||||
}
|
||||
e, err := open(ctx, epoch)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer e.Close()
|
||||
acc, err := e.Accumulator()
|
||||
if err != nil {
|
||||
return fmt.Errorf("error reading accumulator: %w", err)
|
||||
}
|
||||
td, err := e.InitialTD()
|
||||
if err != nil {
|
||||
return fmt.Errorf("error reading total difficulty: %w", err)
|
||||
}
|
||||
info := struct {
|
||||
Accumulator common.Hash `json:"accumulator"`
|
||||
TotalDifficulty *big.Int `json:"totalDifficulty"`
|
||||
StartBlock uint64 `json:"startBlock"`
|
||||
Count uint64 `json:"count"`
|
||||
}{
|
||||
acc, td, 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)
|
||||
)
|
||||
entries, err := era.ReadDir(dir, network)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error reading era dir: %w", err)
|
||||
}
|
||||
if epoch >= uint64(len(entries)) {
|
||||
return nil, fmt.Errorf("epoch out-of-bounds: last %d, want %d", len(entries)-1, epoch)
|
||||
}
|
||||
return era.Open(path.Join(dir, entries[epoch]))
|
||||
}
|
||||
|
||||
// verify checks each era1 file in a directory to ensure it is well-formed and
|
||||
// that the accumulator matches the expected value.
|
||||
func verify(ctx *cli.Context) error {
|
||||
if ctx.Args().Len() != 1 {
|
||||
return fmt.Errorf("missing accumulators file")
|
||||
}
|
||||
|
||||
roots, err := readHashes(ctx.Args().First())
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to read expected roots file: %w", err)
|
||||
}
|
||||
|
||||
var (
|
||||
dir = ctx.String(dirFlag.Name)
|
||||
network = ctx.String(networkFlag.Name)
|
||||
start = time.Now()
|
||||
reported = time.Now()
|
||||
)
|
||||
|
||||
entries, err := era.ReadDir(dir, network)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error reading %s: %w", dir, err)
|
||||
}
|
||||
|
||||
if len(entries) != len(roots) {
|
||||
return fmt.Errorf("number of era1 files should match the number of accumulator hashes")
|
||||
}
|
||||
|
||||
// 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(path.Join(dir, name))
|
||||
if err != nil {
|
||||
return fmt.Errorf("error opening era1 file %s: %w", name, err)
|
||||
}
|
||||
defer e.Close()
|
||||
// Read accumulator and check against expected.
|
||||
if got, err := e.Accumulator(); err != nil {
|
||||
return fmt.Errorf("error retrieving accumulator for %s: %w", name, err)
|
||||
} else if got != want {
|
||||
return fmt.Errorf("invalid root %s: got %s, want %s", name, got, want)
|
||||
}
|
||||
// Recompute accumulator.
|
||||
if err := checkAccumulator(e); err != nil {
|
||||
return fmt.Errorf("error verify era1 file %s: %w", name, err)
|
||||
}
|
||||
// Give the user some feedback that something is happening.
|
||||
if time.Since(reported) >= 8*time.Second {
|
||||
fmt.Printf("Verifying Era1 files \t\t verified=%d,\t elapsed=%s\n", i, common.PrettyDuration(time.Since(start)))
|
||||
reported = time.Now()
|
||||
}
|
||||
return nil
|
||||
}()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// checkAccumulator verifies the accumulator matches the data in the Era.
|
||||
func checkAccumulator(e *era.Era) error {
|
||||
var (
|
||||
err error
|
||||
want common.Hash
|
||||
td *big.Int
|
||||
tds = make([]*big.Int, 0)
|
||||
hashes = make([]common.Hash, 0)
|
||||
)
|
||||
if want, err = e.Accumulator(); err != nil {
|
||||
return fmt.Errorf("error reading accumulator: %w", err)
|
||||
}
|
||||
if td, err = e.InitialTD(); err != nil {
|
||||
return fmt.Errorf("error reading total difficulty: %w", err)
|
||||
}
|
||||
it, err := era.NewIterator(e)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error making era iterator: %w", err)
|
||||
}
|
||||
// To fully verify an era the following attributes must be checked:
|
||||
// 1) the block index is constructed correctly
|
||||
// 2) the tx root matches the value in the block
|
||||
// 3) the receipts root matches the value in the block
|
||||
// 4) the starting total difficulty value is correct
|
||||
// 5) the accumulator is correct by recomputing it locally, which verifies
|
||||
// the blocks are all correct (via hash)
|
||||
//
|
||||
// The attributes 1), 2), and 3) are checked for each block. 4) and 5) require
|
||||
// accumulation across the entire set and are verified at the end.
|
||||
for it.Next() {
|
||||
// 1) next() walks the block index, so we're able to implicitly verify it.
|
||||
if it.Error() != nil {
|
||||
return fmt.Errorf("error reading block %d: %w", it.Number(), err)
|
||||
}
|
||||
block, receipts, err := it.BlockAndReceipts()
|
||||
if it.Error() != nil {
|
||||
return fmt.Errorf("error reading block %d: %w", it.Number(), err)
|
||||
}
|
||||
// 2) recompute tx root and verify against header.
|
||||
tr := types.DeriveSha(block.Transactions(), trie.NewStackTrie(nil))
|
||||
if tr != block.TxHash() {
|
||||
return fmt.Errorf("tx root in block %d mismatch: want %s, got %s", block.NumberU64(), block.TxHash(), tr)
|
||||
}
|
||||
// 3) recompute receipt root and check value against block.
|
||||
rr := types.DeriveSha(receipts, trie.NewStackTrie(nil))
|
||||
if rr != block.ReceiptHash() {
|
||||
return fmt.Errorf("receipt root in block %d mismatch: want %s, got %s", block.NumberU64(), block.ReceiptHash(), rr)
|
||||
}
|
||||
hashes = append(hashes, block.Hash())
|
||||
td.Add(td, block.Difficulty())
|
||||
tds = append(tds, new(big.Int).Set(td))
|
||||
}
|
||||
// 4+5) Verify accumulator and total difficulty.
|
||||
got, err := era.ComputeAccumulator(hashes, tds)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error computing accumulator: %w", err)
|
||||
}
|
||||
if got != want {
|
||||
return fmt.Errorf("expected accumulator root does not match calculated: got %s, want %s", got, want)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// readHashes reads a file of newline-delimited hashes.
|
||||
func readHashes(f string) ([]common.Hash, error) {
|
||||
b, err := os.ReadFile(f)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to open accumulators file")
|
||||
}
|
||||
s := strings.Split(string(b), "\n")
|
||||
// Remove empty last element, if present.
|
||||
if s[len(s)-1] == "" {
|
||||
s = s[:len(s)-1]
|
||||
}
|
||||
// Convert to hashes.
|
||||
r := make([]common.Hash, len(s))
|
||||
for i := range s {
|
||||
r[i] = common.HexToHash(s[i])
|
||||
}
|
||||
return r, nil
|
||||
}
|
||||
|
|
@ -35,10 +35,12 @@ import (
|
|||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/ethdb"
|
||||
"github.com/ethereum/go-ethereum/internal/era"
|
||||
"github.com/ethereum/go-ethereum/internal/flags"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/metrics"
|
||||
"github.com/ethereum/go-ethereum/node"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
|
|
@ -122,6 +124,33 @@ Optional second and third arguments control the first and
|
|||
last block to write. In this mode, the file will be appended
|
||||
if already existing. If the file ends with .gz, the output will
|
||||
be gzipped.`,
|
||||
}
|
||||
importHistoryCommand = &cli.Command{
|
||||
Action: importHistory,
|
||||
Name: "import-history",
|
||||
Usage: "Import an Era archive",
|
||||
ArgsUsage: "<dir>",
|
||||
Flags: flags.Merge([]cli.Flag{
|
||||
utils.TxLookupLimitFlag,
|
||||
},
|
||||
utils.DatabaseFlags,
|
||||
utils.NetworkFlags,
|
||||
),
|
||||
Description: `
|
||||
The import-history command will import blocks and their corresponding receipts
|
||||
from Era archives.
|
||||
`,
|
||||
}
|
||||
exportHistoryCommand = &cli.Command{
|
||||
Action: exportHistory,
|
||||
Name: "export-history",
|
||||
Usage: "Export blockchain history to Era archives",
|
||||
ArgsUsage: "<dir> <first> <last>",
|
||||
Flags: flags.Merge(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.
|
||||
`,
|
||||
}
|
||||
importPreimagesCommand = &cli.Command{
|
||||
Action: importPreimages,
|
||||
|
|
@ -364,7 +393,97 @@ func exportChain(ctx *cli.Context) error {
|
|||
}
|
||||
err = utils.ExportAppendChain(chain, fp, uint64(first), uint64(last))
|
||||
}
|
||||
if err != nil {
|
||||
utils.Fatalf("Export error: %v\n", err)
|
||||
}
|
||||
fmt.Printf("Export done in %v\n", time.Since(start))
|
||||
return nil
|
||||
}
|
||||
|
||||
func importHistory(ctx *cli.Context) error {
|
||||
if ctx.Args().Len() != 1 {
|
||||
utils.Fatalf("usage: %s", ctx.Command.ArgsUsage)
|
||||
}
|
||||
|
||||
stack, _ := makeConfigNode(ctx)
|
||||
defer stack.Close()
|
||||
|
||||
chain, db := utils.MakeChain(ctx, stack, false)
|
||||
defer db.Close()
|
||||
|
||||
var (
|
||||
start = time.Now()
|
||||
dir = ctx.Args().Get(0)
|
||||
network string
|
||||
)
|
||||
|
||||
// Determine network.
|
||||
if utils.IsNetworkPreset(ctx) {
|
||||
switch {
|
||||
case ctx.Bool(utils.MainnetFlag.Name):
|
||||
network = "mainnet"
|
||||
case ctx.Bool(utils.SepoliaFlag.Name):
|
||||
network = "sepolia"
|
||||
case ctx.Bool(utils.GoerliFlag.Name):
|
||||
network = "goerli"
|
||||
}
|
||||
} else {
|
||||
// No network flag set, try to determine network based on files
|
||||
// present in directory.
|
||||
var networks []string
|
||||
for _, n := range params.NetworkNames {
|
||||
entries, err := era.ReadDir(dir, n)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error reading %s: %w", dir, err)
|
||||
}
|
||||
if len(entries) > 0 {
|
||||
networks = append(networks, n)
|
||||
}
|
||||
}
|
||||
if len(networks) == 0 {
|
||||
return fmt.Errorf("no era1 files found in %s", dir)
|
||||
}
|
||||
if len(networks) > 1 {
|
||||
return fmt.Errorf("multiple networks found, use a network flag to specify desired network")
|
||||
}
|
||||
network = networks[0]
|
||||
}
|
||||
|
||||
if err := utils.ImportHistory(chain, db, dir, network); 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.
|
||||
func exportHistory(ctx *cli.Context) error {
|
||||
if ctx.Args().Len() != 3 {
|
||||
utils.Fatalf("usage: %s", ctx.Command.ArgsUsage)
|
||||
}
|
||||
|
||||
stack, _ := makeConfigNode(ctx)
|
||||
defer stack.Close()
|
||||
|
||||
chain, _ := utils.MakeChain(ctx, stack, true)
|
||||
start := time.Now()
|
||||
|
||||
var (
|
||||
dir = ctx.Args().Get(0)
|
||||
first, ferr = strconv.ParseInt(ctx.Args().Get(1), 10, 64)
|
||||
last, lerr = strconv.ParseInt(ctx.Args().Get(2), 10, 64)
|
||||
)
|
||||
if ferr != nil || lerr != nil {
|
||||
utils.Fatalf("Export error in parsing parameters: block number not an integer\n")
|
||||
}
|
||||
if first < 0 || last < 0 {
|
||||
utils.Fatalf("Export error: block number must be greater than 0\n")
|
||||
}
|
||||
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 {
|
||||
utils.Fatalf("Export error: %v\n", err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -208,6 +208,8 @@ func init() {
|
|||
initCommand,
|
||||
importCommand,
|
||||
exportCommand,
|
||||
importHistoryCommand,
|
||||
exportHistoryCommand,
|
||||
importPreimagesCommand,
|
||||
removedbCommand,
|
||||
dumpCommand,
|
||||
|
|
|
|||
191
cmd/utils/cmd.go
191
cmd/utils/cmd.go
|
|
@ -19,12 +19,15 @@ package utils
|
|||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"crypto/sha256"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"os/signal"
|
||||
"path"
|
||||
"runtime"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
|
@ -39,8 +42,10 @@ import (
|
|||
"github.com/ethereum/go-ethereum/eth/ethconfig"
|
||||
"github.com/ethereum/go-ethereum/ethdb"
|
||||
"github.com/ethereum/go-ethereum/internal/debug"
|
||||
"github.com/ethereum/go-ethereum/internal/era"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/node"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
|
@ -228,6 +233,105 @@ func ImportChain(chain *core.BlockChain, fn string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func readList(filename string) ([]string, error) {
|
||||
b, err := os.ReadFile(filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return strings.Split(string(b), "\n"), nil
|
||||
}
|
||||
|
||||
// ImportHistory imports Era1 files containing historical block information,
|
||||
// starting from genesis.
|
||||
func ImportHistory(chain *core.BlockChain, db ethdb.Database, dir string, network string) error {
|
||||
if chain.CurrentSnapBlock().Number.BitLen() != 0 {
|
||||
return fmt.Errorf("history import only supported when starting from genesis")
|
||||
}
|
||||
entries, err := era.ReadDir(dir, network)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error reading %s: %w", dir, err)
|
||||
}
|
||||
checksums, err := readList(path.Join(dir, "checksums.txt"))
|
||||
if err != nil {
|
||||
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))
|
||||
}
|
||||
var (
|
||||
start = time.Now()
|
||||
reported = time.Now()
|
||||
imported = 0
|
||||
forker = core.NewForkChoice(chain, nil)
|
||||
h = sha256.New()
|
||||
buf = bytes.NewBuffer(nil)
|
||||
)
|
||||
for i, filename := range entries {
|
||||
err := func() error {
|
||||
f, err := os.Open(path.Join(dir, filename))
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to open era: %w", 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)
|
||||
}
|
||||
h.Reset()
|
||||
buf.Reset()
|
||||
|
||||
// Import all block data from Era1.
|
||||
e, err := era.From(f)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error opening era: %w", err)
|
||||
}
|
||||
it, err := era.NewIterator(e)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error making era reader: %w", err)
|
||||
}
|
||||
for it.Next() {
|
||||
block, err := it.Block()
|
||||
if err != nil {
|
||||
return fmt.Errorf("error reading block %d: %w", it.Number(), err)
|
||||
}
|
||||
if block.Number().BitLen() == 0 {
|
||||
continue // skip genesis
|
||||
}
|
||||
receipts, err := it.Receipts()
|
||||
if err != nil {
|
||||
return fmt.Errorf("error reading receipts %d: %w", it.Number(), err)
|
||||
}
|
||||
if status, err := chain.HeaderChain().InsertHeaderChain([]*types.Header{block.Header()}, start, forker); err != nil {
|
||||
return fmt.Errorf("error inserting header %d: %w", it.Number(), err)
|
||||
} else if status != core.CanonStatTy {
|
||||
return fmt.Errorf("error inserting header %d, not canon: %v", it.Number(), status)
|
||||
}
|
||||
if _, err := chain.InsertReceiptChain([]*types.Block{block}, []types.Receipts{receipts}, 2^64-1); err != nil {
|
||||
return fmt.Errorf("error inserting body %d: %w", it.Number(), err)
|
||||
}
|
||||
imported += 1
|
||||
|
||||
// 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)))
|
||||
imported = 0
|
||||
reported = time.Now()
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func missingBlocks(chain *core.BlockChain, blocks []*types.Block) []*types.Block {
|
||||
head := chain.CurrentBlock()
|
||||
for i, block := range blocks {
|
||||
|
|
@ -297,6 +401,93 @@ func ExportAppendChain(blockchain *core.BlockChain, fn string, first uint64, las
|
|||
return nil
|
||||
}
|
||||
|
||||
// ExportHistory exports blockchain history into the specified directory,
|
||||
// following the Era format.
|
||||
func ExportHistory(bc *core.BlockChain, dir string, first, last, step uint64) 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)
|
||||
last = head
|
||||
}
|
||||
network := "unknown"
|
||||
if name, ok := params.NetworkNames[bc.Config().ChainID.String()]; ok {
|
||||
network = name
|
||||
}
|
||||
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)
|
||||
checksums []string
|
||||
)
|
||||
for i := first; i <= last; i += step {
|
||||
err := func() error {
|
||||
filename := path.Join(dir, era.Filename(network, int(i/step), common.Hash{}))
|
||||
f, err := os.Create(filename)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not create era file: %w", 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)
|
||||
)
|
||||
if block == nil {
|
||||
return fmt.Errorf("export failed on #%d: not found", n)
|
||||
}
|
||||
receipts := bc.GetReceiptsByHash(block.Hash())
|
||||
if receipts == nil {
|
||||
return fmt.Errorf("export failed on #%d: receipts not found", n)
|
||||
}
|
||||
td := bc.GetTd(block.Hash(), block.NumberU64())
|
||||
if td == nil {
|
||||
return fmt.Errorf("export failed on #%d: total difficulty not found", n)
|
||||
}
|
||||
if err := w.Add(block, receipts, td); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
root, err := w.Finalize()
|
||||
if err != nil {
|
||||
return fmt.Errorf("export failed to finalize %d: %w", step/i, err)
|
||||
}
|
||||
// Set correct filename with root.
|
||||
os.Rename(filename, path.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 {
|
||||
return err
|
||||
}
|
||||
if time.Since(reported) >= 8*time.Second {
|
||||
log.Info("Exporting blocks", "exported", i, "elapsed", common.PrettyDuration(time.Since(start)))
|
||||
reported = time.Now()
|
||||
}
|
||||
}
|
||||
|
||||
os.WriteFile(path.Join(dir, "checksums.txt"), []byte(strings.Join(checksums, "\n")), os.ModePerm)
|
||||
|
||||
log.Info("Exported blockchain to", "dir", dir)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ImportPreimages imports a batch of exported hash preimages into the database.
|
||||
// It's a part of the deprecated functionality, should be removed in the future.
|
||||
func ImportPreimages(db ethdb.Database, fn string) error {
|
||||
|
|
|
|||
184
cmd/utils/history_test.go
Normal file
184
cmd/utils/history_test.go
Normal file
|
|
@ -0,0 +1,184 @@
|
|||
// Copyright 2023 The go-ethereum Authors
|
||||
// This file is part of go-ethereum.
|
||||
//
|
||||
// go-ethereum is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// go-ethereum is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package utils
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/sha256"
|
||||
"io"
|
||||
"math/big"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/consensus/ethash"
|
||||
"github.com/ethereum/go-ethereum/core"
|
||||
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/core/vm"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/internal/era"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"github.com/ethereum/go-ethereum/trie"
|
||||
)
|
||||
|
||||
var (
|
||||
count uint64 = 128
|
||||
step uint64 = 16
|
||||
)
|
||||
|
||||
func TestHistoryImportAndExport(t *testing.T) {
|
||||
var (
|
||||
key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
|
||||
address = crypto.PubkeyToAddress(key.PublicKey)
|
||||
genesis = &core.Genesis{
|
||||
Config: params.TestChainConfig,
|
||||
Alloc: core.GenesisAlloc{address: {Balance: big.NewInt(1000000000000000000)}},
|
||||
}
|
||||
signer = types.LatestSigner(genesis.Config)
|
||||
)
|
||||
|
||||
// Generate chain.
|
||||
db, blocks, _ := core.GenerateChainWithGenesis(genesis, ethash.NewFaker(), int(count), func(i int, g *core.BlockGen) {
|
||||
if i == 0 {
|
||||
return
|
||||
}
|
||||
tx, err := types.SignNewTx(key, signer, &types.DynamicFeeTx{
|
||||
ChainID: genesis.Config.ChainID,
|
||||
Nonce: uint64(i - 1),
|
||||
GasTipCap: common.Big0,
|
||||
GasFeeCap: g.PrevBlock(0).BaseFee(),
|
||||
Gas: 50000,
|
||||
To: &common.Address{0xaa},
|
||||
Value: big.NewInt(int64(i)),
|
||||
Data: nil,
|
||||
AccessList: nil,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("error creating tx: %v", err)
|
||||
}
|
||||
g.AddTx(tx)
|
||||
})
|
||||
|
||||
// Initialize BlockChain.
|
||||
chain, err := core.NewBlockChain(db, nil, genesis, nil, ethash.NewFaker(), vm.Config{}, nil, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to initialize chain: %v", err)
|
||||
}
|
||||
if _, err := chain.InsertChain(blocks); err != nil {
|
||||
t.Fatalf("error insterting chain: %v", err)
|
||||
}
|
||||
|
||||
// Make temp directory for era files.
|
||||
dir, err := os.MkdirTemp("", "history-export-test")
|
||||
if err != nil {
|
||||
t.Fatalf("error creating temp test directory: %v", err)
|
||||
}
|
||||
defer os.RemoveAll(dir)
|
||||
|
||||
// Export history to temp directory.
|
||||
if err := ExportHistory(chain, dir, 0, count, step); err != nil {
|
||||
t.Fatalf("error exporting history: %v", err)
|
||||
}
|
||||
|
||||
// Read checksums.
|
||||
b, err := os.ReadFile(path.Join(dir, "checksums.txt"))
|
||||
if err != nil {
|
||||
t.Fatalf("failed to read checksums: %v", err)
|
||||
}
|
||||
checksums := strings.Split(string(b), "\n")
|
||||
|
||||
// Verify each Era.
|
||||
entries, _ := era.ReadDir(dir, "mainnet")
|
||||
for i, filename := range entries {
|
||||
func() {
|
||||
f, err := os.Open(path.Join(dir, filename))
|
||||
if err != nil {
|
||||
t.Fatalf("error opening era file: %v", err)
|
||||
}
|
||||
var (
|
||||
h = sha256.New()
|
||||
buf = bytes.NewBuffer(nil)
|
||||
)
|
||||
if _, err := io.Copy(h, f); err != nil {
|
||||
t.Fatalf("unable to recalculate checksum: %v", err)
|
||||
}
|
||||
if got, want := common.BytesToHash(h.Sum(buf.Bytes()[:])).Hex(), checksums[i]; got != want {
|
||||
t.Fatalf("checksum %d does not match: got %s, want %s", i, got, want)
|
||||
}
|
||||
e, err := era.From(f)
|
||||
if err != nil {
|
||||
t.Fatalf("error opening era: %v", err)
|
||||
}
|
||||
defer e.Close()
|
||||
it, err := era.NewIterator(e)
|
||||
if err != nil {
|
||||
t.Fatalf("error making era reader: %v", err)
|
||||
}
|
||||
for j := 0; it.Next(); j++ {
|
||||
n := i*int(step) + j
|
||||
if it.Error() != nil {
|
||||
t.Fatalf("error reading block entry %d: %v", n, it.Error())
|
||||
}
|
||||
block, receipts, err := it.BlockAndReceipts()
|
||||
if err != nil {
|
||||
t.Fatalf("error reading block entry %d: %v", n, err)
|
||||
}
|
||||
want := chain.GetBlockByNumber(uint64(n))
|
||||
if want, got := uint64(n), block.NumberU64(); want != got {
|
||||
t.Fatalf("blocks out of order: want %d, got %d", want, got)
|
||||
}
|
||||
if want.Hash() != block.Hash() {
|
||||
t.Fatalf("block hash mismatch %d: want %s, got %s", n, want.Hash().Hex(), block.Hash().Hex())
|
||||
}
|
||||
if got := types.DeriveSha(block.Transactions(), trie.NewStackTrie(nil)); got != want.TxHash() {
|
||||
t.Fatalf("tx hash %d mismatch: want %s, got %s", n, want.TxHash(), got)
|
||||
}
|
||||
if got := types.CalcUncleHash(block.Uncles()); got != want.UncleHash() {
|
||||
t.Fatalf("uncle hash %d mismatch: want %s, got %s", n, want.UncleHash(), got)
|
||||
}
|
||||
if got := types.DeriveSha(receipts, trie.NewStackTrie(nil)); got != want.ReceiptHash() {
|
||||
t.Fatalf("receipt root %d mismatch: want %s, got %s", n, want.ReceiptHash(), got)
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// Now import Era.
|
||||
freezer := t.TempDir()
|
||||
db2, err := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), freezer, "", false)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
t.Cleanup(func() {
|
||||
db2.Close()
|
||||
})
|
||||
|
||||
genesis.MustCommit(db2, trie.NewDatabase(db, trie.HashDefaults))
|
||||
imported, err := core.NewBlockChain(db2, nil, genesis, nil, ethash.NewFaker(), vm.Config{}, nil, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to initialize chain: %v", err)
|
||||
}
|
||||
if err := ImportHistory(imported, db2, dir, "mainnet"); 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())
|
||||
}
|
||||
}
|
||||
|
|
@ -410,6 +410,11 @@ func (bc *BlockChain) TrieDB() *trie.Database {
|
|||
return bc.triedb
|
||||
}
|
||||
|
||||
// HeaderChain returns the underlying header chain.
|
||||
func (bc *BlockChain) HeaderChain() *HeaderChain {
|
||||
return bc.hc
|
||||
}
|
||||
|
||||
// SubscribeRemovedLogsEvent registers a subscription of RemovedLogsEvent.
|
||||
func (bc *BlockChain) SubscribeRemovedLogsEvent(ch chan<- RemovedLogsEvent) event.Subscription {
|
||||
return bc.scope.Track(bc.rmLogsFeed.Subscribe(ch))
|
||||
|
|
|
|||
|
|
@ -83,7 +83,7 @@ func (b *BlockGen) SetDifficulty(diff *big.Int) {
|
|||
b.header.Difficulty = diff
|
||||
}
|
||||
|
||||
// SetPos makes the header a PoS-header (0 difficulty)
|
||||
// SetPoS makes the header a PoS-header (0 difficulty)
|
||||
func (b *BlockGen) SetPoS() {
|
||||
b.header.Difficulty = new(big.Int)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -74,8 +74,10 @@ func TestCreation(t *testing.T) {
|
|||
{15049999, 0, ID{Hash: checksumToBytes(0x20c327fc), Next: 15050000}}, // Last Arrow Glacier block
|
||||
{15050000, 0, ID{Hash: checksumToBytes(0xf0afd0e3), Next: 1681338455}}, // First Gray Glacier block
|
||||
{20000000, 1681338454, ID{Hash: checksumToBytes(0xf0afd0e3), Next: 1681338455}}, // Last Gray Glacier block
|
||||
{20000000, 1681338455, ID{Hash: checksumToBytes(0xdce96c2d), Next: 0}}, // First Shanghai block
|
||||
{30000000, 2000000000, ID{Hash: checksumToBytes(0xdce96c2d), Next: 0}}, // Future Shanghai block
|
||||
{20000000, 1681338455, ID{Hash: checksumToBytes(0xdce96c2d), Next: 1710338135}}, // First Shanghai block
|
||||
{30000000, 1710338134, ID{Hash: checksumToBytes(0xdce96c2d), Next: 1710338135}}, // Last Shanghai block
|
||||
{40000000, 1710338135, ID{Hash: checksumToBytes(0x9f3d2254), Next: 0}}, // First Cancun block
|
||||
{50000000, 2000000000, ID{Hash: checksumToBytes(0x9f3d2254), Next: 0}}, // Future Cancun block
|
||||
},
|
||||
},
|
||||
// Goerli test cases
|
||||
|
|
@ -141,6 +143,7 @@ func TestValidation(t *testing.T) {
|
|||
// Config that has not timestamp enabled
|
||||
legacyConfig := *params.MainnetChainConfig
|
||||
legacyConfig.ShanghaiTime = nil
|
||||
legacyConfig.CancunTime = nil
|
||||
|
||||
tests := []struct {
|
||||
config *params.ChainConfig
|
||||
|
|
@ -213,14 +216,10 @@ func TestValidation(t *testing.T) {
|
|||
// at some future block 88888888, for itself, but past block for local. Local is incompatible.
|
||||
//
|
||||
// This case detects non-upgraded nodes with majority hash power (typical Ropsten mess).
|
||||
//
|
||||
// TODO(karalabe): This testcase will fail once mainnet gets timestamped forks, make legacy chain config
|
||||
{&legacyConfig, 88888888, 0, ID{Hash: checksumToBytes(0xf0afd0e3), Next: 88888888}, ErrLocalIncompatibleOrStale},
|
||||
|
||||
// Local is mainnet Byzantium. Remote is also in Byzantium, but announces Gopherium (non existing
|
||||
// fork) at block 7279999, before Petersburg. Local is incompatible.
|
||||
//
|
||||
// TODO(karalabe): This testcase will fail once mainnet gets timestamped forks, make legacy chain config
|
||||
{&legacyConfig, 7279999, 0, ID{Hash: checksumToBytes(0xa00bc324), Next: 7279999}, ErrLocalIncompatibleOrStale},
|
||||
|
||||
//------------------------------------
|
||||
|
|
@ -297,34 +296,25 @@ func TestValidation(t *testing.T) {
|
|||
// Local is mainnet currently in Shanghai only (so it's aware of Cancun), remote announces
|
||||
// also Shanghai, but it's not yet aware of Cancun (e.g. non updated node before the fork).
|
||||
// In this case we don't know if Cancun passed yet or not.
|
||||
//
|
||||
// TODO(karalabe): Enable this when Cancun is specced
|
||||
//{params.MainnetChainConfig, 20000000, 1668000000, ID{Hash: checksumToBytes(0x71147644), Next: 0}, nil},
|
||||
{params.MainnetChainConfig, 20000000, 1668000000, ID{Hash: checksumToBytes(0xdce96c2d), Next: 0}, nil},
|
||||
|
||||
// Local is mainnet currently in Shanghai only (so it's aware of Cancun), remote announces
|
||||
// also Shanghai, and it's also aware of Cancun (e.g. updated node before the fork). We
|
||||
// don't know if Cancun passed yet (will pass) or not.
|
||||
//
|
||||
// TODO(karalabe): Enable this when Cancun is specced and update next timestamp
|
||||
//{params.MainnetChainConfig, 20000000, 1668000000, ID{Hash: checksumToBytes(0x71147644), Next: 1678000000}, nil},
|
||||
{params.MainnetChainConfig, 20000000, 1668000000, ID{Hash: checksumToBytes(0xdce96c2d), Next: 1710338135}, nil},
|
||||
|
||||
// Local is mainnet currently in Shanghai only (so it's aware of Cancun), remote announces
|
||||
// also Shanghai, and it's also aware of some random fork (e.g. misconfigured Cancun). As
|
||||
// neither forks passed at neither nodes, they may mismatch, but we still connect for now.
|
||||
//
|
||||
// TODO(karalabe): Enable this when Cancun is specced
|
||||
//{params.MainnetChainConfig, 20000000, 1668000000, ID{Hash: checksumToBytes(0x71147644), Next: math.MaxUint64}, nil},
|
||||
{params.MainnetChainConfig, 20000000, 1668000000, ID{Hash: checksumToBytes(0xdce96c2d), Next: math.MaxUint64}, nil},
|
||||
|
||||
// Local is mainnet exactly on Cancun, remote announces Shanghai + knowledge about Cancun. Remote
|
||||
// is simply out of sync, accept.
|
||||
//
|
||||
// TODO(karalabe): Enable this when Cancun is specced, update local head and time, next timestamp
|
||||
// {params.MainnetChainConfig, 21000000, 1678000000, ID{Hash: checksumToBytes(0x71147644), Next: 1678000000}, nil},
|
||||
{params.MainnetChainConfig, 21000000, 1710338135, ID{Hash: checksumToBytes(0xdce96c2d), Next: 1710338135}, nil},
|
||||
|
||||
// Local is mainnet Cancun, remote announces Shanghai + knowledge about Cancun. Remote
|
||||
// is simply out of sync, accept.
|
||||
// TODO(karalabe): Enable this when Cancun is specced, update local head and time, next timestamp
|
||||
//{params.MainnetChainConfig, 21123456, 1678123456, ID{Hash: checksumToBytes(0x71147644), Next: 1678000000}, nil},
|
||||
{params.MainnetChainConfig, 21123456, 1710338136, ID{Hash: checksumToBytes(0xdce96c2d), Next: 1710338135}, nil},
|
||||
|
||||
// Local is mainnet Prague, remote announces Shanghai + knowledge about Cancun. Remote
|
||||
// is definitely out of sync. It may or may not need the Prague update, we don't know yet.
|
||||
|
|
@ -333,9 +323,7 @@ func TestValidation(t *testing.T) {
|
|||
//{params.MainnetChainConfig, 0, 0, ID{Hash: checksumToBytes(0x3edd5b10), Next: 4370000}, nil},
|
||||
|
||||
// Local is mainnet Shanghai, remote announces Cancun. Local is out of sync, accept.
|
||||
//
|
||||
// TODO(karalabe): Enable this when Cancun is specced, update remote checksum
|
||||
//{params.MainnetChainConfig, 21000000, 1678000000, ID{Hash: checksumToBytes(0x00000000), Next: 0}, nil},
|
||||
{params.MainnetChainConfig, 21000000, 1700000000, ID{Hash: checksumToBytes(0x9f3d2254), Next: 0}, nil},
|
||||
|
||||
// Local is mainnet Shanghai, remote announces Cancun, but is not aware of Prague. Local
|
||||
// out of sync. Local also knows about a future fork, but that is uncertain yet.
|
||||
|
|
@ -345,9 +333,7 @@ func TestValidation(t *testing.T) {
|
|||
|
||||
// Local is mainnet Cancun. remote announces Shanghai but is not aware of further forks.
|
||||
// Remote needs software update.
|
||||
//
|
||||
// TODO(karalabe): Enable this when Cancun is specced, update local head and time
|
||||
//{params.MainnetChainConfig, 21000000, 1678000000, ID{Hash: checksumToBytes(0x71147644), Next: 0}, ErrRemoteStale},
|
||||
{params.MainnetChainConfig, 21000000, 1710338135, ID{Hash: checksumToBytes(0xdce96c2d), Next: 0}, ErrRemoteStale},
|
||||
|
||||
// Local is mainnet Shanghai, and isn't aware of more forks. Remote announces Shanghai +
|
||||
// 0xffffffff. Local needs software update, reject.
|
||||
|
|
@ -355,24 +341,20 @@ func TestValidation(t *testing.T) {
|
|||
|
||||
// Local is mainnet Shanghai, and is aware of Cancun. Remote announces Cancun +
|
||||
// 0xffffffff. Local needs software update, reject.
|
||||
//
|
||||
// TODO(karalabe): Enable this when Cancun is specced, update remote checksum
|
||||
//{params.MainnetChainConfig, 20000000, 1668000000, ID{Hash: checksumToBytes(checksumUpdate(0x00000000, math.MaxUint64)), Next: 0}, ErrLocalIncompatibleOrStale},
|
||||
{params.MainnetChainConfig, 20000000, 1668000000, ID{Hash: checksumToBytes(checksumUpdate(0x9f3d2254, math.MaxUint64)), Next: 0}, ErrLocalIncompatibleOrStale},
|
||||
|
||||
// Local is mainnet Shanghai, remote is random Shanghai.
|
||||
{params.MainnetChainConfig, 20000000, 1681338455, ID{Hash: checksumToBytes(0x12345678), Next: 0}, ErrLocalIncompatibleOrStale},
|
||||
|
||||
// Local is mainnet Shanghai, far in the future. Remote announces Gopherium (non existing fork)
|
||||
// Local is mainnet Cancun, far in the future. Remote announces Gopherium (non existing fork)
|
||||
// at some future timestamp 8888888888, for itself, but past block for local. Local is incompatible.
|
||||
//
|
||||
// This case detects non-upgraded nodes with majority hash power (typical Ropsten mess).
|
||||
{params.MainnetChainConfig, 88888888, 8888888888, ID{Hash: checksumToBytes(0xdce96c2d), Next: 8888888888}, ErrLocalIncompatibleOrStale},
|
||||
{params.MainnetChainConfig, 88888888, 8888888888, ID{Hash: checksumToBytes(0x9f3d2254), Next: 8888888888}, ErrLocalIncompatibleOrStale},
|
||||
|
||||
// Local is mainnet Shanghai. Remote is also in Shanghai, but announces Gopherium (non existing
|
||||
// fork) at timestamp 1668000000, before Cancun. Local is incompatible.
|
||||
//
|
||||
// TODO(karalabe): Enable this when Cancun is specced
|
||||
//{params.MainnetChainConfig, 20999999, 1677999999, ID{Hash: checksumToBytes(0x71147644), Next: 1678000000}, ErrLocalIncompatibleOrStale},
|
||||
{params.MainnetChainConfig, 20999999, 1699999999, ID{Hash: checksumToBytes(0x71147644), Next: 1700000000}, ErrLocalIncompatibleOrStale},
|
||||
}
|
||||
genesis := core.DefaultGenesisBlock().ToBlock()
|
||||
for i, tt := range tests {
|
||||
|
|
|
|||
|
|
@ -413,6 +413,8 @@ func (g *Genesis) configOrDefault(ghash common.Hash) *params.ChainConfig {
|
|||
return g.Config
|
||||
case ghash == params.MainnetGenesisHash:
|
||||
return params.MainnetChainConfig
|
||||
case ghash == params.HoleskyGenesisHash:
|
||||
return params.HoleskyChainConfig
|
||||
case ghash == params.SepoliaGenesisHash:
|
||||
return params.SepoliaChainConfig
|
||||
case ghash == params.GoerliGenesisHash:
|
||||
|
|
|
|||
|
|
@ -93,7 +93,7 @@ type stateObject struct {
|
|||
|
||||
// empty returns whether the account is considered empty.
|
||||
func (s *stateObject) empty() bool {
|
||||
return s.data.Nonce == 0 && s.data.Balance.Sign() == 0 && bytes.Equal(s.data.CodeHash, types.EmptyCodeHash.Bytes())
|
||||
return s.data.Nonce == 0 && s.data.Balance.IsZero() && bytes.Equal(s.data.CodeHash, types.EmptyCodeHash.Bytes())
|
||||
}
|
||||
|
||||
// newObject creates a state object.
|
||||
|
|
@ -408,7 +408,7 @@ func (s *stateObject) commit() (*trienode.NodeSet, error) {
|
|||
func (s *stateObject) AddBalance(amount *uint256.Int) {
|
||||
// EIP161: We must check emptiness for the objects such that the account
|
||||
// clearing (0,0,0 objects) can take effect.
|
||||
if amount.Sign() == 0 {
|
||||
if amount.IsZero() {
|
||||
if s.empty() {
|
||||
s.touch()
|
||||
}
|
||||
|
|
@ -420,7 +420,7 @@ func (s *stateObject) AddBalance(amount *uint256.Int) {
|
|||
// SubBalance removes amount from s's balance.
|
||||
// It is used to remove funds from the origin account of a transfer.
|
||||
func (s *stateObject) SubBalance(amount *uint256.Int) {
|
||||
if amount.Sign() == 0 {
|
||||
if amount.IsZero() {
|
||||
return
|
||||
}
|
||||
s.SetBalance(new(uint256.Int).Sub(s.Balance(), amount))
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ import (
|
|||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/ethereum/go-ethereum/crypto/kzg4844"
|
||||
"github.com/holiman/uint256"
|
||||
)
|
||||
|
||||
|
|
@ -47,6 +48,11 @@ type txJSON struct {
|
|||
S *hexutil.Big `json:"s"`
|
||||
YParity *hexutil.Uint64 `json:"yParity,omitempty"`
|
||||
|
||||
// Blob transaction sidecar encoding:
|
||||
Blobs []kzg4844.Blob `json:"blobs,omitempty"`
|
||||
Commitments []kzg4844.Commitment `json:"commitments,omitempty"`
|
||||
Proofs []kzg4844.Proof `json:"proofs,omitempty"`
|
||||
|
||||
// Only used for encoding:
|
||||
Hash common.Hash `json:"hash"`
|
||||
}
|
||||
|
|
@ -142,6 +148,11 @@ func (tx *Transaction) MarshalJSON() ([]byte, error) {
|
|||
enc.S = (*hexutil.Big)(itx.S.ToBig())
|
||||
yparity := itx.V.Uint64()
|
||||
enc.YParity = (*hexutil.Uint64)(&yparity)
|
||||
if sidecar := itx.Sidecar; sidecar != nil {
|
||||
enc.Blobs = itx.Sidecar.Blobs
|
||||
enc.Commitments = itx.Sidecar.Commitments
|
||||
enc.Proofs = itx.Sidecar.Proofs
|
||||
}
|
||||
}
|
||||
return json.Marshal(&enc)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -182,7 +182,7 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas
|
|||
return nil, gas, ErrDepth
|
||||
}
|
||||
// Fail if we're trying to transfer more than the available balance
|
||||
if value.Sign() != 0 && !evm.Context.CanTransfer(evm.StateDB, caller.Address(), value) {
|
||||
if !value.IsZero() && !evm.Context.CanTransfer(evm.StateDB, caller.Address(), value) {
|
||||
return nil, gas, ErrInsufficientBalance
|
||||
}
|
||||
snapshot := evm.StateDB.Snapshot()
|
||||
|
|
@ -190,7 +190,7 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas
|
|||
debug := evm.Config.Tracer != nil
|
||||
|
||||
if !evm.StateDB.Exist(addr) {
|
||||
if !isPrecompile && evm.chainRules.IsEIP158 && value.Sign() == 0 {
|
||||
if !isPrecompile && evm.chainRules.IsEIP158 && value.IsZero() {
|
||||
// Calling a non existing account, don't do anything, but ping the tracer
|
||||
if debug {
|
||||
if evm.depth == 0 {
|
||||
|
|
|
|||
|
|
@ -347,9 +347,7 @@ func opExtCodeSize(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext)
|
|||
}
|
||||
|
||||
func opCodeSize(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
||||
l := new(uint256.Int)
|
||||
l.SetUint64(uint64(len(scope.Contract.Code)))
|
||||
scope.Stack.push(l)
|
||||
scope.Stack.push(new(uint256.Int).SetUint64(uint64(len(scope.Contract.Code))))
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -166,7 +166,7 @@ type G2 struct {
|
|||
p *twistPoint
|
||||
}
|
||||
|
||||
// RandomG1 returns x and g₂ˣ where x is a random, non-zero number read from r.
|
||||
// RandomG2 returns x and g₂ˣ where x is a random, non-zero number read from r.
|
||||
func RandomG2(r io.Reader) (*big.Int, *G2, error) {
|
||||
var k *big.Int
|
||||
var err error
|
||||
|
|
|
|||
|
|
@ -21,21 +21,60 @@ import (
|
|||
"embed"
|
||||
"errors"
|
||||
"hash"
|
||||
"reflect"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
)
|
||||
|
||||
//go:embed trusted_setup.json
|
||||
var content embed.FS
|
||||
|
||||
var (
|
||||
blobT = reflect.TypeOf(Blob{})
|
||||
commitmentT = reflect.TypeOf(Commitment{})
|
||||
proofT = reflect.TypeOf(Proof{})
|
||||
)
|
||||
|
||||
// Blob represents a 4844 data blob.
|
||||
type Blob [131072]byte
|
||||
|
||||
// UnmarshalJSON parses a blob in hex syntax.
|
||||
func (b *Blob) UnmarshalJSON(input []byte) error {
|
||||
return hexutil.UnmarshalFixedJSON(blobT, input, b[:])
|
||||
}
|
||||
|
||||
// MarshalText returns the hex representation of b.
|
||||
func (b Blob) MarshalText() ([]byte, error) {
|
||||
return hexutil.Bytes(b[:]).MarshalText()
|
||||
}
|
||||
|
||||
// Commitment is a serialized commitment to a polynomial.
|
||||
type Commitment [48]byte
|
||||
|
||||
// UnmarshalJSON parses a commitment in hex syntax.
|
||||
func (c *Commitment) UnmarshalJSON(input []byte) error {
|
||||
return hexutil.UnmarshalFixedJSON(commitmentT, input, c[:])
|
||||
}
|
||||
|
||||
// MarshalText returns the hex representation of c.
|
||||
func (c Commitment) MarshalText() ([]byte, error) {
|
||||
return hexutil.Bytes(c[:]).MarshalText()
|
||||
}
|
||||
|
||||
// Proof is a serialized commitment to the quotient polynomial.
|
||||
type Proof [48]byte
|
||||
|
||||
// UnmarshalJSON parses a proof in hex syntax.
|
||||
func (p *Proof) UnmarshalJSON(input []byte) error {
|
||||
return hexutil.UnmarshalFixedJSON(proofT, input, p[:])
|
||||
}
|
||||
|
||||
// MarshalText returns the hex representation of p.
|
||||
func (p Proof) MarshalText() ([]byte, error) {
|
||||
return hexutil.Bytes(p[:]).MarshalText()
|
||||
}
|
||||
|
||||
// Point is a BLS field element.
|
||||
type Point [32]byte
|
||||
|
||||
|
|
|
|||
|
|
@ -440,9 +440,6 @@ func assertOwnChain(t *testing.T, tester *downloadTester, length int) {
|
|||
func TestCanonicalSynchronisation68Full(t *testing.T) { testCanonSync(t, eth.ETH68, FullSync) }
|
||||
func TestCanonicalSynchronisation68Snap(t *testing.T) { testCanonSync(t, eth.ETH68, SnapSync) }
|
||||
func TestCanonicalSynchronisation68Light(t *testing.T) { testCanonSync(t, eth.ETH68, LightSync) }
|
||||
func TestCanonicalSynchronisation67Full(t *testing.T) { testCanonSync(t, eth.ETH67, FullSync) }
|
||||
func TestCanonicalSynchronisation67Snap(t *testing.T) { testCanonSync(t, eth.ETH67, SnapSync) }
|
||||
func TestCanonicalSynchronisation67Light(t *testing.T) { testCanonSync(t, eth.ETH67, LightSync) }
|
||||
|
||||
func testCanonSync(t *testing.T, protocol uint, mode SyncMode) {
|
||||
tester := newTester(t)
|
||||
|
|
@ -463,8 +460,6 @@ func testCanonSync(t *testing.T, protocol uint, mode SyncMode) {
|
|||
// until the cached blocks are retrieved.
|
||||
func TestThrottling68Full(t *testing.T) { testThrottling(t, eth.ETH68, FullSync) }
|
||||
func TestThrottling68Snap(t *testing.T) { testThrottling(t, eth.ETH68, SnapSync) }
|
||||
func TestThrottling67Full(t *testing.T) { testThrottling(t, eth.ETH67, FullSync) }
|
||||
func TestThrottling67Snap(t *testing.T) { testThrottling(t, eth.ETH67, SnapSync) }
|
||||
|
||||
func testThrottling(t *testing.T, protocol uint, mode SyncMode) {
|
||||
tester := newTester(t)
|
||||
|
|
@ -546,9 +541,6 @@ func testThrottling(t *testing.T, protocol uint, mode SyncMode) {
|
|||
func TestForkedSync68Full(t *testing.T) { testForkedSync(t, eth.ETH68, FullSync) }
|
||||
func TestForkedSync68Snap(t *testing.T) { testForkedSync(t, eth.ETH68, SnapSync) }
|
||||
func TestForkedSync68Light(t *testing.T) { testForkedSync(t, eth.ETH68, LightSync) }
|
||||
func TestForkedSync67Full(t *testing.T) { testForkedSync(t, eth.ETH67, FullSync) }
|
||||
func TestForkedSync67Snap(t *testing.T) { testForkedSync(t, eth.ETH67, SnapSync) }
|
||||
func TestForkedSync67Light(t *testing.T) { testForkedSync(t, eth.ETH67, LightSync) }
|
||||
|
||||
func testForkedSync(t *testing.T, protocol uint, mode SyncMode) {
|
||||
tester := newTester(t)
|
||||
|
|
@ -576,9 +568,6 @@ func testForkedSync(t *testing.T, protocol uint, mode SyncMode) {
|
|||
func TestHeavyForkedSync68Full(t *testing.T) { testHeavyForkedSync(t, eth.ETH68, FullSync) }
|
||||
func TestHeavyForkedSync68Snap(t *testing.T) { testHeavyForkedSync(t, eth.ETH68, SnapSync) }
|
||||
func TestHeavyForkedSync68Light(t *testing.T) { testHeavyForkedSync(t, eth.ETH68, LightSync) }
|
||||
func TestHeavyForkedSync67Full(t *testing.T) { testHeavyForkedSync(t, eth.ETH67, FullSync) }
|
||||
func TestHeavyForkedSync67Snap(t *testing.T) { testHeavyForkedSync(t, eth.ETH67, SnapSync) }
|
||||
func TestHeavyForkedSync67Light(t *testing.T) { testHeavyForkedSync(t, eth.ETH67, LightSync) }
|
||||
|
||||
func testHeavyForkedSync(t *testing.T, protocol uint, mode SyncMode) {
|
||||
tester := newTester(t)
|
||||
|
|
@ -608,9 +597,6 @@ func testHeavyForkedSync(t *testing.T, protocol uint, mode SyncMode) {
|
|||
func TestBoundedForkedSync68Full(t *testing.T) { testBoundedForkedSync(t, eth.ETH68, FullSync) }
|
||||
func TestBoundedForkedSync68Snap(t *testing.T) { testBoundedForkedSync(t, eth.ETH68, SnapSync) }
|
||||
func TestBoundedForkedSync68Light(t *testing.T) { testBoundedForkedSync(t, eth.ETH68, LightSync) }
|
||||
func TestBoundedForkedSync67Full(t *testing.T) { testBoundedForkedSync(t, eth.ETH67, FullSync) }
|
||||
func TestBoundedForkedSync67Snap(t *testing.T) { testBoundedForkedSync(t, eth.ETH67, SnapSync) }
|
||||
func TestBoundedForkedSync67Light(t *testing.T) { testBoundedForkedSync(t, eth.ETH67, LightSync) }
|
||||
|
||||
func testBoundedForkedSync(t *testing.T, protocol uint, mode SyncMode) {
|
||||
tester := newTester(t)
|
||||
|
|
@ -645,15 +631,6 @@ func TestBoundedHeavyForkedSync68Snap(t *testing.T) {
|
|||
func TestBoundedHeavyForkedSync68Light(t *testing.T) {
|
||||
testBoundedHeavyForkedSync(t, eth.ETH68, LightSync)
|
||||
}
|
||||
func TestBoundedHeavyForkedSync67Full(t *testing.T) {
|
||||
testBoundedHeavyForkedSync(t, eth.ETH67, FullSync)
|
||||
}
|
||||
func TestBoundedHeavyForkedSync67Snap(t *testing.T) {
|
||||
testBoundedHeavyForkedSync(t, eth.ETH67, SnapSync)
|
||||
}
|
||||
func TestBoundedHeavyForkedSync67Light(t *testing.T) {
|
||||
testBoundedHeavyForkedSync(t, eth.ETH67, LightSync)
|
||||
}
|
||||
|
||||
func testBoundedHeavyForkedSync(t *testing.T, protocol uint, mode SyncMode) {
|
||||
tester := newTester(t)
|
||||
|
|
@ -681,9 +658,6 @@ func testBoundedHeavyForkedSync(t *testing.T, protocol uint, mode SyncMode) {
|
|||
func TestCancel68Full(t *testing.T) { testCancel(t, eth.ETH68, FullSync) }
|
||||
func TestCancel68Snap(t *testing.T) { testCancel(t, eth.ETH68, SnapSync) }
|
||||
func TestCancel68Light(t *testing.T) { testCancel(t, eth.ETH68, LightSync) }
|
||||
func TestCancel67Full(t *testing.T) { testCancel(t, eth.ETH67, FullSync) }
|
||||
func TestCancel67Snap(t *testing.T) { testCancel(t, eth.ETH67, SnapSync) }
|
||||
func TestCancel67Light(t *testing.T) { testCancel(t, eth.ETH67, LightSync) }
|
||||
|
||||
func testCancel(t *testing.T, protocol uint, mode SyncMode) {
|
||||
tester := newTester(t)
|
||||
|
|
@ -711,9 +685,6 @@ func testCancel(t *testing.T, protocol uint, mode SyncMode) {
|
|||
func TestMultiSynchronisation68Full(t *testing.T) { testMultiSynchronisation(t, eth.ETH68, FullSync) }
|
||||
func TestMultiSynchronisation68Snap(t *testing.T) { testMultiSynchronisation(t, eth.ETH68, SnapSync) }
|
||||
func TestMultiSynchronisation68Light(t *testing.T) { testMultiSynchronisation(t, eth.ETH68, LightSync) }
|
||||
func TestMultiSynchronisation67Full(t *testing.T) { testMultiSynchronisation(t, eth.ETH67, FullSync) }
|
||||
func TestMultiSynchronisation67Snap(t *testing.T) { testMultiSynchronisation(t, eth.ETH67, SnapSync) }
|
||||
func TestMultiSynchronisation67Light(t *testing.T) { testMultiSynchronisation(t, eth.ETH67, LightSync) }
|
||||
|
||||
func testMultiSynchronisation(t *testing.T, protocol uint, mode SyncMode) {
|
||||
tester := newTester(t)
|
||||
|
|
@ -738,9 +709,6 @@ func testMultiSynchronisation(t *testing.T, protocol uint, mode SyncMode) {
|
|||
func TestMultiProtoSynchronisation68Full(t *testing.T) { testMultiProtoSync(t, eth.ETH68, FullSync) }
|
||||
func TestMultiProtoSynchronisation68Snap(t *testing.T) { testMultiProtoSync(t, eth.ETH68, SnapSync) }
|
||||
func TestMultiProtoSynchronisation68Light(t *testing.T) { testMultiProtoSync(t, eth.ETH68, LightSync) }
|
||||
func TestMultiProtoSynchronisation67Full(t *testing.T) { testMultiProtoSync(t, eth.ETH67, FullSync) }
|
||||
func TestMultiProtoSynchronisation67Snap(t *testing.T) { testMultiProtoSync(t, eth.ETH67, SnapSync) }
|
||||
func TestMultiProtoSynchronisation67Light(t *testing.T) { testMultiProtoSync(t, eth.ETH67, LightSync) }
|
||||
|
||||
func testMultiProtoSync(t *testing.T, protocol uint, mode SyncMode) {
|
||||
tester := newTester(t)
|
||||
|
|
@ -751,7 +719,6 @@ func testMultiProtoSync(t *testing.T, protocol uint, mode SyncMode) {
|
|||
|
||||
// Create peers of every type
|
||||
tester.newPeer("peer 68", eth.ETH68, chain.blocks[1:])
|
||||
tester.newPeer("peer 67", eth.ETH67, chain.blocks[1:])
|
||||
|
||||
// Synchronise with the requested peer and make sure all blocks were retrieved
|
||||
if err := tester.sync(fmt.Sprintf("peer %d", protocol), nil, mode); err != nil {
|
||||
|
|
@ -760,7 +727,7 @@ func testMultiProtoSync(t *testing.T, protocol uint, mode SyncMode) {
|
|||
assertOwnChain(t, tester, len(chain.blocks))
|
||||
|
||||
// Check that no peers have been dropped off
|
||||
for _, version := range []int{68, 67} {
|
||||
for _, version := range []int{68} {
|
||||
peer := fmt.Sprintf("peer %d", version)
|
||||
if _, ok := tester.peers[peer]; !ok {
|
||||
t.Errorf("%s dropped", peer)
|
||||
|
|
@ -773,9 +740,6 @@ func testMultiProtoSync(t *testing.T, protocol uint, mode SyncMode) {
|
|||
func TestEmptyShortCircuit68Full(t *testing.T) { testEmptyShortCircuit(t, eth.ETH68, FullSync) }
|
||||
func TestEmptyShortCircuit68Snap(t *testing.T) { testEmptyShortCircuit(t, eth.ETH68, SnapSync) }
|
||||
func TestEmptyShortCircuit68Light(t *testing.T) { testEmptyShortCircuit(t, eth.ETH68, LightSync) }
|
||||
func TestEmptyShortCircuit67Full(t *testing.T) { testEmptyShortCircuit(t, eth.ETH67, FullSync) }
|
||||
func TestEmptyShortCircuit67Snap(t *testing.T) { testEmptyShortCircuit(t, eth.ETH67, SnapSync) }
|
||||
func TestEmptyShortCircuit67Light(t *testing.T) { testEmptyShortCircuit(t, eth.ETH67, LightSync) }
|
||||
|
||||
func testEmptyShortCircuit(t *testing.T, protocol uint, mode SyncMode) {
|
||||
tester := newTester(t)
|
||||
|
|
@ -824,9 +788,6 @@ func testEmptyShortCircuit(t *testing.T, protocol uint, mode SyncMode) {
|
|||
func TestMissingHeaderAttack68Full(t *testing.T) { testMissingHeaderAttack(t, eth.ETH68, FullSync) }
|
||||
func TestMissingHeaderAttack68Snap(t *testing.T) { testMissingHeaderAttack(t, eth.ETH68, SnapSync) }
|
||||
func TestMissingHeaderAttack68Light(t *testing.T) { testMissingHeaderAttack(t, eth.ETH68, LightSync) }
|
||||
func TestMissingHeaderAttack67Full(t *testing.T) { testMissingHeaderAttack(t, eth.ETH67, FullSync) }
|
||||
func TestMissingHeaderAttack67Snap(t *testing.T) { testMissingHeaderAttack(t, eth.ETH67, SnapSync) }
|
||||
func TestMissingHeaderAttack67Light(t *testing.T) { testMissingHeaderAttack(t, eth.ETH67, LightSync) }
|
||||
|
||||
func testMissingHeaderAttack(t *testing.T, protocol uint, mode SyncMode) {
|
||||
tester := newTester(t)
|
||||
|
|
@ -853,9 +814,6 @@ func testMissingHeaderAttack(t *testing.T, protocol uint, mode SyncMode) {
|
|||
func TestShiftedHeaderAttack68Full(t *testing.T) { testShiftedHeaderAttack(t, eth.ETH68, FullSync) }
|
||||
func TestShiftedHeaderAttack68Snap(t *testing.T) { testShiftedHeaderAttack(t, eth.ETH68, SnapSync) }
|
||||
func TestShiftedHeaderAttack68Light(t *testing.T) { testShiftedHeaderAttack(t, eth.ETH68, LightSync) }
|
||||
func TestShiftedHeaderAttack67Full(t *testing.T) { testShiftedHeaderAttack(t, eth.ETH67, FullSync) }
|
||||
func TestShiftedHeaderAttack67Snap(t *testing.T) { testShiftedHeaderAttack(t, eth.ETH67, SnapSync) }
|
||||
func TestShiftedHeaderAttack67Light(t *testing.T) { testShiftedHeaderAttack(t, eth.ETH67, LightSync) }
|
||||
|
||||
func testShiftedHeaderAttack(t *testing.T, protocol uint, mode SyncMode) {
|
||||
tester := newTester(t)
|
||||
|
|
@ -889,15 +847,6 @@ func TestHighTDStarvationAttack68Snap(t *testing.T) {
|
|||
func TestHighTDStarvationAttack68Light(t *testing.T) {
|
||||
testHighTDStarvationAttack(t, eth.ETH68, LightSync)
|
||||
}
|
||||
func TestHighTDStarvationAttack67Full(t *testing.T) {
|
||||
testHighTDStarvationAttack(t, eth.ETH67, FullSync)
|
||||
}
|
||||
func TestHighTDStarvationAttack67Snap(t *testing.T) {
|
||||
testHighTDStarvationAttack(t, eth.ETH67, SnapSync)
|
||||
}
|
||||
func TestHighTDStarvationAttack67Light(t *testing.T) {
|
||||
testHighTDStarvationAttack(t, eth.ETH67, LightSync)
|
||||
}
|
||||
|
||||
func testHighTDStarvationAttack(t *testing.T, protocol uint, mode SyncMode) {
|
||||
tester := newTester(t)
|
||||
|
|
@ -912,7 +861,6 @@ func testHighTDStarvationAttack(t *testing.T, protocol uint, mode SyncMode) {
|
|||
|
||||
// Tests that misbehaving peers are disconnected, whilst behaving ones are not.
|
||||
func TestBlockHeaderAttackerDropping68(t *testing.T) { testBlockHeaderAttackerDropping(t, eth.ETH68) }
|
||||
func TestBlockHeaderAttackerDropping67(t *testing.T) { testBlockHeaderAttackerDropping(t, eth.ETH67) }
|
||||
|
||||
func testBlockHeaderAttackerDropping(t *testing.T, protocol uint) {
|
||||
// Define the disconnection requirement for individual hash fetch errors
|
||||
|
|
@ -963,9 +911,6 @@ func testBlockHeaderAttackerDropping(t *testing.T, protocol uint) {
|
|||
func TestSyncProgress68Full(t *testing.T) { testSyncProgress(t, eth.ETH68, FullSync) }
|
||||
func TestSyncProgress68Snap(t *testing.T) { testSyncProgress(t, eth.ETH68, SnapSync) }
|
||||
func TestSyncProgress68Light(t *testing.T) { testSyncProgress(t, eth.ETH68, LightSync) }
|
||||
func TestSyncProgress67Full(t *testing.T) { testSyncProgress(t, eth.ETH67, FullSync) }
|
||||
func TestSyncProgress67Snap(t *testing.T) { testSyncProgress(t, eth.ETH67, SnapSync) }
|
||||
func TestSyncProgress67Light(t *testing.T) { testSyncProgress(t, eth.ETH67, LightSync) }
|
||||
|
||||
func testSyncProgress(t *testing.T, protocol uint, mode SyncMode) {
|
||||
tester := newTester(t)
|
||||
|
|
@ -1043,9 +988,6 @@ func checkProgress(t *testing.T, d *Downloader, stage string, want ethereum.Sync
|
|||
func TestForkedSyncProgress68Full(t *testing.T) { testForkedSyncProgress(t, eth.ETH68, FullSync) }
|
||||
func TestForkedSyncProgress68Snap(t *testing.T) { testForkedSyncProgress(t, eth.ETH68, SnapSync) }
|
||||
func TestForkedSyncProgress68Light(t *testing.T) { testForkedSyncProgress(t, eth.ETH68, LightSync) }
|
||||
func TestForkedSyncProgress67Full(t *testing.T) { testForkedSyncProgress(t, eth.ETH67, FullSync) }
|
||||
func TestForkedSyncProgress67Snap(t *testing.T) { testForkedSyncProgress(t, eth.ETH67, SnapSync) }
|
||||
func TestForkedSyncProgress67Light(t *testing.T) { testForkedSyncProgress(t, eth.ETH67, LightSync) }
|
||||
|
||||
func testForkedSyncProgress(t *testing.T, protocol uint, mode SyncMode) {
|
||||
tester := newTester(t)
|
||||
|
|
@ -1117,9 +1059,6 @@ func testForkedSyncProgress(t *testing.T, protocol uint, mode SyncMode) {
|
|||
func TestFailedSyncProgress68Full(t *testing.T) { testFailedSyncProgress(t, eth.ETH68, FullSync) }
|
||||
func TestFailedSyncProgress68Snap(t *testing.T) { testFailedSyncProgress(t, eth.ETH68, SnapSync) }
|
||||
func TestFailedSyncProgress68Light(t *testing.T) { testFailedSyncProgress(t, eth.ETH68, LightSync) }
|
||||
func TestFailedSyncProgress67Full(t *testing.T) { testFailedSyncProgress(t, eth.ETH67, FullSync) }
|
||||
func TestFailedSyncProgress67Snap(t *testing.T) { testFailedSyncProgress(t, eth.ETH67, SnapSync) }
|
||||
func TestFailedSyncProgress67Light(t *testing.T) { testFailedSyncProgress(t, eth.ETH67, LightSync) }
|
||||
|
||||
func testFailedSyncProgress(t *testing.T, protocol uint, mode SyncMode) {
|
||||
tester := newTester(t)
|
||||
|
|
@ -1186,9 +1125,6 @@ func testFailedSyncProgress(t *testing.T, protocol uint, mode SyncMode) {
|
|||
func TestFakedSyncProgress68Full(t *testing.T) { testFakedSyncProgress(t, eth.ETH68, FullSync) }
|
||||
func TestFakedSyncProgress68Snap(t *testing.T) { testFakedSyncProgress(t, eth.ETH68, SnapSync) }
|
||||
func TestFakedSyncProgress68Light(t *testing.T) { testFakedSyncProgress(t, eth.ETH68, LightSync) }
|
||||
func TestFakedSyncProgress67Full(t *testing.T) { testFakedSyncProgress(t, eth.ETH67, FullSync) }
|
||||
func TestFakedSyncProgress67Snap(t *testing.T) { testFakedSyncProgress(t, eth.ETH67, SnapSync) }
|
||||
func TestFakedSyncProgress67Light(t *testing.T) { testFakedSyncProgress(t, eth.ETH67, LightSync) }
|
||||
|
||||
func testFakedSyncProgress(t *testing.T, protocol uint, mode SyncMode) {
|
||||
tester := newTester(t)
|
||||
|
|
@ -1332,8 +1268,6 @@ func TestRemoteHeaderRequestSpan(t *testing.T) {
|
|||
// being fast-synced from, avoiding potential cheap eclipse attacks.
|
||||
func TestBeaconSync68Full(t *testing.T) { testBeaconSync(t, eth.ETH68, FullSync) }
|
||||
func TestBeaconSync68Snap(t *testing.T) { testBeaconSync(t, eth.ETH68, SnapSync) }
|
||||
func TestBeaconSync67Full(t *testing.T) { testBeaconSync(t, eth.ETH67, FullSync) }
|
||||
func TestBeaconSync67Snap(t *testing.T) { testBeaconSync(t, eth.ETH67, SnapSync) }
|
||||
|
||||
func testBeaconSync(t *testing.T, protocol uint, mode SyncMode) {
|
||||
//log.Root().SetHandler(log.LvlFilterHandler(log.LvlInfo, log.StreamHandler(os.Stderr, log.TerminalFormat(true))))
|
||||
|
|
|
|||
|
|
@ -811,7 +811,7 @@ func TestSkeletonSyncRetrievals(t *testing.T) {
|
|||
// Create a peer set to feed headers through
|
||||
peerset := newPeerSet()
|
||||
for _, peer := range tt.peers {
|
||||
peerset.Register(newPeerConnection(peer.id, eth.ETH67, peer, log.New("id", peer.id)))
|
||||
peerset.Register(newPeerConnection(peer.id, eth.ETH68, peer, log.New("id", peer.id)))
|
||||
}
|
||||
// Create a peer dropper to track malicious peers
|
||||
dropped := make(map[string]int)
|
||||
|
|
@ -913,7 +913,7 @@ func TestSkeletonSyncRetrievals(t *testing.T) {
|
|||
skeleton.Sync(tt.newHead, nil, true)
|
||||
}
|
||||
if tt.newPeer != nil {
|
||||
if err := peerset.Register(newPeerConnection(tt.newPeer.id, eth.ETH67, tt.newPeer, log.New("id", tt.newPeer.id))); err != nil {
|
||||
if err := peerset.Register(newPeerConnection(tt.newPeer.id, eth.ETH68, tt.newPeer, log.New("id", tt.newPeer.id))); err != nil {
|
||||
t.Errorf("test %d: failed to register new peer: %v", i, err)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -227,8 +227,8 @@ func (oracle *Oracle) FeeHistory(ctx context.Context, blocks uint64, unresolvedL
|
|||
if p < 0 || p > 100 {
|
||||
return common.Big0, nil, nil, nil, fmt.Errorf("%w: %f", errInvalidPercentile, p)
|
||||
}
|
||||
if i > 0 && p < rewardPercentiles[i-1] {
|
||||
return common.Big0, nil, nil, nil, fmt.Errorf("%w: #%d:%f > #%d:%f", errInvalidPercentile, i-1, rewardPercentiles[i-1], i, p)
|
||||
if i > 0 && p <= rewardPercentiles[i-1] {
|
||||
return common.Big0, nil, nil, nil, fmt.Errorf("%w: #%d:%f >= #%d:%f", errInvalidPercentile, i-1, rewardPercentiles[i-1], i, p)
|
||||
}
|
||||
}
|
||||
var (
|
||||
|
|
|
|||
|
|
@ -67,10 +67,7 @@ func (h *ethHandler) Handle(peer *eth.Peer, packet eth.Packet) error {
|
|||
case *eth.NewBlockPacket:
|
||||
return h.handleBlockBroadcast(peer, packet.Block, packet.TD)
|
||||
|
||||
case *eth.NewPooledTransactionHashesPacket67:
|
||||
return h.txFetcher.Notify(peer.ID(), nil, nil, *packet)
|
||||
|
||||
case *eth.NewPooledTransactionHashesPacket68:
|
||||
case *eth.NewPooledTransactionHashesPacket:
|
||||
return h.txFetcher.Notify(peer.ID(), packet.Types, packet.Sizes, packet.Hashes)
|
||||
|
||||
case *eth.TransactionsPacket:
|
||||
|
|
|
|||
|
|
@ -58,11 +58,7 @@ func (h *testEthHandler) Handle(peer *eth.Peer, packet eth.Packet) error {
|
|||
h.blockBroadcasts.Send(packet.Block)
|
||||
return nil
|
||||
|
||||
case *eth.NewPooledTransactionHashesPacket67:
|
||||
h.txAnnounces.Send(([]common.Hash)(*packet))
|
||||
return nil
|
||||
|
||||
case *eth.NewPooledTransactionHashesPacket68:
|
||||
case *eth.NewPooledTransactionHashesPacket:
|
||||
h.txAnnounces.Send(packet.Hashes)
|
||||
return nil
|
||||
|
||||
|
|
@ -81,7 +77,6 @@ func (h *testEthHandler) Handle(peer *eth.Peer, packet eth.Packet) error {
|
|||
|
||||
// Tests that peers are correctly accepted (or rejected) based on the advertised
|
||||
// fork IDs in the protocol handshake.
|
||||
func TestForkIDSplit67(t *testing.T) { testForkIDSplit(t, eth.ETH67) }
|
||||
func TestForkIDSplit68(t *testing.T) { testForkIDSplit(t, eth.ETH68) }
|
||||
|
||||
func testForkIDSplit(t *testing.T, protocol uint) {
|
||||
|
|
@ -236,7 +231,6 @@ func testForkIDSplit(t *testing.T, protocol uint) {
|
|||
}
|
||||
|
||||
// Tests that received transactions are added to the local pool.
|
||||
func TestRecvTransactions67(t *testing.T) { testRecvTransactions(t, eth.ETH67) }
|
||||
func TestRecvTransactions68(t *testing.T) { testRecvTransactions(t, eth.ETH68) }
|
||||
|
||||
func testRecvTransactions(t *testing.T, protocol uint) {
|
||||
|
|
@ -294,7 +288,6 @@ func testRecvTransactions(t *testing.T, protocol uint) {
|
|||
}
|
||||
|
||||
// This test checks that pending transactions are sent.
|
||||
func TestSendTransactions67(t *testing.T) { testSendTransactions(t, eth.ETH67) }
|
||||
func TestSendTransactions68(t *testing.T) { testSendTransactions(t, eth.ETH68) }
|
||||
|
||||
func testSendTransactions(t *testing.T, protocol uint) {
|
||||
|
|
@ -353,7 +346,7 @@ func testSendTransactions(t *testing.T, protocol uint) {
|
|||
seen := make(map[common.Hash]struct{})
|
||||
for len(seen) < len(insert) {
|
||||
switch protocol {
|
||||
case 67, 68:
|
||||
case 68:
|
||||
select {
|
||||
case hashes := <-anns:
|
||||
for _, hash := range hashes {
|
||||
|
|
@ -379,7 +372,6 @@ func testSendTransactions(t *testing.T, protocol uint) {
|
|||
|
||||
// Tests that transactions get propagated to all attached peers, either via direct
|
||||
// broadcasts or via announcements/retrievals.
|
||||
func TestTransactionPropagation67(t *testing.T) { testTransactionPropagation(t, eth.ETH67) }
|
||||
func TestTransactionPropagation68(t *testing.T) { testTransactionPropagation(t, eth.ETH68) }
|
||||
|
||||
func testTransactionPropagation(t *testing.T, protocol uint) {
|
||||
|
|
@ -486,8 +478,8 @@ func testBroadcastBlock(t *testing.T, peers, bcasts int) {
|
|||
defer sourcePipe.Close()
|
||||
defer sinkPipe.Close()
|
||||
|
||||
sourcePeer := eth.NewPeer(eth.ETH67, p2p.NewPeerPipe(enode.ID{byte(i)}, "", nil, sourcePipe), sourcePipe, nil)
|
||||
sinkPeer := eth.NewPeer(eth.ETH67, p2p.NewPeerPipe(enode.ID{0}, "", nil, sinkPipe), sinkPipe, nil)
|
||||
sourcePeer := eth.NewPeer(eth.ETH68, p2p.NewPeerPipe(enode.ID{byte(i)}, "", nil, sourcePipe), sourcePipe, nil)
|
||||
sinkPeer := eth.NewPeer(eth.ETH68, p2p.NewPeerPipe(enode.ID{0}, "", nil, sinkPipe), sinkPipe, nil)
|
||||
defer sourcePeer.Close()
|
||||
defer sinkPeer.Close()
|
||||
|
||||
|
|
@ -539,7 +531,6 @@ func testBroadcastBlock(t *testing.T, peers, bcasts int) {
|
|||
|
||||
// Tests that a propagated malformed block (uncles or transactions don't match
|
||||
// with the hashes in the header) gets discarded and not broadcast forward.
|
||||
func TestBroadcastMalformedBlock67(t *testing.T) { testBroadcastMalformedBlock(t, eth.ETH67) }
|
||||
func TestBroadcastMalformedBlock68(t *testing.T) { testBroadcastMalformedBlock(t, eth.ETH68) }
|
||||
|
||||
func testBroadcastMalformedBlock(t *testing.T, protocol uint) {
|
||||
|
|
|
|||
|
|
@ -163,16 +163,9 @@ func (p *Peer) announceTransactions() {
|
|||
if len(pending) > 0 {
|
||||
done = make(chan struct{})
|
||||
go func() {
|
||||
if p.version >= ETH68 {
|
||||
if err := p.sendPooledTransactionHashes68(pending, pendingTypes, pendingSizes); err != nil {
|
||||
fail <- err
|
||||
return
|
||||
}
|
||||
} else {
|
||||
if err := p.sendPooledTransactionHashes66(pending); err != nil {
|
||||
fail <- err
|
||||
return
|
||||
}
|
||||
if err := p.sendPooledTransactionHashes(pending, pendingTypes, pendingSizes); err != nil {
|
||||
fail <- err
|
||||
return
|
||||
}
|
||||
close(done)
|
||||
p.Log().Trace("Sent transaction announcements", "count", len(pending))
|
||||
|
|
|
|||
|
|
@ -93,10 +93,6 @@ type TxPool interface {
|
|||
func MakeProtocols(backend Backend, network uint64, dnsdisc enode.Iterator) []p2p.Protocol {
|
||||
protocols := make([]p2p.Protocol, 0, len(ProtocolVersions))
|
||||
for _, version := range ProtocolVersions {
|
||||
// Blob transactions require eth/68 announcements, disable everything else
|
||||
if version <= ETH67 && backend.Chain().Config().CancunTime != nil {
|
||||
continue
|
||||
}
|
||||
version := version // Closure
|
||||
|
||||
protocols = append(protocols, p2p.Protocol{
|
||||
|
|
@ -166,26 +162,11 @@ type Decoder interface {
|
|||
Time() time.Time
|
||||
}
|
||||
|
||||
var eth67 = map[uint64]msgHandler{
|
||||
NewBlockHashesMsg: handleNewBlockhashes,
|
||||
NewBlockMsg: handleNewBlock,
|
||||
TransactionsMsg: handleTransactions,
|
||||
NewPooledTransactionHashesMsg: handleNewPooledTransactionHashes67,
|
||||
GetBlockHeadersMsg: handleGetBlockHeaders,
|
||||
BlockHeadersMsg: handleBlockHeaders,
|
||||
GetBlockBodiesMsg: handleGetBlockBodies,
|
||||
BlockBodiesMsg: handleBlockBodies,
|
||||
GetReceiptsMsg: handleGetReceipts,
|
||||
ReceiptsMsg: handleReceipts,
|
||||
GetPooledTransactionsMsg: handleGetPooledTransactions,
|
||||
PooledTransactionsMsg: handlePooledTransactions,
|
||||
}
|
||||
|
||||
var eth68 = map[uint64]msgHandler{
|
||||
NewBlockHashesMsg: handleNewBlockhashes,
|
||||
NewBlockMsg: handleNewBlock,
|
||||
TransactionsMsg: handleTransactions,
|
||||
NewPooledTransactionHashesMsg: handleNewPooledTransactionHashes68,
|
||||
NewPooledTransactionHashesMsg: handleNewPooledTransactionHashes,
|
||||
GetBlockHeadersMsg: handleGetBlockHeaders,
|
||||
BlockHeadersMsg: handleBlockHeaders,
|
||||
GetBlockBodiesMsg: handleGetBlockBodies,
|
||||
|
|
@ -209,10 +190,8 @@ func handleMessage(backend Backend, peer *Peer) error {
|
|||
}
|
||||
defer msg.Discard()
|
||||
|
||||
var handlers = eth67
|
||||
if peer.Version() >= ETH68 {
|
||||
handlers = eth68
|
||||
}
|
||||
var handlers = eth68
|
||||
|
||||
// Track the amount of time it takes to serve the request and run the handler
|
||||
if metrics.Enabled {
|
||||
h := fmt.Sprintf("%s/%s/%d/%#02x", p2p.HandleHistName, ProtocolName, peer.Version(), msg.Code)
|
||||
|
|
|
|||
|
|
@ -150,7 +150,6 @@ func (b *testBackend) Handle(*Peer, Packet) error {
|
|||
}
|
||||
|
||||
// Tests that block headers can be retrieved from a remote chain based on user queries.
|
||||
func TestGetBlockHeaders67(t *testing.T) { testGetBlockHeaders(t, ETH67) }
|
||||
func TestGetBlockHeaders68(t *testing.T) { testGetBlockHeaders(t, ETH68) }
|
||||
|
||||
func testGetBlockHeaders(t *testing.T, protocol uint) {
|
||||
|
|
@ -336,7 +335,6 @@ func testGetBlockHeaders(t *testing.T, protocol uint) {
|
|||
}
|
||||
|
||||
// Tests that block contents can be retrieved from a remote chain based on their hashes.
|
||||
func TestGetBlockBodies67(t *testing.T) { testGetBlockBodies(t, ETH67) }
|
||||
func TestGetBlockBodies68(t *testing.T) { testGetBlockBodies(t, ETH68) }
|
||||
|
||||
func testGetBlockBodies(t *testing.T, protocol uint) {
|
||||
|
|
@ -431,7 +429,6 @@ func testGetBlockBodies(t *testing.T, protocol uint) {
|
|||
}
|
||||
|
||||
// Tests that the transaction receipts can be retrieved based on hashes.
|
||||
func TestGetBlockReceipts67(t *testing.T) { testGetBlockReceipts(t, ETH67) }
|
||||
func TestGetBlockReceipts68(t *testing.T) { testGetBlockReceipts(t, ETH68) }
|
||||
|
||||
func testGetBlockReceipts(t *testing.T, protocol uint) {
|
||||
|
|
|
|||
|
|
@ -383,30 +383,13 @@ func handleReceipts(backend Backend, msg Decoder, peer *Peer) error {
|
|||
}, metadata)
|
||||
}
|
||||
|
||||
func handleNewPooledTransactionHashes67(backend Backend, msg Decoder, peer *Peer) error {
|
||||
func handleNewPooledTransactionHashes(backend Backend, msg Decoder, peer *Peer) error {
|
||||
// New transaction announcement arrived, make sure we have
|
||||
// a valid and fresh chain to handle them
|
||||
if !backend.AcceptTxs() {
|
||||
return nil
|
||||
}
|
||||
ann := new(NewPooledTransactionHashesPacket67)
|
||||
if err := msg.Decode(ann); err != nil {
|
||||
return fmt.Errorf("%w: message %v: %v", errDecode, msg, err)
|
||||
}
|
||||
// Schedule all the unknown hashes for retrieval
|
||||
for _, hash := range *ann {
|
||||
peer.markTransaction(hash)
|
||||
}
|
||||
return backend.Handle(peer, ann)
|
||||
}
|
||||
|
||||
func handleNewPooledTransactionHashes68(backend Backend, msg Decoder, peer *Peer) error {
|
||||
// New transaction announcement arrived, make sure we have
|
||||
// a valid and fresh chain to handle them
|
||||
if !backend.AcceptTxs() {
|
||||
return nil
|
||||
}
|
||||
ann := new(NewPooledTransactionHashesPacket68)
|
||||
ann := new(NewPooledTransactionHashesPacket)
|
||||
if err := msg.Decode(ann); err != nil {
|
||||
return fmt.Errorf("%w: message %v: %v", errDecode, msg, err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,7 +27,6 @@ import (
|
|||
)
|
||||
|
||||
// Tests that handshake failures are detected and reported correctly.
|
||||
func TestHandshake67(t *testing.T) { testHandshake(t, ETH67) }
|
||||
func TestHandshake68(t *testing.T) { testHandshake(t, ETH68) }
|
||||
|
||||
func testHandshake(t *testing.T, protocol uint) {
|
||||
|
|
|
|||
|
|
@ -210,29 +210,17 @@ func (p *Peer) AsyncSendTransactions(hashes []common.Hash) {
|
|||
}
|
||||
}
|
||||
|
||||
// sendPooledTransactionHashes66 sends transaction hashes to the peer and includes
|
||||
// them in its transaction hash set for future reference.
|
||||
//
|
||||
// This method is a helper used by the async transaction announcer. Don't call it
|
||||
// directly as the queueing (memory) and transmission (bandwidth) costs should
|
||||
// not be managed directly.
|
||||
func (p *Peer) sendPooledTransactionHashes66(hashes []common.Hash) error {
|
||||
// Mark all the transactions as known, but ensure we don't overflow our limits
|
||||
p.knownTxs.Add(hashes...)
|
||||
return p2p.Send(p.rw, NewPooledTransactionHashesMsg, NewPooledTransactionHashesPacket67(hashes))
|
||||
}
|
||||
|
||||
// sendPooledTransactionHashes68 sends transaction hashes (tagged with their type
|
||||
// sendPooledTransactionHashes sends transaction hashes (tagged with their type
|
||||
// and size) to the peer and includes them in its transaction hash set for future
|
||||
// reference.
|
||||
//
|
||||
// This method is a helper used by the async transaction announcer. Don't call it
|
||||
// directly as the queueing (memory) and transmission (bandwidth) costs should
|
||||
// not be managed directly.
|
||||
func (p *Peer) sendPooledTransactionHashes68(hashes []common.Hash, types []byte, sizes []uint32) error {
|
||||
func (p *Peer) sendPooledTransactionHashes(hashes []common.Hash, types []byte, sizes []uint32) error {
|
||||
// Mark all the transactions as known, but ensure we don't overflow our limits
|
||||
p.knownTxs.Add(hashes...)
|
||||
return p2p.Send(p.rw, NewPooledTransactionHashesMsg, NewPooledTransactionHashesPacket68{Types: types, Sizes: sizes, Hashes: hashes})
|
||||
return p2p.Send(p.rw, NewPooledTransactionHashesMsg, NewPooledTransactionHashesPacket{Types: types, Sizes: sizes, Hashes: hashes})
|
||||
}
|
||||
|
||||
// AsyncSendPooledTransactionHashes queues a list of transactions hashes to eventually
|
||||
|
|
|
|||
|
|
@ -30,7 +30,6 @@ import (
|
|||
|
||||
// Constants to match up protocol versions and messages
|
||||
const (
|
||||
ETH67 = 67
|
||||
ETH68 = 68
|
||||
)
|
||||
|
||||
|
|
@ -40,11 +39,11 @@ const ProtocolName = "eth"
|
|||
|
||||
// ProtocolVersions are the supported versions of the `eth` protocol (first
|
||||
// is primary).
|
||||
var ProtocolVersions = []uint{ETH68, ETH67}
|
||||
var ProtocolVersions = []uint{ETH68}
|
||||
|
||||
// protocolLengths are the number of implemented message corresponding to
|
||||
// different protocol versions.
|
||||
var protocolLengths = map[uint]uint64{ETH68: 17, ETH67: 17}
|
||||
var protocolLengths = map[uint]uint64{ETH68: 17}
|
||||
|
||||
// maxMessageSize is the maximum cap on the size of a protocol message.
|
||||
const maxMessageSize = 10 * 1024 * 1024
|
||||
|
|
@ -283,11 +282,8 @@ type ReceiptsRLPPacket struct {
|
|||
ReceiptsRLPResponse
|
||||
}
|
||||
|
||||
// NewPooledTransactionHashesPacket67 represents a transaction announcement packet on eth/67.
|
||||
type NewPooledTransactionHashesPacket67 []common.Hash
|
||||
|
||||
// NewPooledTransactionHashesPacket68 represents a transaction announcement packet on eth/68 and newer.
|
||||
type NewPooledTransactionHashesPacket68 struct {
|
||||
// NewPooledTransactionHashesPacket represents a transaction announcement packet on eth/68 and newer.
|
||||
type NewPooledTransactionHashesPacket struct {
|
||||
Types []byte
|
||||
Sizes []uint32
|
||||
Hashes []common.Hash
|
||||
|
|
@ -346,10 +342,8 @@ func (*BlockBodiesResponse) Kind() byte { return BlockBodiesMsg }
|
|||
func (*NewBlockPacket) Name() string { return "NewBlock" }
|
||||
func (*NewBlockPacket) Kind() byte { return NewBlockMsg }
|
||||
|
||||
func (*NewPooledTransactionHashesPacket67) Name() string { return "NewPooledTransactionHashes" }
|
||||
func (*NewPooledTransactionHashesPacket67) Kind() byte { return NewPooledTransactionHashesMsg }
|
||||
func (*NewPooledTransactionHashesPacket68) Name() string { return "NewPooledTransactionHashes" }
|
||||
func (*NewPooledTransactionHashesPacket68) Kind() byte { return NewPooledTransactionHashesMsg }
|
||||
func (*NewPooledTransactionHashesPacket) Name() string { return "NewPooledTransactionHashes" }
|
||||
func (*NewPooledTransactionHashesPacket) Kind() byte { return NewPooledTransactionHashesMsg }
|
||||
|
||||
func (*GetPooledTransactionsRequest) Name() string { return "GetPooledTransactions" }
|
||||
func (*GetPooledTransactionsRequest) Kind() byte { return GetPooledTransactionsMsg }
|
||||
|
|
|
|||
|
|
@ -28,7 +28,6 @@ import (
|
|||
)
|
||||
|
||||
// Tests that snap sync is disabled after a successful sync cycle.
|
||||
func TestSnapSyncDisabling67(t *testing.T) { testSnapSyncDisabling(t, eth.ETH67, snap.SNAP1) }
|
||||
func TestSnapSyncDisabling68(t *testing.T) { testSnapSyncDisabling(t, eth.ETH68, snap.SNAP1) }
|
||||
|
||||
// Tests that snap sync gets disabled as soon as a real block is successfully
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ func WithCallGasLimit(gaslimit uint64) func(nodeConf *node.Config, ethConf *ethc
|
|||
// gas tip for a transaction to be included.
|
||||
//
|
||||
// 0 is not possible as a live Geth node would reject that due to DoS protection,
|
||||
// so the simulated backend will replicate that behavior for consisntency.
|
||||
// so the simulated backend will replicate that behavior for consistency.
|
||||
func WithMinerMinTip(tip *big.Int) func(nodeConf *node.Config, ethConf *ethconfig.Config) {
|
||||
if tip == nil || tip.Cmp(new(big.Int)) <= 0 {
|
||||
panic("invalid miner minimum tip")
|
||||
|
|
|
|||
3
go.mod
3
go.mod
|
|
@ -22,6 +22,7 @@ require (
|
|||
github.com/dop251/goja v0.0.0-20230806174421-c933cf95e127
|
||||
github.com/ethereum/c-kzg-4844 v0.4.0
|
||||
github.com/fatih/color v1.13.0
|
||||
github.com/ferranbt/fastssz v0.1.2
|
||||
github.com/fjl/gencodec v0.0.0-20230517082657-f9840df7b83e
|
||||
github.com/fjl/memsize v0.0.2
|
||||
github.com/fsnotify/fsnotify v1.6.0
|
||||
|
|
@ -114,10 +115,12 @@ require (
|
|||
github.com/jmespath/go-jmespath v0.4.0 // indirect
|
||||
github.com/kilic/bls12-381 v0.1.0 // indirect
|
||||
github.com/klauspost/compress v1.15.15 // 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
|
||||
github.com/mattn/go-runewidth v0.0.13 // indirect
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect
|
||||
github.com/minio/sha256-simd v1.0.0 // indirect
|
||||
github.com/mitchellh/mapstructure v1.4.1 // indirect
|
||||
github.com/mitchellh/pointerstructure v1.2.0 // indirect
|
||||
github.com/mmcloughlin/addchain v0.4.0 // indirect
|
||||
|
|
|
|||
8
go.sum
8
go.sum
|
|
@ -187,6 +187,8 @@ github.com/fasthttp-contrib/websocket v0.0.0-20160511215533-1f3b11f56072/go.mod
|
|||
github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w=
|
||||
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
|
||||
github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
|
||||
github.com/ferranbt/fastssz v0.1.2 h1:Dky6dXlngF6Qjc+EfDipAkE83N5I5DE68bY6O0VLNPk=
|
||||
github.com/ferranbt/fastssz v0.1.2/go.mod h1:X5UPrE2u1UJjxHA8X54u04SBwdAQjG2sFtWs39YxyWs=
|
||||
github.com/fjl/gencodec v0.0.0-20230517082657-f9840df7b83e h1:bBLctRc7kr01YGvaDfgLbTwjFNW5jdp5y5rj8XXBHfY=
|
||||
github.com/fjl/gencodec v0.0.0-20230517082657-f9840df7b83e/go.mod h1:AzA8Lj6YtixmJWL+wkKoBGsLWy9gFrAzi4g+5bCKwpY=
|
||||
github.com/fjl/memsize v0.0.2 h1:27txuSD9or+NZlnOWdKUxeBzTAUkWCVh+4Gf2dWFOzA=
|
||||
|
|
@ -399,6 +401,9 @@ github.com/klauspost/compress v1.9.0/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0
|
|||
github.com/klauspost/compress v1.15.15 h1:EF27CXIuDsYJ6mmvtBRlEuB2UVOqHG1tAXgZ7yIO+lw=
|
||||
github.com/klauspost/compress v1.15.15/go.mod h1:ZcK2JAFqKOpnBlxcLsJzYfrS9X1akm9fHZNnD9+Vo/4=
|
||||
github.com/klauspost/cpuid v1.2.1/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
|
||||
github.com/klauspost/cpuid/v2 v2.0.4/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||
github.com/klauspost/cpuid/v2 v2.0.9 h1:lgaqFMSdTdQYdZ04uHyN2d/eKdOMyi2YLSvlQIBFYa4=
|
||||
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
||||
|
|
@ -446,6 +451,8 @@ github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182aff
|
|||
github.com/mediocregopher/mediocre-go-lib v0.0.0-20181029021733-cb65787f37ed/go.mod h1:dSsfyI2zABAdhcbvkXqgxOxrCsbYeHCPgrZkku60dSg=
|
||||
github.com/mediocregopher/radix/v3 v3.3.0/go.mod h1:EmfVyvspXz1uZEyPBMyGK+kjWiKQGvsUt6O3Pj+LDCQ=
|
||||
github.com/microcosm-cc/bluemonday v1.0.2/go.mod h1:iVP4YcDBq+n/5fb23BhYFvIMq/leAFZyRl6bYmGDlGc=
|
||||
github.com/minio/sha256-simd v1.0.0 h1:v1ta+49hkWZyvaKwrQB8elexRqm6Y0aMLjCNsrYxo6g=
|
||||
github.com/minio/sha256-simd v1.0.0/go.mod h1:OuYzVNI5vcoYIAmbIvHPl3N3jUzVedXbKy5RFepssQM=
|
||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||
github.com/mitchellh/mapstructure v1.4.1 h1:CpVNEelQCZBooIPDn+AR3NpivK/TIKU8bDxdASFVQag=
|
||||
|
|
@ -523,6 +530,7 @@ github.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0
|
|||
github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
|
||||
github.com/protolambda/bls12-381-util v0.0.0-20220416220906-d8552aa452c7 h1:cZC+usqsYgHtlBaGulVnZ1hfKAi8iWtujBnRLQE698c=
|
||||
github.com/protolambda/bls12-381-util v0.0.0-20220416220906-d8552aa452c7/go.mod h1:IToEjHuttnUzwZI5KBSM/LOOW3qLbbrHOEfp3SbECGY=
|
||||
github.com/prysmaticlabs/gohashtree v0.0.1-alpha.0.20220714111606-acbb2962fb48 h1:cSo6/vk8YpvkLbk9v3FO97cakNmUoxwi2KMP8hd5WIw=
|
||||
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
|
||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
|
|
|
|||
90
internal/era/accumulator.go
Normal file
90
internal/era/accumulator.go
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
// Copyright 2023 The go-ethereum Authors
|
||||
// This file is part of go-ethereum.
|
||||
//
|
||||
// go-ethereum is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// go-ethereum is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package era
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/big"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
ssz "github.com/ferranbt/fastssz"
|
||||
)
|
||||
|
||||
// ComputeAccumulator calculates the SSZ hash tree root of the Era1
|
||||
// accumulator of header records.
|
||||
func ComputeAccumulator(hashes []common.Hash, tds []*big.Int) (common.Hash, error) {
|
||||
if len(hashes) != len(tds) {
|
||||
return common.Hash{}, fmt.Errorf("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)
|
||||
}
|
||||
hh := ssz.NewHasher()
|
||||
for i := range hashes {
|
||||
rec := headerRecord{hashes[i], tds[i]}
|
||||
root, err := rec.HashTreeRoot()
|
||||
if err != nil {
|
||||
return common.Hash{}, err
|
||||
}
|
||||
hh.Append(root[:])
|
||||
}
|
||||
hh.MerkleizeWithMixin(0, uint64(len(hashes)), uint64(MaxEra1Size))
|
||||
return hh.HashRoot()
|
||||
}
|
||||
|
||||
// headerRecord is an individual record for a historical header.
|
||||
//
|
||||
// See https://github.com/ethereum/portal-network-specs/blob/master/history-network.md#the-header-accumulator
|
||||
// for more information.
|
||||
type headerRecord struct {
|
||||
Hash common.Hash
|
||||
TotalDifficulty *big.Int
|
||||
}
|
||||
|
||||
// GetTree completes the ssz.HashRoot interface, but is unused.
|
||||
func (h *headerRecord) GetTree() (*ssz.Node, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// HashTreeRoot ssz hashes the headerRecord object.
|
||||
func (h *headerRecord) HashTreeRoot() ([32]byte, error) {
|
||||
return ssz.HashWithDefaultHasher(h)
|
||||
}
|
||||
|
||||
// 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)
|
||||
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) {
|
||||
n.FillBytes(b[:])
|
||||
reverseOrder(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
|
||||
}
|
||||
224
internal/era/builder.go
Normal file
224
internal/era/builder.go
Normal file
|
|
@ -0,0 +1,224 @@
|
|||
// Copyright 2023 The go-ethereum Authors
|
||||
// This file is part of go-ethereum.
|
||||
//
|
||||
// go-ethereum is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// go-ethereum is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
package era
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io"
|
||||
"math/big"
|
||||
|
||||
"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"
|
||||
)
|
||||
|
||||
// Builder is used to create Era1 archives of block data.
|
||||
//
|
||||
// Era1 files are themselves e2store files. For more information on this format,
|
||||
// see https://github.com/status-im/nimbus-eth2/blob/stable/docs/e2store.md.
|
||||
//
|
||||
// The overall structure of an Era1 file follows closely the structure of an Era file
|
||||
// which contains consensus Layer data (and as a byproduct, EL data after the merge).
|
||||
//
|
||||
// The structure can be summarized through this definition:
|
||||
//
|
||||
// era1 := Version | block-tuple* | other-entries* | Accumulator | BlockIndex
|
||||
// block-tuple := CompressedHeader | CompressedBody | CompressedReceipts | TotalDifficulty
|
||||
//
|
||||
// Each basic element is its own entry:
|
||||
//
|
||||
// Version = { type: [0x65, 0x32], data: nil }
|
||||
// CompressedHeader = { type: [0x03, 0x00], data: snappyFramed(rlp(header)) }
|
||||
// CompressedBody = { type: [0x04, 0x00], data: snappyFramed(rlp(body)) }
|
||||
// CompressedReceipts = { type: [0x05, 0x00], data: snappyFramed(rlp(receipts)) }
|
||||
// TotalDifficulty = { type: [0x06, 0x00], data: uint256(header.total_difficulty) }
|
||||
// AccumulatorRoot = { type: [0x07, 0x00], data: accumulator-root }
|
||||
// BlockIndex = { type: [0x32, 0x66], data: block-index }
|
||||
//
|
||||
// Accumulator is computed by constructing an SSZ list of header-records of length at most
|
||||
// 8192 and then calculating the hash_tree_root of that list.
|
||||
//
|
||||
// header-record := { block-hash: Bytes32, total-difficulty: Uint256 }
|
||||
// accumulator := hash_tree_root([]header-record, 8192)
|
||||
//
|
||||
// BlockIndex stores relative offsets to each compressed block entry. The
|
||||
// format is:
|
||||
//
|
||||
// block-index := starting-number | index | index | index ... | count
|
||||
//
|
||||
// starting-number is the first block number in the archive. Every index is a
|
||||
// defined relative to beginning of the record. The total number of block
|
||||
// entries in the file is recorded with count.
|
||||
//
|
||||
// Due to the accumulator size limit of 8192, the maximum number of blocks in
|
||||
// an Era1 batch is also 8192.
|
||||
type Builder struct {
|
||||
w *e2store.Writer
|
||||
startNum *uint64
|
||||
startTd *big.Int
|
||||
indexes []uint64
|
||||
hashes []common.Hash
|
||||
tds []*big.Int
|
||||
written int
|
||||
|
||||
buf *bytes.Buffer
|
||||
snappy *snappy.Writer
|
||||
}
|
||||
|
||||
// NewBuilder returns a new Builder instance.
|
||||
func NewBuilder(w io.Writer) *Builder {
|
||||
buf := bytes.NewBuffer(nil)
|
||||
return &Builder{
|
||||
w: e2store.NewWriter(w),
|
||||
buf: buf,
|
||||
snappy: snappy.NewBufferedWriter(buf),
|
||||
}
|
||||
}
|
||||
|
||||
// Add writes a compressed block entry and compressed receipts entry to the
|
||||
// underlying 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 err
|
||||
}
|
||||
eb, err := rlp.EncodeToBytes(block.Body())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
er, err := rlp.EncodeToBytes(receipts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return b.AddRLP(eh, eb, er, block.NumberU64(), block.Hash(), td, block.Difficulty())
|
||||
}
|
||||
|
||||
// AddRLP writes a compressed block entry and compressed receipts entry to the
|
||||
// underlying e2store file.
|
||||
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)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
startNum := number
|
||||
b.startNum = &startNum
|
||||
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)
|
||||
}
|
||||
|
||||
b.indexes = append(b.indexes, uint64(b.written))
|
||||
b.hashes = append(b.hashes, hash)
|
||||
b.tds = append(b.tds, td)
|
||||
|
||||
// Write block data.
|
||||
if err := b.snappyWrite(TypeCompressedHeader, header); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := b.snappyWrite(TypeCompressedBody, body); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := b.snappyWrite(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[:])
|
||||
b.written += n
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Finalize computes the accumulator and block index values, then writes the
|
||||
// corresponding e2store entries.
|
||||
func (b *Builder) Finalize() (common.Hash, error) {
|
||||
if b.startNum == nil {
|
||||
return common.Hash{}, fmt.Errorf("finalize called on empty builder")
|
||||
}
|
||||
// Compute accumulator root and write entry.
|
||||
root, err := 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[:])
|
||||
b.written += n
|
||||
if err != nil {
|
||||
return common.Hash{}, fmt.Errorf("error writing accumulator: %w", err)
|
||||
}
|
||||
// Get beginning of index entry to calculate block relative offset.
|
||||
base := int64(b.written)
|
||||
|
||||
// Construct block index. Detailed format described in Builder
|
||||
// documentation, but it is essentially encoded as:
|
||||
// "start | index | index | ... | count"
|
||||
var (
|
||||
count = len(b.indexes)
|
||||
index = make([]byte, 16+count*8)
|
||||
)
|
||||
binary.LittleEndian.PutUint64(index, *b.startNum)
|
||||
// Each offset is relative from the position it is encoded in the
|
||||
// index. This means that even if the same block was to be included in
|
||||
// the index twice (this would be invalid anyways), the relative offset
|
||||
// would be different. The idea with this is that after reading a
|
||||
// relative offset, the corresponding block can be quickly read by
|
||||
// performing a seek relative to the current position.
|
||||
for i, offset := range b.indexes {
|
||||
relative := int64(offset) - base
|
||||
binary.LittleEndian.PutUint64(index[8+i*8:], uint64(relative))
|
||||
}
|
||||
binary.LittleEndian.PutUint64(index[8+count*8:], uint64(count))
|
||||
|
||||
// Finally, write the block index entry.
|
||||
if _, err := b.w.Write(TypeBlockIndex, index); err != nil {
|
||||
return common.Hash{}, fmt.Errorf("unable to write block index: %w", err)
|
||||
}
|
||||
|
||||
return root, nil
|
||||
}
|
||||
|
||||
// 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 (
|
||||
buf = b.buf
|
||||
s = b.snappy
|
||||
)
|
||||
buf.Reset()
|
||||
s.Reset(buf)
|
||||
if _, err := b.snappy.Write(in); err != nil {
|
||||
return fmt.Errorf("error snappy encoding: %w", err)
|
||||
}
|
||||
if err := s.Flush(); err != nil {
|
||||
return fmt.Errorf("error flushing snappy encoding: %w", err)
|
||||
}
|
||||
n, err := b.w.Write(typ, b.buf.Bytes())
|
||||
b.written += n
|
||||
if err != nil {
|
||||
return fmt.Errorf("error writing e2store entry: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
220
internal/era/e2store/e2store.go
Normal file
220
internal/era/e2store/e2store.go
Normal file
|
|
@ -0,0 +1,220 @@
|
|||
// Copyright 2023 The go-ethereum Authors
|
||||
// This file is part of go-ethereum.
|
||||
//
|
||||
// go-ethereum is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// go-ethereum is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package e2store
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
const (
|
||||
headerSize = 8
|
||||
valueSizeLimit = 1024 * 1024 * 50
|
||||
)
|
||||
|
||||
// Entry is a variable-length-data record in an e2store.
|
||||
type Entry struct {
|
||||
Type uint16
|
||||
Value []byte
|
||||
}
|
||||
|
||||
// Writer writes entries using e2store encoding.
|
||||
// For more information on this format, see:
|
||||
// https://github.com/status-im/nimbus-eth2/blob/stable/docs/e2store.md
|
||||
type Writer struct {
|
||||
w io.Writer
|
||||
}
|
||||
|
||||
// NewWriter returns a new Writer that writes to w.
|
||||
func NewWriter(w io.Writer) *Writer {
|
||||
return &Writer{w}
|
||||
}
|
||||
|
||||
// Write writes a single e2store entry to w.
|
||||
// An entry is encoded in a type-length-value format. The first 8 bytes of the
|
||||
// record store the type (2 bytes), the length (4 bytes), and some reserved
|
||||
// data (2 bytes). The remaining bytes store b.
|
||||
func (w *Writer) Write(typ uint16, b []byte) (int, error) {
|
||||
buf := make([]byte, headerSize)
|
||||
binary.LittleEndian.PutUint16(buf, typ)
|
||||
binary.LittleEndian.PutUint32(buf[2:], uint32(len(b)))
|
||||
|
||||
// Write header.
|
||||
if n, err := w.w.Write(buf); err != nil {
|
||||
return n, err
|
||||
}
|
||||
// Write value, return combined write size.
|
||||
n, err := w.w.Write(b)
|
||||
return n + headerSize, err
|
||||
}
|
||||
|
||||
// A Reader reads entries from an e2store-encoded file.
|
||||
// For more information on this format, see
|
||||
// https://github.com/status-im/nimbus-eth2/blob/stable/docs/e2store.md
|
||||
type Reader struct {
|
||||
r io.ReaderAt
|
||||
offset int64
|
||||
}
|
||||
|
||||
// NewReader returns a new Reader that reads from r.
|
||||
func NewReader(r io.ReaderAt) *Reader {
|
||||
return &Reader{r, 0}
|
||||
}
|
||||
|
||||
// Read reads one Entry from r.
|
||||
func (r *Reader) Read() (*Entry, error) {
|
||||
var e Entry
|
||||
n, err := r.ReadAt(&e, r.offset)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
r.offset += int64(n)
|
||||
return &e, nil
|
||||
}
|
||||
|
||||
// ReadAt reads one Entry from r at the specified offset.
|
||||
func (r *Reader) ReadAt(entry *Entry, off int64) (int, error) {
|
||||
typ, length, err := r.ReadMetadataAt(off)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
entry.Type = typ
|
||||
|
||||
// Check length bounds.
|
||||
if length > valueSizeLimit {
|
||||
return headerSize, fmt.Errorf("item larger than item size limit %d: have %d", valueSizeLimit, length)
|
||||
}
|
||||
if length == 0 {
|
||||
return headerSize, nil
|
||||
}
|
||||
|
||||
// Read value.
|
||||
val := make([]byte, length)
|
||||
if n, err := r.r.ReadAt(val, off+headerSize); err != nil {
|
||||
n += headerSize
|
||||
// An entry with a non-zero length should not return EOF when
|
||||
// reading the value.
|
||||
if err == io.EOF {
|
||||
return n, io.ErrUnexpectedEOF
|
||||
}
|
||||
return n, err
|
||||
}
|
||||
entry.Value = val
|
||||
return int(headerSize + length), nil
|
||||
}
|
||||
|
||||
// ReaderAt returns an io.Reader delivering value data for the entry at
|
||||
// the specified offset. If the entry type does not match the expected type, an
|
||||
// error is returned.
|
||||
func (r *Reader) ReaderAt(expectedType uint16, off int64) (io.Reader, int, error) {
|
||||
// problem = need to return length+headerSize not just value length via section reader
|
||||
typ, length, err := r.ReadMetadataAt(off)
|
||||
if err != nil {
|
||||
return nil, headerSize, err
|
||||
}
|
||||
if typ != expectedType {
|
||||
return nil, headerSize, fmt.Errorf("wrong type, want %d have %d", expectedType, typ)
|
||||
}
|
||||
if length > valueSizeLimit {
|
||||
return nil, headerSize, fmt.Errorf("item larger than item size limit %d: have %d", valueSizeLimit, length)
|
||||
}
|
||||
return io.NewSectionReader(r.r, off+headerSize, int64(length)), headerSize + int(length), nil
|
||||
}
|
||||
|
||||
// LengthAt reads the header at off and returns the total length of the entry,
|
||||
// including header.
|
||||
func (r *Reader) LengthAt(off int64) (int64, error) {
|
||||
_, length, err := r.ReadMetadataAt(off)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return int64(length) + headerSize, nil
|
||||
}
|
||||
|
||||
// ReadMetadataAt reads the header metadata at the given offset.
|
||||
func (r *Reader) ReadMetadataAt(off int64) (typ uint16, length uint32, err error) {
|
||||
b := make([]byte, headerSize)
|
||||
if n, err := r.r.ReadAt(b, off); err != nil {
|
||||
if err == io.EOF && n > 0 {
|
||||
return 0, 0, io.ErrUnexpectedEOF
|
||||
}
|
||||
return 0, 0, err
|
||||
}
|
||||
typ = binary.LittleEndian.Uint16(b)
|
||||
length = binary.LittleEndian.Uint32(b[2:])
|
||||
|
||||
// Check reserved bytes of header.
|
||||
if b[6] != 0 || b[7] != 0 {
|
||||
return 0, 0, fmt.Errorf("reserved bytes are non-zero")
|
||||
}
|
||||
|
||||
return typ, length, nil
|
||||
}
|
||||
|
||||
// Find returns the first entry with the matching type.
|
||||
func (r *Reader) Find(want uint16) (*Entry, error) {
|
||||
var (
|
||||
off int64
|
||||
typ uint16
|
||||
length uint32
|
||||
err error
|
||||
)
|
||||
for {
|
||||
typ, length, err = r.ReadMetadataAt(off)
|
||||
if err == io.EOF {
|
||||
return nil, io.EOF
|
||||
} else if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if typ == want {
|
||||
var e Entry
|
||||
if _, err := r.ReadAt(&e, off); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &e, nil
|
||||
}
|
||||
off += int64(headerSize + length)
|
||||
}
|
||||
}
|
||||
|
||||
// FindAll returns all entries with the matching type.
|
||||
func (r *Reader) FindAll(want uint16) ([]*Entry, error) {
|
||||
var (
|
||||
off int64
|
||||
typ uint16
|
||||
length uint32
|
||||
entries []*Entry
|
||||
err error
|
||||
)
|
||||
for {
|
||||
typ, length, err = r.ReadMetadataAt(off)
|
||||
if err == io.EOF {
|
||||
return entries, nil
|
||||
} else if err != nil {
|
||||
return entries, err
|
||||
}
|
||||
if typ == want {
|
||||
e := new(Entry)
|
||||
if _, err := r.ReadAt(e, off); err != nil {
|
||||
return entries, err
|
||||
}
|
||||
entries = append(entries, e)
|
||||
}
|
||||
off += int64(headerSize + length)
|
||||
}
|
||||
}
|
||||
150
internal/era/e2store/e2store_test.go
Normal file
150
internal/era/e2store/e2store_test.go
Normal file
|
|
@ -0,0 +1,150 @@
|
|||
// Copyright 2023 The go-ethereum Authors
|
||||
// This file is part of go-ethereum.
|
||||
//
|
||||
// go-ethereum is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// go-ethereum is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package e2store
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"testing"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
)
|
||||
|
||||
func TestEncode(t *testing.T) {
|
||||
for _, test := range []struct {
|
||||
entries []Entry
|
||||
want string
|
||||
name string
|
||||
}{
|
||||
{
|
||||
name: "emptyEntry",
|
||||
entries: []Entry{{0xffff, nil}},
|
||||
want: "ffff000000000000",
|
||||
},
|
||||
{
|
||||
name: "beef",
|
||||
entries: []Entry{{42, common.Hex2Bytes("beef")}},
|
||||
want: "2a00020000000000beef",
|
||||
},
|
||||
{
|
||||
name: "twoEntries",
|
||||
entries: []Entry{
|
||||
{42, common.Hex2Bytes("beef")},
|
||||
{9, common.Hex2Bytes("abcdabcd")},
|
||||
},
|
||||
want: "2a00020000000000beef0900040000000000abcdabcd",
|
||||
},
|
||||
} {
|
||||
tt := test
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
var (
|
||||
b = bytes.NewBuffer(nil)
|
||||
w = NewWriter(b)
|
||||
)
|
||||
for _, e := range tt.entries {
|
||||
if _, err := w.Write(e.Type, e.Value); err != nil {
|
||||
t.Fatalf("encoding error: %v", err)
|
||||
}
|
||||
}
|
||||
if want, have := common.FromHex(tt.want), b.Bytes(); !bytes.Equal(want, have) {
|
||||
t.Fatalf("encoding mismatch (want %x, have %x", want, have)
|
||||
}
|
||||
r := NewReader(bytes.NewReader(b.Bytes()))
|
||||
for _, want := range tt.entries {
|
||||
have, err := r.Read()
|
||||
if err != nil {
|
||||
t.Fatalf("decoding error: %v", err)
|
||||
}
|
||||
if have.Type != want.Type {
|
||||
t.Fatalf("decoded entry does type mismatch (want %v, got %v)", want.Type, have.Type)
|
||||
}
|
||||
if !bytes.Equal(have.Value, want.Value) {
|
||||
t.Fatalf("decoded entry does not match (want %#x, got %#x)", want.Value, have.Value)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDecode(t *testing.T) {
|
||||
for i, tt := range []struct {
|
||||
have string
|
||||
err error
|
||||
}{
|
||||
{ // basic valid decoding
|
||||
have: "ffff000000000000",
|
||||
},
|
||||
{ // basic invalid decoding
|
||||
have: "ffff000000000001",
|
||||
err: fmt.Errorf("reserved bytes are non-zero"),
|
||||
},
|
||||
{ // no more entries to read, returns EOF
|
||||
have: "",
|
||||
err: io.EOF,
|
||||
},
|
||||
{ // malformed type
|
||||
have: "bad",
|
||||
err: io.ErrUnexpectedEOF,
|
||||
},
|
||||
{ // malformed length
|
||||
have: "badbeef",
|
||||
err: io.ErrUnexpectedEOF,
|
||||
},
|
||||
{ // specified length longer than actual value
|
||||
have: "beef010000000000",
|
||||
err: io.ErrUnexpectedEOF,
|
||||
},
|
||||
} {
|
||||
r := NewReader(bytes.NewReader(common.FromHex(tt.have)))
|
||||
if tt.err != nil {
|
||||
_, err := r.Read()
|
||||
if err == nil && tt.err != nil {
|
||||
t.Fatalf("test %d, expected error, got none", i)
|
||||
}
|
||||
if err != nil && tt.err == nil {
|
||||
t.Fatalf("test %d, expected no error, got %v", i, err)
|
||||
}
|
||||
if err != nil && tt.err != nil && err.Error() != tt.err.Error() {
|
||||
t.Fatalf("expected error %v, got %v", tt.err, err)
|
||||
}
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func FuzzCodec(f *testing.F) {
|
||||
f.Fuzz(func(t *testing.T, input []byte) {
|
||||
r := NewReader(bytes.NewReader(input))
|
||||
entry, err := r.Read()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
var (
|
||||
b = bytes.NewBuffer(nil)
|
||||
w = NewWriter(b)
|
||||
)
|
||||
w.Write(entry.Type, entry.Value)
|
||||
output := b.Bytes()
|
||||
// Only care about the input that was actually consumed
|
||||
input = input[:r.offset]
|
||||
if !bytes.Equal(input, output) {
|
||||
t.Fatalf("decode-encode mismatch, input %#x output %#x", input, output)
|
||||
}
|
||||
})
|
||||
}
|
||||
283
internal/era/era.go
Normal file
283
internal/era/era.go
Normal file
|
|
@ -0,0 +1,283 @@
|
|||
// Copyright 2023 The go-ethereum Authors
|
||||
// This file is part of go-ethereum.
|
||||
//
|
||||
// go-ethereum is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// go-ethereum is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package era
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io"
|
||||
"math/big"
|
||||
"os"
|
||||
"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"
|
||||
)
|
||||
|
||||
var (
|
||||
TypeVersion uint16 = 0x3265
|
||||
TypeCompressedHeader uint16 = 0x03
|
||||
TypeCompressedBody uint16 = 0x04
|
||||
TypeCompressedReceipts uint16 = 0x05
|
||||
TypeTotalDifficulty uint16 = 0x06
|
||||
TypeAccumulator uint16 = 0x07
|
||||
TypeBlockIndex uint16 = 0x3266
|
||||
|
||||
MaxEra1Size = 8192
|
||||
)
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
func (e *Era) Close() error {
|
||||
return e.f.Close()
|
||||
}
|
||||
|
||||
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")
|
||||
}
|
||||
off, err := e.readOffset(num)
|
||||
if err != nil {
|
||||
return nil, 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.Transactions, body.Uncles), nil
|
||||
}
|
||||
|
||||
// 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
|
||||
)
|
||||
|
||||
// Read first header.
|
||||
if off, err = e.readOffset(e.m.start); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if r, n, err = newSnappyReader(e.s, TypeCompressedHeader, off); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := rlp.Decode(r, &header); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
off += n
|
||||
|
||||
// Skip over next two records.
|
||||
for i := 0; i < 2; i++ {
|
||||
length, err := e.s.LengthAt(off)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
off += length
|
||||
}
|
||||
|
||||
// Read total difficulty after first block.
|
||||
if r, _, err = e.s.ReaderAt(TypeTotalDifficulty, off); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rawTd, err = io.ReadAll(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
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()
|
||||
clearBuffer(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
|
||||
}
|
||||
|
||||
// newReader 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
|
||||
}
|
||||
|
||||
// clearBuffer zeroes out the buffer.
|
||||
func clearBuffer(buf []byte) {
|
||||
for i := 0; i < len(buf); i++ {
|
||||
buf[i] = 0
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
142
internal/era/era_test.go
Normal file
142
internal/era/era_test.go
Normal file
|
|
@ -0,0 +1,142 @@
|
|||
// Copyright 2023 The go-ethereum Authors
|
||||
// This file is part of go-ethereum.
|
||||
//
|
||||
// go-ethereum is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// go-ethereum is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package era
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"math/big"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
)
|
||||
|
||||
type testchain struct {
|
||||
headers [][]byte
|
||||
bodies [][]byte
|
||||
receipts [][]byte
|
||||
tds []*big.Int
|
||||
}
|
||||
|
||||
func TestEra1Builder(t *testing.T) {
|
||||
// Get temp directory.
|
||||
f, err := os.CreateTemp("", "era1-test")
|
||||
if err != nil {
|
||||
t.Fatalf("error creating temp file: %v", err)
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
var (
|
||||
builder = NewBuilder(f)
|
||||
chain = testchain{}
|
||||
)
|
||||
for i := 0; i < 128; i++ {
|
||||
chain.headers = append(chain.headers, []byte{byte('h'), byte(i)})
|
||||
chain.bodies = append(chain.bodies, []byte{byte('b'), byte(i)})
|
||||
chain.receipts = append(chain.receipts, []byte{byte('r'), byte(i)})
|
||||
chain.tds = append(chain.tds, big.NewInt(int64(i)))
|
||||
}
|
||||
|
||||
// Write blocks to Era1.
|
||||
for i := 0; i < len(chain.headers); i++ {
|
||||
var (
|
||||
header = chain.headers[i]
|
||||
body = chain.bodies[i]
|
||||
receipts = chain.receipts[i]
|
||||
hash = common.Hash{byte(i)}
|
||||
td = chain.tds[i]
|
||||
)
|
||||
if err = builder.AddRLP(header, body, receipts, uint64(i), hash, td, big.NewInt(1)); err != nil {
|
||||
t.Fatalf("error adding entry: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Finalize Era1.
|
||||
if _, err := builder.Finalize(); err != nil {
|
||||
t.Fatalf("error finalizing era1: %v", err)
|
||||
}
|
||||
|
||||
// Verify Era1 contents.
|
||||
e, err := Open(f.Name())
|
||||
if err != nil {
|
||||
t.Fatalf("failed to open era: %v", err)
|
||||
}
|
||||
it, err := NewRawIterator(e)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to make iterator: %s", err)
|
||||
}
|
||||
for i := uint64(0); i < uint64(len(chain.headers)); i++ {
|
||||
if !it.Next() {
|
||||
t.Fatalf("expected more entries")
|
||||
}
|
||||
if it.Error() != nil {
|
||||
t.Fatalf("unexpected error %v", it.Error())
|
||||
}
|
||||
// Check headers.
|
||||
header, err := io.ReadAll(it.Header)
|
||||
if err != nil {
|
||||
t.Fatalf("error reading header: %v", err)
|
||||
}
|
||||
if !bytes.Equal(header, chain.headers[i]) {
|
||||
t.Fatalf("mismatched header: want %s, got %s", chain.headers[i], header)
|
||||
}
|
||||
// Check bodies.
|
||||
body, err := io.ReadAll(it.Body)
|
||||
if err != nil {
|
||||
t.Fatalf("error reading body: %v", err)
|
||||
}
|
||||
if !bytes.Equal(body, chain.bodies[i]) {
|
||||
t.Fatalf("mismatched body: want %s, got %s", chain.bodies[i], body)
|
||||
}
|
||||
// Check receipts.
|
||||
receipts, err := io.ReadAll(it.Receipts)
|
||||
if err != nil {
|
||||
t.Fatalf("error reading receipts: %v", err)
|
||||
}
|
||||
if !bytes.Equal(receipts, chain.receipts[i]) {
|
||||
t.Fatalf("mismatched receipts: want %s, got %s", chain.receipts[i], receipts)
|
||||
}
|
||||
|
||||
// Check total difficulty.
|
||||
rawTd, err := io.ReadAll(it.TotalDifficulty)
|
||||
if err != nil {
|
||||
t.Fatalf("error reading td: %v", err)
|
||||
}
|
||||
td := new(big.Int).SetBytes(reverseOrder(rawTd))
|
||||
if td.Cmp(chain.tds[i]) != 0 {
|
||||
t.Fatalf("mismatched tds: want %s, got %s", chain.tds[i], td)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestEraFilename(t *testing.T) {
|
||||
for i, tt := range []struct {
|
||||
network string
|
||||
epoch int
|
||||
root common.Hash
|
||||
expected string
|
||||
}{
|
||||
{"mainnet", 1, common.Hash{1}, "mainnet-00001-01000000.era1"},
|
||||
{"goerli", 99999, common.HexToHash("0xdeadbeef00000000000000000000000000000000000000000000000000000000"), "goerli-99999-deadbeef.era1"},
|
||||
} {
|
||||
got := Filename(tt.network, tt.epoch, tt.root)
|
||||
if tt.expected != got {
|
||||
t.Errorf("test %d: invalid filename: want %s, got %s", i, tt.expected, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
197
internal/era/iterator.go
Normal file
197
internal/era/iterator.go
Normal file
|
|
@ -0,0 +1,197 @@
|
|||
// Copyright 2023 The go-ethereum Authors
|
||||
// This file is part of go-ethereum.
|
||||
//
|
||||
// go-ethereum is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// go-ethereum is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package era
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"math/big"
|
||||
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
)
|
||||
|
||||
// Iterator wraps RawIterator and returns decoded Era1 entries.
|
||||
type Iterator struct {
|
||||
inner *RawIterator
|
||||
}
|
||||
|
||||
// NewRawIterator 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)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Iterator{inner}, nil
|
||||
}
|
||||
|
||||
// Next moves the iterator to the next block entry. It returns false when all
|
||||
// items have been read or an error has halted its progress. Block, Receipts,
|
||||
// and BlockAndReceipts should no longer be called after false is returned.
|
||||
func (it *Iterator) Next() bool {
|
||||
return it.inner.Next()
|
||||
}
|
||||
|
||||
// Number returns the current number block the iterator will return.
|
||||
func (it *Iterator) Number() uint64 {
|
||||
return it.inner.next - 1
|
||||
}
|
||||
|
||||
// Error returns the error status of the iterator. It should be called before
|
||||
// reading from any of the iterator's values.
|
||||
func (it *Iterator) Error() error {
|
||||
return it.inner.Error()
|
||||
}
|
||||
|
||||
// Block returns the block for the iterator's current position.
|
||||
func (it *Iterator) Block() (*types.Block, error) {
|
||||
if it.inner.Header == nil || it.inner.Body == nil {
|
||||
return nil, fmt.Errorf("header and body must be non-nil")
|
||||
}
|
||||
var (
|
||||
header types.Header
|
||||
body types.Body
|
||||
)
|
||||
if err := rlp.Decode(it.inner.Header, &header); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := rlp.Decode(it.inner.Body, &body); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return types.NewBlockWithHeader(&header).WithBody(body.Transactions, body.Uncles), nil
|
||||
}
|
||||
|
||||
// Receipts returns the receipts for the iterator's current position.
|
||||
func (it *Iterator) Receipts() (types.Receipts, error) {
|
||||
if it.inner.Receipts == nil {
|
||||
return nil, fmt.Errorf("receipts must be non-nil")
|
||||
}
|
||||
var receipts types.Receipts
|
||||
err := rlp.Decode(it.inner.Receipts, &receipts)
|
||||
return receipts, err
|
||||
}
|
||||
|
||||
// BlockAndReceipts returns the block and receipts for the iterator's current
|
||||
// position.
|
||||
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 total difficulty for the iterator's current
|
||||
// position.
|
||||
func (it *Iterator) TotalDifficulty() (*big.Int, error) {
|
||||
td, err := io.ReadAll(it.inner.TotalDifficulty)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return new(big.Int).SetBytes(reverseOrder(td)), nil
|
||||
}
|
||||
|
||||
// RawIterator reads an RLP-encode Era1 entries.
|
||||
type RawIterator struct {
|
||||
e *Era // backing Era1
|
||||
next uint64 // next block to read
|
||||
err error // last error
|
||||
|
||||
Header io.Reader
|
||||
Body io.Reader
|
||||
Receipts io.Reader
|
||||
TotalDifficulty io.Reader
|
||||
}
|
||||
|
||||
// NewRawIterator returns a new RawIterator instance. Next must be immediately
|
||||
// called on new iterators to load the first item.
|
||||
func NewRawIterator(e *Era) (*RawIterator, error) {
|
||||
return &RawIterator{
|
||||
e: e,
|
||||
next: e.m.start,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Next moves the iterator to the next block entry. It returns false when all
|
||||
// items have been read or an error has halted its progress. Header, Body,
|
||||
// Receipts, TotalDifficulty will be set to nil in the case returning false or
|
||||
// finding an error and should therefore no longer be read from.
|
||||
func (it *RawIterator) Next() bool {
|
||||
// Clear old errors.
|
||||
it.err = nil
|
||||
if it.e.m.start+it.e.m.count <= it.next {
|
||||
it.clear()
|
||||
return false
|
||||
}
|
||||
off, err := it.e.readOffset(it.next)
|
||||
if err != nil {
|
||||
// Error here means block index is corrupted, so don't
|
||||
// continue.
|
||||
it.clear()
|
||||
it.err = err
|
||||
return false
|
||||
}
|
||||
var n int64
|
||||
if it.Header, n, it.err = newSnappyReader(it.e.s, 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 {
|
||||
it.clear()
|
||||
return true
|
||||
}
|
||||
off += n
|
||||
if it.Receipts, n, it.err = newSnappyReader(it.e.s, 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 {
|
||||
it.clear()
|
||||
return true
|
||||
}
|
||||
it.next += 1
|
||||
return true
|
||||
}
|
||||
|
||||
// Number returns the current number block the iterator will return.
|
||||
func (it *RawIterator) Number() uint64 {
|
||||
return it.next - 1
|
||||
}
|
||||
|
||||
// Error returns the error status of the iterator. It should be called before
|
||||
// reading from any of the iterator's values.
|
||||
func (it *RawIterator) Error() error {
|
||||
if it.err == io.EOF {
|
||||
return nil
|
||||
}
|
||||
return it.err
|
||||
}
|
||||
|
||||
// clear sets all the outputs to nil.
|
||||
func (it *RawIterator) clear() {
|
||||
it.Header = nil
|
||||
it.Body = nil
|
||||
it.Receipts = nil
|
||||
it.TotalDifficulty = nil
|
||||
}
|
||||
|
|
@ -655,7 +655,7 @@ func (s *BlockChainAPI) GetBalance(ctx context.Context, address common.Address,
|
|||
return (*hexutil.Big)(b), state.Error()
|
||||
}
|
||||
|
||||
// Result structs for GetProof
|
||||
// AccountResult structs for GetProof
|
||||
type AccountResult struct {
|
||||
Address common.Address `json:"address"`
|
||||
AccountProof []string `json:"accountProof"`
|
||||
|
|
@ -1812,13 +1812,14 @@ func (s *TransactionAPI) SendTransaction(ctx context.Context, args TransactionAr
|
|||
// on a given unsigned transaction, and returns it to the caller for further
|
||||
// processing (signing + broadcast).
|
||||
func (s *TransactionAPI) FillTransaction(ctx context.Context, args TransactionArgs) (*SignTransactionResult, error) {
|
||||
args.blobSidecarAllowed = true
|
||||
|
||||
// Set some sanity defaults and terminate on failure
|
||||
if err := args.setDefaults(ctx, s.b); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Assemble the transaction and obtain rlp
|
||||
tx := args.toTransaction()
|
||||
// TODO(s1na): fill in blob proofs, commitments
|
||||
data, err := tx.MarshalBinary()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ import (
|
|||
"bytes"
|
||||
"context"
|
||||
"crypto/ecdsa"
|
||||
"crypto/sha256"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
|
@ -45,6 +46,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/kzg4844"
|
||||
"github.com/ethereum/go-ethereum/ethdb"
|
||||
"github.com/ethereum/go-ethereum/event"
|
||||
"github.com/ethereum/go-ethereum/internal/blocktest"
|
||||
|
|
@ -1079,6 +1081,195 @@ func TestSendBlobTransaction(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestFillBlobTransaction(t *testing.T) {
|
||||
t.Parallel()
|
||||
// Initialize test accounts
|
||||
var (
|
||||
key, _ = crypto.HexToECDSA("8a1f9a8f95be41cd7ccb6168179afb4504aefe388d1e14474d32c45c72ce7b7a")
|
||||
to = crypto.PubkeyToAddress(key.PublicKey)
|
||||
genesis = &core.Genesis{
|
||||
Config: params.MergedTestChainConfig,
|
||||
Alloc: core.GenesisAlloc{},
|
||||
}
|
||||
emptyBlob = kzg4844.Blob{}
|
||||
emptyBlobCommit, _ = kzg4844.BlobToCommitment(emptyBlob)
|
||||
emptyBlobProof, _ = kzg4844.ComputeBlobProof(emptyBlob, emptyBlobCommit)
|
||||
emptyBlobHash common.Hash = kzg4844.CalcBlobHashV1(sha256.New(), &emptyBlobCommit)
|
||||
)
|
||||
b := newTestBackend(t, 1, genesis, beacon.New(ethash.NewFaker()), func(i int, b *core.BlockGen) {
|
||||
b.SetPoS()
|
||||
})
|
||||
api := NewTransactionAPI(b, nil)
|
||||
type result struct {
|
||||
Hashes []common.Hash
|
||||
Sidecar *types.BlobTxSidecar
|
||||
}
|
||||
suite := []struct {
|
||||
name string
|
||||
args TransactionArgs
|
||||
err string
|
||||
want *result
|
||||
}{
|
||||
{
|
||||
name: "TestInvalidParamsCombination1",
|
||||
args: TransactionArgs{
|
||||
From: &b.acc.Address,
|
||||
To: &to,
|
||||
Value: (*hexutil.Big)(big.NewInt(1)),
|
||||
Blobs: []kzg4844.Blob{{}},
|
||||
Proofs: []kzg4844.Proof{{}},
|
||||
},
|
||||
err: `blob proofs provided while commitments were not`,
|
||||
},
|
||||
{
|
||||
name: "TestInvalidParamsCombination2",
|
||||
args: TransactionArgs{
|
||||
From: &b.acc.Address,
|
||||
To: &to,
|
||||
Value: (*hexutil.Big)(big.NewInt(1)),
|
||||
Blobs: []kzg4844.Blob{{}},
|
||||
Commitments: []kzg4844.Commitment{{}},
|
||||
},
|
||||
err: `blob commitments provided while proofs were not`,
|
||||
},
|
||||
{
|
||||
name: "TestInvalidParamsCount1",
|
||||
args: TransactionArgs{
|
||||
From: &b.acc.Address,
|
||||
To: &to,
|
||||
Value: (*hexutil.Big)(big.NewInt(1)),
|
||||
Blobs: []kzg4844.Blob{{}},
|
||||
Commitments: []kzg4844.Commitment{{}, {}},
|
||||
Proofs: []kzg4844.Proof{{}, {}},
|
||||
},
|
||||
err: `number of blobs and commitments mismatch (have=2, want=1)`,
|
||||
},
|
||||
{
|
||||
name: "TestInvalidParamsCount2",
|
||||
args: TransactionArgs{
|
||||
From: &b.acc.Address,
|
||||
To: &to,
|
||||
Value: (*hexutil.Big)(big.NewInt(1)),
|
||||
Blobs: []kzg4844.Blob{{}, {}},
|
||||
Commitments: []kzg4844.Commitment{{}, {}},
|
||||
Proofs: []kzg4844.Proof{{}},
|
||||
},
|
||||
err: `number of blobs and proofs mismatch (have=1, want=2)`,
|
||||
},
|
||||
{
|
||||
name: "TestInvalidProofVerification",
|
||||
args: TransactionArgs{
|
||||
From: &b.acc.Address,
|
||||
To: &to,
|
||||
Value: (*hexutil.Big)(big.NewInt(1)),
|
||||
Blobs: []kzg4844.Blob{{}, {}},
|
||||
Commitments: []kzg4844.Commitment{{}, {}},
|
||||
Proofs: []kzg4844.Proof{{}, {}},
|
||||
},
|
||||
err: `failed to verify blob proof: short buffer`,
|
||||
},
|
||||
{
|
||||
name: "TestGenerateBlobHashes",
|
||||
args: TransactionArgs{
|
||||
From: &b.acc.Address,
|
||||
To: &to,
|
||||
Value: (*hexutil.Big)(big.NewInt(1)),
|
||||
Blobs: []kzg4844.Blob{emptyBlob},
|
||||
Commitments: []kzg4844.Commitment{emptyBlobCommit},
|
||||
Proofs: []kzg4844.Proof{emptyBlobProof},
|
||||
},
|
||||
want: &result{
|
||||
Hashes: []common.Hash{emptyBlobHash},
|
||||
Sidecar: &types.BlobTxSidecar{
|
||||
Blobs: []kzg4844.Blob{emptyBlob},
|
||||
Commitments: []kzg4844.Commitment{emptyBlobCommit},
|
||||
Proofs: []kzg4844.Proof{emptyBlobProof},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "TestValidBlobHashes",
|
||||
args: TransactionArgs{
|
||||
From: &b.acc.Address,
|
||||
To: &to,
|
||||
Value: (*hexutil.Big)(big.NewInt(1)),
|
||||
BlobHashes: []common.Hash{emptyBlobHash},
|
||||
Blobs: []kzg4844.Blob{emptyBlob},
|
||||
Commitments: []kzg4844.Commitment{emptyBlobCommit},
|
||||
Proofs: []kzg4844.Proof{emptyBlobProof},
|
||||
},
|
||||
want: &result{
|
||||
Hashes: []common.Hash{emptyBlobHash},
|
||||
Sidecar: &types.BlobTxSidecar{
|
||||
Blobs: []kzg4844.Blob{emptyBlob},
|
||||
Commitments: []kzg4844.Commitment{emptyBlobCommit},
|
||||
Proofs: []kzg4844.Proof{emptyBlobProof},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "TestInvalidBlobHashes",
|
||||
args: TransactionArgs{
|
||||
From: &b.acc.Address,
|
||||
To: &to,
|
||||
Value: (*hexutil.Big)(big.NewInt(1)),
|
||||
BlobHashes: []common.Hash{{0x01, 0x22}},
|
||||
Blobs: []kzg4844.Blob{emptyBlob},
|
||||
Commitments: []kzg4844.Commitment{emptyBlobCommit},
|
||||
Proofs: []kzg4844.Proof{emptyBlobProof},
|
||||
},
|
||||
err: fmt.Sprintf("blob hash verification failed (have=%s, want=%s)", common.Hash{0x01, 0x22}, emptyBlobHash),
|
||||
},
|
||||
{
|
||||
name: "TestGenerateBlobProofs",
|
||||
args: TransactionArgs{
|
||||
From: &b.acc.Address,
|
||||
To: &to,
|
||||
Value: (*hexutil.Big)(big.NewInt(1)),
|
||||
Blobs: []kzg4844.Blob{emptyBlob},
|
||||
},
|
||||
want: &result{
|
||||
Hashes: []common.Hash{emptyBlobHash},
|
||||
Sidecar: &types.BlobTxSidecar{
|
||||
Blobs: []kzg4844.Blob{emptyBlob},
|
||||
Commitments: []kzg4844.Commitment{emptyBlobCommit},
|
||||
Proofs: []kzg4844.Proof{emptyBlobProof},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tc := range suite {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
res, err := api.FillTransaction(context.Background(), tc.args)
|
||||
if len(tc.err) > 0 {
|
||||
if err == nil {
|
||||
t.Fatalf("missing error. want: %s", tc.err)
|
||||
} else if err != nil && err.Error() != tc.err {
|
||||
t.Fatalf("error mismatch. want: %s, have: %s", tc.err, err.Error())
|
||||
}
|
||||
return
|
||||
}
|
||||
if err != nil && len(tc.err) == 0 {
|
||||
t.Fatalf("expected no error. have: %s", err)
|
||||
}
|
||||
if res == nil {
|
||||
t.Fatal("result missing")
|
||||
}
|
||||
want, err := json.Marshal(tc.want)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to encode expected: %v", err)
|
||||
}
|
||||
have, err := json.Marshal(result{Hashes: res.Tx.BlobHashes(), Sidecar: res.Tx.BlobTxSidecar()})
|
||||
if err != nil {
|
||||
t.Fatalf("failed to encode computed sidecar: %v", err)
|
||||
}
|
||||
if !bytes.Equal(have, want) {
|
||||
t.Errorf("blob sidecar mismatch. Have: %s, want: %s", have, want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func argsFromTransaction(tx *types.Transaction, from common.Address) TransactionArgs {
|
||||
var (
|
||||
gas = tx.Gas()
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ package ethapi
|
|||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
|
|
@ -29,11 +30,17 @@ import (
|
|||
"github.com/ethereum/go-ethereum/consensus/misc/eip4844"
|
||||
"github.com/ethereum/go-ethereum/core"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/crypto/kzg4844"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"github.com/ethereum/go-ethereum/rpc"
|
||||
"github.com/holiman/uint256"
|
||||
)
|
||||
|
||||
var (
|
||||
maxBlobsPerTransaction = params.MaxBlobGasPerBlock / params.BlobTxBlobGasPerBlob
|
||||
)
|
||||
|
||||
// TransactionArgs represents the arguments to construct a new transaction
|
||||
// or a message call.
|
||||
type TransactionArgs struct {
|
||||
|
|
@ -56,9 +63,17 @@ type TransactionArgs struct {
|
|||
AccessList *types.AccessList `json:"accessList,omitempty"`
|
||||
ChainID *hexutil.Big `json:"chainId,omitempty"`
|
||||
|
||||
// Introduced by EIP-4844.
|
||||
// For BlobTxType
|
||||
BlobFeeCap *hexutil.Big `json:"maxFeePerBlobGas"`
|
||||
BlobHashes []common.Hash `json:"blobVersionedHashes,omitempty"`
|
||||
|
||||
// For BlobTxType transactions with blob sidecar
|
||||
Blobs []kzg4844.Blob `json:"blobs"`
|
||||
Commitments []kzg4844.Commitment `json:"commitments"`
|
||||
Proofs []kzg4844.Proof `json:"proofs"`
|
||||
|
||||
// This configures whether blobs are allowed to be passed.
|
||||
blobSidecarAllowed bool
|
||||
}
|
||||
|
||||
// from retrieves the transaction sender address.
|
||||
|
|
@ -82,9 +97,13 @@ func (args *TransactionArgs) data() []byte {
|
|||
|
||||
// setDefaults fills in default values for unspecified tx fields.
|
||||
func (args *TransactionArgs) setDefaults(ctx context.Context, b Backend) error {
|
||||
if err := args.setBlobTxSidecar(ctx, b); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := args.setFeeDefaults(ctx, b); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if args.Value == nil {
|
||||
args.Value = new(hexutil.Big)
|
||||
}
|
||||
|
|
@ -98,15 +117,25 @@ func (args *TransactionArgs) setDefaults(ctx context.Context, b Backend) error {
|
|||
if args.Data != nil && args.Input != nil && !bytes.Equal(*args.Data, *args.Input) {
|
||||
return errors.New(`both "data" and "input" are set and not equal. Please use "input" to pass transaction call data`)
|
||||
}
|
||||
if args.BlobHashes != nil && args.To == nil {
|
||||
return errors.New(`blob transactions cannot have the form of a create transaction`)
|
||||
}
|
||||
|
||||
// BlobTx fields
|
||||
if args.BlobHashes != nil && len(args.BlobHashes) == 0 {
|
||||
return errors.New(`need at least 1 blob for a blob transaction`)
|
||||
}
|
||||
if args.To == nil && len(args.data()) == 0 {
|
||||
return errors.New(`contract creation without any data provided`)
|
||||
if args.BlobHashes != nil && len(args.BlobHashes) > maxBlobsPerTransaction {
|
||||
return fmt.Errorf(`too many blobs in transaction (have=%d, max=%d)`, len(args.BlobHashes), maxBlobsPerTransaction)
|
||||
}
|
||||
|
||||
// create check
|
||||
if args.To == nil {
|
||||
if args.BlobHashes != nil {
|
||||
return errors.New(`missing "to" in blob transaction`)
|
||||
}
|
||||
if len(args.data()) == 0 {
|
||||
return errors.New(`contract creation without any data provided`)
|
||||
}
|
||||
}
|
||||
|
||||
// Estimate the gas usage if necessary.
|
||||
if args.Gas == nil {
|
||||
// These fields are immutable during the estimation, safe to
|
||||
|
|
@ -121,6 +150,8 @@ func (args *TransactionArgs) setDefaults(ctx context.Context, b Backend) error {
|
|||
Value: args.Value,
|
||||
Data: (*hexutil.Bytes)(&data),
|
||||
AccessList: args.AccessList,
|
||||
BlobFeeCap: args.BlobFeeCap,
|
||||
BlobHashes: args.BlobHashes,
|
||||
}
|
||||
latestBlockNr := rpc.BlockNumberOrHashWithNumber(rpc.LatestBlockNumber)
|
||||
estimated, err := DoEstimateGas(ctx, b, callArgs, latestBlockNr, nil, b.RPCGasCap())
|
||||
|
|
@ -130,6 +161,7 @@ func (args *TransactionArgs) setDefaults(ctx context.Context, b Backend) error {
|
|||
args.Gas = &estimated
|
||||
log.Trace("Estimate gas usage automatically", "gas", args.Gas)
|
||||
}
|
||||
|
||||
// If chain id is provided, ensure it matches the local chain id. Otherwise, set the local
|
||||
// chain id as the default.
|
||||
want := b.ChainConfig().ChainID
|
||||
|
|
@ -165,10 +197,12 @@ func (args *TransactionArgs) setFeeDefaults(ctx context.Context, b Backend) erro
|
|||
}
|
||||
return nil // No need to set anything, user already set MaxFeePerGas and MaxPriorityFeePerGas
|
||||
}
|
||||
|
||||
// Sanity check the EIP-4844 fee parameters.
|
||||
if args.BlobFeeCap != nil && args.BlobFeeCap.ToInt().Sign() == 0 {
|
||||
return errors.New("maxFeePerBlobGas must be non-zero")
|
||||
}
|
||||
|
||||
// Sanity check the non-EIP-1559 fee parameters.
|
||||
head := b.CurrentHeader()
|
||||
isLondon := b.ChainConfig().IsLondon(head.Number)
|
||||
|
|
@ -250,6 +284,81 @@ func (args *TransactionArgs) setLondonFeeDefaults(ctx context.Context, head *typ
|
|||
return nil
|
||||
}
|
||||
|
||||
// setBlobTxSidecar adds the blob tx
|
||||
func (args *TransactionArgs) setBlobTxSidecar(ctx context.Context, b Backend) error {
|
||||
// No blobs, we're done.
|
||||
if args.Blobs == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Passing blobs is not allowed in all contexts, only in specific methods.
|
||||
if !args.blobSidecarAllowed {
|
||||
return errors.New(`"blobs" is not supported for this RPC method`)
|
||||
}
|
||||
|
||||
n := len(args.Blobs)
|
||||
// Assume user provides either only blobs (w/o hashes), or
|
||||
// blobs together with commitments and proofs.
|
||||
if args.Commitments == nil && args.Proofs != nil {
|
||||
return errors.New(`blob proofs provided while commitments were not`)
|
||||
} else if args.Commitments != nil && args.Proofs == nil {
|
||||
return errors.New(`blob commitments provided while proofs were not`)
|
||||
}
|
||||
|
||||
// len(blobs) == len(commitments) == len(proofs) == len(hashes)
|
||||
if args.Commitments != nil && len(args.Commitments) != n {
|
||||
return fmt.Errorf("number of blobs and commitments mismatch (have=%d, want=%d)", len(args.Commitments), n)
|
||||
}
|
||||
if args.Proofs != nil && len(args.Proofs) != n {
|
||||
return fmt.Errorf("number of blobs and proofs mismatch (have=%d, want=%d)", len(args.Proofs), n)
|
||||
}
|
||||
if args.BlobHashes != nil && len(args.BlobHashes) != n {
|
||||
return fmt.Errorf("number of blobs and hashes mismatch (have=%d, want=%d)", len(args.BlobHashes), n)
|
||||
}
|
||||
|
||||
if args.Commitments == nil {
|
||||
// Generate commitment and proof.
|
||||
commitments := make([]kzg4844.Commitment, n)
|
||||
proofs := make([]kzg4844.Proof, n)
|
||||
for i, b := range args.Blobs {
|
||||
c, err := kzg4844.BlobToCommitment(b)
|
||||
if err != nil {
|
||||
return fmt.Errorf("blobs[%d]: error computing commitment: %v", i, err)
|
||||
}
|
||||
commitments[i] = c
|
||||
p, err := kzg4844.ComputeBlobProof(b, c)
|
||||
if err != nil {
|
||||
return fmt.Errorf("blobs[%d]: error computing proof: %v", i, err)
|
||||
}
|
||||
proofs[i] = p
|
||||
}
|
||||
args.Commitments = commitments
|
||||
args.Proofs = proofs
|
||||
} else {
|
||||
for i, b := range args.Blobs {
|
||||
if err := kzg4844.VerifyBlobProof(b, args.Commitments[i], args.Proofs[i]); err != nil {
|
||||
return fmt.Errorf("failed to verify blob proof: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
hashes := make([]common.Hash, n)
|
||||
hasher := sha256.New()
|
||||
for i, c := range args.Commitments {
|
||||
hashes[i] = kzg4844.CalcBlobHashV1(hasher, &c)
|
||||
}
|
||||
if args.BlobHashes != nil {
|
||||
for i, h := range hashes {
|
||||
if h != args.BlobHashes[i] {
|
||||
return fmt.Errorf("blob hash verification failed (have=%s, want=%s)", args.BlobHashes[i], h)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
args.BlobHashes = hashes
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ToMessage converts the transaction arguments to the Message type used by the
|
||||
// core evm. This method is used in calls and traces that do not require a real
|
||||
// live transaction.
|
||||
|
|
@ -363,6 +472,14 @@ func (args *TransactionArgs) toTransaction() *types.Transaction {
|
|||
BlobHashes: args.BlobHashes,
|
||||
BlobFeeCap: uint256.MustFromBig((*big.Int)(args.BlobFeeCap)),
|
||||
}
|
||||
if args.Blobs != nil {
|
||||
data.(*types.BlobTx).Sidecar = &types.BlobTxSidecar{
|
||||
Blobs: args.Blobs,
|
||||
Commitments: args.Commitments,
|
||||
Proofs: args.Proofs,
|
||||
}
|
||||
}
|
||||
|
||||
case args.MaxFeePerGas != nil:
|
||||
al := types.AccessList{}
|
||||
if args.AccessList != nil {
|
||||
|
|
@ -379,6 +496,7 @@ func (args *TransactionArgs) toTransaction() *types.Transaction {
|
|||
Data: args.data(),
|
||||
AccessList: al,
|
||||
}
|
||||
|
||||
case args.AccessList != nil:
|
||||
data = &types.AccessListTx{
|
||||
To: args.To,
|
||||
|
|
@ -390,6 +508,7 @@ func (args *TransactionArgs) toTransaction() *types.Transaction {
|
|||
Data: args.data(),
|
||||
AccessList: *args.AccessList,
|
||||
}
|
||||
|
||||
default:
|
||||
data = &types.LegacyTx{
|
||||
To: args.To,
|
||||
|
|
@ -403,12 +522,6 @@ func (args *TransactionArgs) toTransaction() *types.Transaction {
|
|||
return types.NewTx(data)
|
||||
}
|
||||
|
||||
// ToTransaction converts the arguments to a transaction.
|
||||
// This assumes that setDefaults has been called.
|
||||
func (args *TransactionArgs) ToTransaction() *types.Transaction {
|
||||
return args.toTransaction()
|
||||
}
|
||||
|
||||
// IsEIP4844 returns an indicator if the args contains EIP4844 fields.
|
||||
func (args *TransactionArgs) IsEIP4844() bool {
|
||||
return args.BlobHashes != nil || args.BlobFeeCap != nil
|
||||
|
|
|
|||
|
|
@ -192,7 +192,7 @@ func (h *GlogHandler) Handle(_ context.Context, r slog.Record) error {
|
|||
frame, _ := fs.Next()
|
||||
|
||||
for _, rule := range h.patterns {
|
||||
if rule.pattern.MatchString(fmt.Sprintf("%+s", frame.File)) {
|
||||
if rule.pattern.MatchString(fmt.Sprintf("+%s", frame.File)) {
|
||||
h.siteCache[r.PC], lvl, ok = rule.level, rule.level, true
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ type CounterSnapshot interface {
|
|||
Count() int64
|
||||
}
|
||||
|
||||
// Counters hold an int64 value that can be incremented and decremented.
|
||||
// Counter hold an int64 value that can be incremented and decremented.
|
||||
type Counter interface {
|
||||
Clear()
|
||||
Dec(int64)
|
||||
|
|
|
|||
|
|
@ -2,12 +2,12 @@ package metrics
|
|||
|
||||
import "sync/atomic"
|
||||
|
||||
// gaugeSnapshot contains a readonly int64.
|
||||
// GaugeSnapshot contains a readonly int64.
|
||||
type GaugeSnapshot interface {
|
||||
Value() int64
|
||||
}
|
||||
|
||||
// Gauges hold an int64 value that can be set arbitrarily.
|
||||
// Gauge holds an int64 value that can be set arbitrarily.
|
||||
type Gauge interface {
|
||||
Snapshot() GaugeSnapshot
|
||||
Update(int64)
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@ type gaugeFloat64Snapshot float64
|
|||
// Value returns the value at the time the snapshot was taken.
|
||||
func (g gaugeFloat64Snapshot) Value() float64 { return float64(g) }
|
||||
|
||||
// NilGauge is a no-op Gauge.
|
||||
// NilGaugeFloat64 is a no-op Gauge.
|
||||
type NilGaugeFloat64 struct{}
|
||||
|
||||
func (NilGaugeFloat64) Snapshot() GaugeFloat64Snapshot { return NilGaugeFloat64{} }
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ type GaugeInfoSnapshot interface {
|
|||
Value() GaugeInfoValue
|
||||
}
|
||||
|
||||
// GaugeInfos hold a GaugeInfoValue value that can be set arbitrarily.
|
||||
// GaugeInfo holds a GaugeInfoValue value that can be set arbitrarily.
|
||||
type GaugeInfo interface {
|
||||
Update(GaugeInfoValue)
|
||||
Snapshot() GaugeInfoSnapshot
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
package metrics
|
||||
|
||||
// Healthchecks hold an error value describing an arbitrary up/down status.
|
||||
// Healthcheck holds an error value describing an arbitrary up/down status.
|
||||
type Healthcheck interface {
|
||||
Check()
|
||||
Error() error
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ type HistogramSnapshot interface {
|
|||
SampleSnapshot
|
||||
}
|
||||
|
||||
// Histograms calculate distribution statistics from a series of int64 values.
|
||||
// Histogram calculates distribution statistics from a series of int64 values.
|
||||
type Histogram interface {
|
||||
Clear()
|
||||
Update(int64)
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ type v2Reporter struct {
|
|||
write api.WriteAPI
|
||||
}
|
||||
|
||||
// InfluxDBWithTags starts a InfluxDB reporter which will post the from the given metrics.Registry at each d interval with the specified tags
|
||||
// InfluxDBV2WithTags starts a InfluxDB reporter which will post the from the given metrics.Registry at each d interval with the specified tags
|
||||
func InfluxDBV2WithTags(r metrics.Registry, d time.Duration, endpoint string, token string, bucket string, organization string, namespace string, tags map[string]string) {
|
||||
rep := &v2Reporter{
|
||||
reg: r,
|
||||
|
|
|
|||
|
|
@ -41,6 +41,7 @@ const (
|
|||
// needs of all CLs.
|
||||
engineAPIBatchItemLimit = 2000
|
||||
engineAPIBatchResponseSizeLimit = 250 * 1000 * 1000
|
||||
engineAPIBodyLimit = 128 * 1024 * 1024
|
||||
)
|
||||
|
||||
var (
|
||||
|
|
|
|||
|
|
@ -453,14 +453,16 @@ func (n *Node) startRPC() error {
|
|||
jwtSecret: secret,
|
||||
batchItemLimit: engineAPIBatchItemLimit,
|
||||
batchResponseSizeLimit: engineAPIBatchResponseSizeLimit,
|
||||
httpBodyLimit: engineAPIBodyLimit,
|
||||
}
|
||||
if err := server.enableRPC(allAPIs, httpConfig{
|
||||
err := server.enableRPC(allAPIs, httpConfig{
|
||||
CorsAllowedOrigins: DefaultAuthCors,
|
||||
Vhosts: n.config.AuthVirtualHosts,
|
||||
Modules: DefaultAuthModules,
|
||||
prefix: DefaultAuthPrefix,
|
||||
rpcEndpointConfig: sharedConfig,
|
||||
}); err != nil {
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
servers = append(servers, server)
|
||||
|
|
|
|||
|
|
@ -56,6 +56,7 @@ type rpcEndpointConfig struct {
|
|||
jwtSecret []byte // optional JWT secret
|
||||
batchItemLimit int
|
||||
batchResponseSizeLimit int
|
||||
httpBodyLimit int
|
||||
}
|
||||
|
||||
type rpcHandler struct {
|
||||
|
|
@ -304,6 +305,9 @@ func (h *httpServer) enableRPC(apis []rpc.API, config httpConfig) error {
|
|||
// Create RPC server and handler.
|
||||
srv := rpc.NewServer()
|
||||
srv.SetBatchLimits(config.batchItemLimit, config.batchResponseSizeLimit)
|
||||
if config.httpBodyLimit > 0 {
|
||||
srv.SetHTTPBodyLimit(config.httpBodyLimit)
|
||||
}
|
||||
if err := RegisterApis(apis, config.Modules, srv); err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -336,6 +340,9 @@ func (h *httpServer) enableWS(apis []rpc.API, config wsConfig) error {
|
|||
// Create RPC server and handler.
|
||||
srv := rpc.NewServer()
|
||||
srv.SetBatchLimits(config.batchItemLimit, config.batchResponseSizeLimit)
|
||||
if config.httpBodyLimit > 0 {
|
||||
srv.SetHTTPBodyLimit(config.httpBodyLimit)
|
||||
}
|
||||
if err := RegisterApis(apis, config.Modules, srv); err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -58,7 +58,7 @@ func newMeteredConn(conn UDPConn) UDPConn {
|
|||
return &meteredUdpConn{UDPConn: conn}
|
||||
}
|
||||
|
||||
// Read delegates a network read to the underlying connection, bumping the udp ingress traffic meter along the way.
|
||||
// ReadFromUDP delegates a network read to the underlying connection, bumping the udp ingress traffic meter along the way.
|
||||
func (c *meteredUdpConn) ReadFromUDP(b []byte) (n int, addr *net.UDPAddr, err error) {
|
||||
n, addr, err = c.UDPConn.ReadFromUDP(b)
|
||||
ingressTrafficMeter.Mark(int64(n))
|
||||
|
|
|
|||
|
|
@ -58,6 +58,7 @@ var (
|
|||
TerminalTotalDifficulty: MainnetTerminalTotalDifficulty, // 58_750_000_000_000_000_000_000
|
||||
TerminalTotalDifficultyPassed: true,
|
||||
ShanghaiTime: newUint64(1681338455),
|
||||
CancunTime: newUint64(1710338135),
|
||||
Ethash: new(EthashConfig),
|
||||
}
|
||||
// HoleskyChainConfig contains the chain parameters to run a node on the Holesky test network.
|
||||
|
|
@ -642,7 +643,7 @@ func (c *ChainConfig) CheckConfigForkOrder() error {
|
|||
lastFork.name, cur.name, cur.block)
|
||||
} else {
|
||||
return fmt.Errorf("unsupported fork ordering: %v not enabled, but %v enabled at timestamp %v",
|
||||
lastFork.name, cur.name, cur.timestamp)
|
||||
lastFork.name, cur.name, *cur.timestamp)
|
||||
}
|
||||
|
||||
// Fork (whether defined by block or timestamp) must follow the fork definition sequence
|
||||
|
|
@ -652,7 +653,7 @@ func (c *ChainConfig) CheckConfigForkOrder() error {
|
|||
lastFork.name, lastFork.block, cur.name, cur.block)
|
||||
} else if lastFork.timestamp != nil && *lastFork.timestamp > *cur.timestamp {
|
||||
return fmt.Errorf("unsupported fork ordering: %v enabled at timestamp %v, but %v enabled at timestamp %v",
|
||||
lastFork.name, lastFork.timestamp, cur.name, cur.timestamp)
|
||||
lastFork.name, *lastFork.timestamp, cur.name, *cur.timestamp)
|
||||
}
|
||||
|
||||
// Timestamp based forks can follow block based ones, but not the other way around
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ import (
|
|||
const (
|
||||
VersionMajor = 1 // Major version component of the current release
|
||||
VersionMinor = 13 // Minor version component of the current release
|
||||
VersionPatch = 12 // Patch version component of the current release
|
||||
VersionPatch = 13 // Patch version component of the current release
|
||||
VersionMeta = "unstable" // Version metadata to append to the version string
|
||||
)
|
||||
|
||||
|
|
|
|||
18
rpc/http.go
18
rpc/http.go
|
|
@ -33,8 +33,8 @@ import (
|
|||
)
|
||||
|
||||
const (
|
||||
maxRequestContentLength = 1024 * 1024 * 5
|
||||
contentType = "application/json"
|
||||
defaultBodyLimit = 5 * 1024 * 1024
|
||||
contentType = "application/json"
|
||||
)
|
||||
|
||||
// https://www.jsonrpc.org/historical/json-rpc-over-http.html#id13
|
||||
|
|
@ -253,8 +253,8 @@ type httpServerConn struct {
|
|||
r *http.Request
|
||||
}
|
||||
|
||||
func newHTTPServerConn(r *http.Request, w http.ResponseWriter) ServerCodec {
|
||||
body := io.LimitReader(r.Body, maxRequestContentLength)
|
||||
func (s *Server) newHTTPServerConn(r *http.Request, w http.ResponseWriter) ServerCodec {
|
||||
body := io.LimitReader(r.Body, int64(s.httpBodyLimit))
|
||||
conn := &httpServerConn{Reader: body, Writer: w, r: r}
|
||||
|
||||
encoder := func(v any, isErrorResponse bool) error {
|
||||
|
|
@ -312,7 +312,7 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||
w.WriteHeader(http.StatusOK)
|
||||
return
|
||||
}
|
||||
if code, err := validateRequest(r); err != nil {
|
||||
if code, err := s.validateRequest(r); err != nil {
|
||||
http.Error(w, err.Error(), code)
|
||||
return
|
||||
}
|
||||
|
|
@ -330,19 +330,19 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||
// until EOF, writes the response to w, and orders the server to process a
|
||||
// single request.
|
||||
w.Header().Set("content-type", contentType)
|
||||
codec := newHTTPServerConn(r, w)
|
||||
codec := s.newHTTPServerConn(r, w)
|
||||
defer codec.close()
|
||||
s.serveSingleRequest(ctx, codec)
|
||||
}
|
||||
|
||||
// validateRequest returns a non-zero response code and error message if the
|
||||
// request is invalid.
|
||||
func validateRequest(r *http.Request) (int, error) {
|
||||
func (s *Server) validateRequest(r *http.Request) (int, error) {
|
||||
if r.Method == http.MethodPut || r.Method == http.MethodDelete {
|
||||
return http.StatusMethodNotAllowed, errors.New("method not allowed")
|
||||
}
|
||||
if r.ContentLength > maxRequestContentLength {
|
||||
err := fmt.Errorf("content length too large (%d>%d)", r.ContentLength, maxRequestContentLength)
|
||||
if r.ContentLength > int64(s.httpBodyLimit) {
|
||||
err := fmt.Errorf("content length too large (%d>%d)", r.ContentLength, s.httpBodyLimit)
|
||||
return http.StatusRequestEntityTooLarge, err
|
||||
}
|
||||
// Allow OPTIONS (regardless of content-type)
|
||||
|
|
|
|||
|
|
@ -40,11 +40,13 @@ func confirmStatusCode(t *testing.T, got, want int) {
|
|||
|
||||
func confirmRequestValidationCode(t *testing.T, method, contentType, body string, expectedStatusCode int) {
|
||||
t.Helper()
|
||||
|
||||
s := NewServer()
|
||||
request := httptest.NewRequest(method, "http://url.com", strings.NewReader(body))
|
||||
if len(contentType) > 0 {
|
||||
request.Header.Set("Content-Type", contentType)
|
||||
}
|
||||
code, err := validateRequest(request)
|
||||
code, err := s.validateRequest(request)
|
||||
if code == 0 {
|
||||
if err != nil {
|
||||
t.Errorf("validation: got error %v, expected nil", err)
|
||||
|
|
@ -64,7 +66,7 @@ func TestHTTPErrorResponseWithPut(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestHTTPErrorResponseWithMaxContentLength(t *testing.T) {
|
||||
body := make([]rune, maxRequestContentLength+1)
|
||||
body := make([]rune, defaultBodyLimit+1)
|
||||
confirmRequestValidationCode(t,
|
||||
http.MethodPost, contentType, string(body), http.StatusRequestEntityTooLarge)
|
||||
}
|
||||
|
|
@ -104,7 +106,7 @@ func TestHTTPResponseWithEmptyGet(t *testing.T) {
|
|||
|
||||
// This checks that maxRequestContentLength is not applied to the response of a request.
|
||||
func TestHTTPRespBodyUnlimited(t *testing.T) {
|
||||
const respLength = maxRequestContentLength * 3
|
||||
const respLength = defaultBodyLimit * 3
|
||||
|
||||
s := NewServer()
|
||||
defer s.Stop()
|
||||
|
|
|
|||
|
|
@ -51,13 +51,15 @@ type Server struct {
|
|||
run atomic.Bool
|
||||
batchItemLimit int
|
||||
batchResponseLimit int
|
||||
httpBodyLimit int
|
||||
}
|
||||
|
||||
// NewServer creates a new server instance with no registered handlers.
|
||||
func NewServer() *Server {
|
||||
server := &Server{
|
||||
idgen: randomIDGenerator(),
|
||||
codecs: make(map[ServerCodec]struct{}),
|
||||
idgen: randomIDGenerator(),
|
||||
codecs: make(map[ServerCodec]struct{}),
|
||||
httpBodyLimit: defaultBodyLimit,
|
||||
}
|
||||
server.run.Store(true)
|
||||
// Register the default service providing meta information about the RPC service such
|
||||
|
|
@ -78,6 +80,13 @@ func (s *Server) SetBatchLimits(itemLimit, maxResponseSize int) {
|
|||
s.batchResponseLimit = maxResponseSize
|
||||
}
|
||||
|
||||
// SetHTTPBodyLimit sets the size limit for HTTP requests.
|
||||
//
|
||||
// This method should be called before processing any requests via ServeHTTP.
|
||||
func (s *Server) SetHTTPBodyLimit(limit int) {
|
||||
s.httpBodyLimit = limit
|
||||
}
|
||||
|
||||
// RegisterName creates a service for the given receiver type under the given name. When no
|
||||
// methods on the given receiver match the criteria to be either a RPC method or a
|
||||
// subscription an error is returned. Otherwise a new service is created and added to the
|
||||
|
|
|
|||
|
|
@ -97,7 +97,7 @@ func TestWebsocketLargeCall(t *testing.T) {
|
|||
|
||||
// This call sends slightly less than the limit and should work.
|
||||
var result echoResult
|
||||
arg := strings.Repeat("x", maxRequestContentLength-200)
|
||||
arg := strings.Repeat("x", defaultBodyLimit-200)
|
||||
if err := client.Call(&result, "test_echo", arg, 1); err != nil {
|
||||
t.Fatalf("valid call didn't work: %v", err)
|
||||
}
|
||||
|
|
@ -106,7 +106,7 @@ func TestWebsocketLargeCall(t *testing.T) {
|
|||
}
|
||||
|
||||
// This call sends twice the allowed size and shouldn't work.
|
||||
arg = strings.Repeat("x", maxRequestContentLength*2)
|
||||
arg = strings.Repeat("x", defaultBodyLimit*2)
|
||||
err = client.Call(&result, "test_echo", arg)
|
||||
if err == nil {
|
||||
t.Fatal("no error for too large call")
|
||||
|
|
|
|||
Loading…
Reference in a new issue