mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-18 02:40:45 +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
|
// This file contains the implementation for interacting with the Trezor hardware
|
||||||
// wallets. The wire protocol spec can be found on the SatoshiLabs website:
|
// 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 !!!
|
// !!! STAHP !!!
|
||||||
//
|
//
|
||||||
|
|
|
||||||
|
|
@ -121,14 +121,13 @@ var (
|
||||||
// Note: vivid is unsupported because there is no golang-1.6 package for it.
|
// 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:
|
// Note: the following Ubuntu releases have been officially deprecated on Launchpad:
|
||||||
// wily, yakkety, zesty, artful, cosmic, disco, eoan, groovy, hirsuite, impish,
|
// wily, yakkety, zesty, artful, cosmic, disco, eoan, groovy, hirsuite, impish,
|
||||||
// kinetic
|
// kinetic, lunar
|
||||||
debDistroGoBoots = map[string]string{
|
debDistroGoBoots = map[string]string{
|
||||||
"trusty": "golang-1.11", // 14.04, EOL: 04/2024
|
"trusty": "golang-1.11", // 14.04, EOL: 04/2024
|
||||||
"xenial": "golang-go", // 16.04, EOL: 04/2026
|
"xenial": "golang-go", // 16.04, EOL: 04/2026
|
||||||
"bionic": "golang-go", // 18.04, EOL: 04/2028
|
"bionic": "golang-go", // 18.04, EOL: 04/2028
|
||||||
"focal": "golang-go", // 20.04, EOL: 04/2030
|
"focal": "golang-go", // 20.04, EOL: 04/2030
|
||||||
"jammy": "golang-go", // 22.04, EOL: 04/2032
|
"jammy": "golang-go", // 22.04, EOL: 04/2032
|
||||||
"lunar": "golang-go", // 23.04, EOL: 01/2024
|
|
||||||
"mantic": "golang-go", // 23.10, EOL: 07/2024
|
"mantic": "golang-go", // 23.10, EOL: 07/2024
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -166,7 +166,7 @@ func (c *Conn) ReadEth() (any, error) {
|
||||||
case eth.TransactionsMsg:
|
case eth.TransactionsMsg:
|
||||||
msg = new(eth.TransactionsPacket)
|
msg = new(eth.TransactionsPacket)
|
||||||
case eth.NewPooledTransactionHashesMsg:
|
case eth.NewPooledTransactionHashesMsg:
|
||||||
msg = new(eth.NewPooledTransactionHashesPacket68)
|
msg = new(eth.NewPooledTransactionHashesPacket)
|
||||||
case eth.GetPooledTransactionsMsg:
|
case eth.GetPooledTransactionsMsg:
|
||||||
msg = new(eth.GetPooledTransactionsPacket)
|
msg = new(eth.GetPooledTransactionsPacket)
|
||||||
case eth.PooledTransactionsMsg:
|
case eth.PooledTransactionsMsg:
|
||||||
|
|
|
||||||
|
|
@ -710,7 +710,7 @@ func (s *Suite) TestNewPooledTxs(t *utesting.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send announcement.
|
// 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)
|
err = conn.Write(ethProto, eth.NewPooledTransactionHashesMsg, ann)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed to write to connection: %v", err)
|
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))
|
t.Fatalf("unexpected number of txs requested: wanted %d, got %d", len(hashes), len(msg.GetPooledTransactionsRequest))
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
case *eth.NewPooledTransactionHashesPacket68:
|
case *eth.NewPooledTransactionHashesPacket:
|
||||||
continue
|
continue
|
||||||
case *eth.TransactionsPacket:
|
case *eth.TransactionsPacket:
|
||||||
continue
|
continue
|
||||||
|
|
@ -796,12 +796,12 @@ func (s *Suite) TestBlobViolations(t *utesting.T) {
|
||||||
t2 = s.makeBlobTxs(2, 3, 0x2)
|
t2 = s.makeBlobTxs(2, 3, 0x2)
|
||||||
)
|
)
|
||||||
for _, test := range []struct {
|
for _, test := range []struct {
|
||||||
ann eth.NewPooledTransactionHashesPacket68
|
ann eth.NewPooledTransactionHashesPacket
|
||||||
resp eth.PooledTransactionsResponse
|
resp eth.PooledTransactionsResponse
|
||||||
}{
|
}{
|
||||||
// Invalid tx size.
|
// Invalid tx size.
|
||||||
{
|
{
|
||||||
ann: eth.NewPooledTransactionHashesPacket68{
|
ann: eth.NewPooledTransactionHashesPacket{
|
||||||
Types: []byte{types.BlobTxType, types.BlobTxType},
|
Types: []byte{types.BlobTxType, types.BlobTxType},
|
||||||
Sizes: []uint32{uint32(t1[0].Size()), uint32(t1[1].Size() + 10)},
|
Sizes: []uint32{uint32(t1[0].Size()), uint32(t1[1].Size() + 10)},
|
||||||
Hashes: []common.Hash{t1[0].Hash(), t1[1].Hash()},
|
Hashes: []common.Hash{t1[0].Hash(), t1[1].Hash()},
|
||||||
|
|
@ -810,7 +810,7 @@ func (s *Suite) TestBlobViolations(t *utesting.T) {
|
||||||
},
|
},
|
||||||
// Wrong tx type.
|
// Wrong tx type.
|
||||||
{
|
{
|
||||||
ann: eth.NewPooledTransactionHashesPacket68{
|
ann: eth.NewPooledTransactionHashesPacket{
|
||||||
Types: []byte{types.DynamicFeeTxType, types.BlobTxType},
|
Types: []byte{types.DynamicFeeTxType, types.BlobTxType},
|
||||||
Sizes: []uint32{uint32(t2[0].Size()), uint32(t2[1].Size())},
|
Sizes: []uint32{uint32(t2[0].Size()), uint32(t2[1].Size())},
|
||||||
Hashes: []common.Hash{t2[0].Hash(), t2[1].Hash()},
|
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 {
|
for _, tx := range *msg {
|
||||||
got[tx.Hash()] = true
|
got[tx.Hash()] = true
|
||||||
}
|
}
|
||||||
case *eth.NewPooledTransactionHashesPacket68:
|
case *eth.NewPooledTransactionHashesPacket:
|
||||||
for _, hash := range msg.Hashes {
|
for _, hash := range msg.Hashes {
|
||||||
got[hash] = true
|
got[hash] = true
|
||||||
}
|
}
|
||||||
|
|
@ -146,7 +146,7 @@ func (s *Suite) sendInvalidTxs(txs []*types.Transaction) error {
|
||||||
return fmt.Errorf("received bad tx: %s", tx.Hash())
|
return fmt.Errorf("received bad tx: %s", tx.Hash())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case *eth.NewPooledTransactionHashesPacket68:
|
case *eth.NewPooledTransactionHashesPacket:
|
||||||
for _, hash := range msg.Hashes {
|
for _, hash := range msg.Hashes {
|
||||||
if _, ok := invalids[hash]; ok {
|
if _, ok := invalids[hash]; ok {
|
||||||
return fmt.Errorf("received bad tx: %s", hash)
|
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/core/types"
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
"github.com/ethereum/go-ethereum/ethdb"
|
"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/internal/flags"
|
||||||
"github.com/ethereum/go-ethereum/log"
|
"github.com/ethereum/go-ethereum/log"
|
||||||
"github.com/ethereum/go-ethereum/metrics"
|
"github.com/ethereum/go-ethereum/metrics"
|
||||||
"github.com/ethereum/go-ethereum/node"
|
"github.com/ethereum/go-ethereum/node"
|
||||||
|
"github.com/ethereum/go-ethereum/params"
|
||||||
"github.com/urfave/cli/v2"
|
"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
|
last block to write. In this mode, the file will be appended
|
||||||
if already existing. If the file ends with .gz, the output will
|
if already existing. If the file ends with .gz, the output will
|
||||||
be gzipped.`,
|
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{
|
importPreimagesCommand = &cli.Command{
|
||||||
Action: importPreimages,
|
Action: importPreimages,
|
||||||
|
|
@ -364,7 +393,97 @@ func exportChain(ctx *cli.Context) error {
|
||||||
}
|
}
|
||||||
err = utils.ExportAppendChain(chain, fp, uint64(first), uint64(last))
|
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 {
|
if err != nil {
|
||||||
utils.Fatalf("Export error: %v\n", err)
|
utils.Fatalf("Export error: %v\n", err)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -208,6 +208,8 @@ func init() {
|
||||||
initCommand,
|
initCommand,
|
||||||
importCommand,
|
importCommand,
|
||||||
exportCommand,
|
exportCommand,
|
||||||
|
importHistoryCommand,
|
||||||
|
exportHistoryCommand,
|
||||||
importPreimagesCommand,
|
importPreimagesCommand,
|
||||||
removedbCommand,
|
removedbCommand,
|
||||||
dumpCommand,
|
dumpCommand,
|
||||||
|
|
|
||||||
191
cmd/utils/cmd.go
191
cmd/utils/cmd.go
|
|
@ -19,12 +19,15 @@ package utils
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
|
"bytes"
|
||||||
"compress/gzip"
|
"compress/gzip"
|
||||||
|
"crypto/sha256"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
|
"path"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
@ -39,8 +42,10 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/eth/ethconfig"
|
"github.com/ethereum/go-ethereum/eth/ethconfig"
|
||||||
"github.com/ethereum/go-ethereum/ethdb"
|
"github.com/ethereum/go-ethereum/ethdb"
|
||||||
"github.com/ethereum/go-ethereum/internal/debug"
|
"github.com/ethereum/go-ethereum/internal/debug"
|
||||||
|
"github.com/ethereum/go-ethereum/internal/era"
|
||||||
"github.com/ethereum/go-ethereum/log"
|
"github.com/ethereum/go-ethereum/log"
|
||||||
"github.com/ethereum/go-ethereum/node"
|
"github.com/ethereum/go-ethereum/node"
|
||||||
|
"github.com/ethereum/go-ethereum/params"
|
||||||
"github.com/ethereum/go-ethereum/rlp"
|
"github.com/ethereum/go-ethereum/rlp"
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
)
|
)
|
||||||
|
|
@ -228,6 +233,105 @@ func ImportChain(chain *core.BlockChain, fn string) error {
|
||||||
return nil
|
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 {
|
func missingBlocks(chain *core.BlockChain, blocks []*types.Block) []*types.Block {
|
||||||
head := chain.CurrentBlock()
|
head := chain.CurrentBlock()
|
||||||
for i, block := range blocks {
|
for i, block := range blocks {
|
||||||
|
|
@ -297,6 +401,93 @@ func ExportAppendChain(blockchain *core.BlockChain, fn string, first uint64, las
|
||||||
return nil
|
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.
|
// 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.
|
// It's a part of the deprecated functionality, should be removed in the future.
|
||||||
func ImportPreimages(db ethdb.Database, fn string) error {
|
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
|
return bc.triedb
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// HeaderChain returns the underlying header chain.
|
||||||
|
func (bc *BlockChain) HeaderChain() *HeaderChain {
|
||||||
|
return bc.hc
|
||||||
|
}
|
||||||
|
|
||||||
// SubscribeRemovedLogsEvent registers a subscription of RemovedLogsEvent.
|
// SubscribeRemovedLogsEvent registers a subscription of RemovedLogsEvent.
|
||||||
func (bc *BlockChain) SubscribeRemovedLogsEvent(ch chan<- RemovedLogsEvent) event.Subscription {
|
func (bc *BlockChain) SubscribeRemovedLogsEvent(ch chan<- RemovedLogsEvent) event.Subscription {
|
||||||
return bc.scope.Track(bc.rmLogsFeed.Subscribe(ch))
|
return bc.scope.Track(bc.rmLogsFeed.Subscribe(ch))
|
||||||
|
|
|
||||||
|
|
@ -83,7 +83,7 @@ func (b *BlockGen) SetDifficulty(diff *big.Int) {
|
||||||
b.header.Difficulty = diff
|
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() {
|
func (b *BlockGen) SetPoS() {
|
||||||
b.header.Difficulty = new(big.Int)
|
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
|
{15049999, 0, ID{Hash: checksumToBytes(0x20c327fc), Next: 15050000}}, // Last Arrow Glacier block
|
||||||
{15050000, 0, ID{Hash: checksumToBytes(0xf0afd0e3), Next: 1681338455}}, // First Gray 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, 1681338454, ID{Hash: checksumToBytes(0xf0afd0e3), Next: 1681338455}}, // Last Gray Glacier block
|
||||||
{20000000, 1681338455, ID{Hash: checksumToBytes(0xdce96c2d), Next: 0}}, // First Shanghai block
|
{20000000, 1681338455, ID{Hash: checksumToBytes(0xdce96c2d), Next: 1710338135}}, // First Shanghai block
|
||||||
{30000000, 2000000000, ID{Hash: checksumToBytes(0xdce96c2d), Next: 0}}, // Future 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
|
// Goerli test cases
|
||||||
|
|
@ -141,6 +143,7 @@ func TestValidation(t *testing.T) {
|
||||||
// Config that has not timestamp enabled
|
// Config that has not timestamp enabled
|
||||||
legacyConfig := *params.MainnetChainConfig
|
legacyConfig := *params.MainnetChainConfig
|
||||||
legacyConfig.ShanghaiTime = nil
|
legacyConfig.ShanghaiTime = nil
|
||||||
|
legacyConfig.CancunTime = nil
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
config *params.ChainConfig
|
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.
|
// 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).
|
// 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},
|
{&legacyConfig, 88888888, 0, ID{Hash: checksumToBytes(0xf0afd0e3), Next: 88888888}, ErrLocalIncompatibleOrStale},
|
||||||
|
|
||||||
// Local is mainnet Byzantium. Remote is also in Byzantium, but announces Gopherium (non existing
|
// Local is mainnet Byzantium. Remote is also in Byzantium, but announces Gopherium (non existing
|
||||||
// fork) at block 7279999, before Petersburg. Local is incompatible.
|
// 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},
|
{&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
|
// 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).
|
// 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.
|
// In this case we don't know if Cancun passed yet or not.
|
||||||
//
|
{params.MainnetChainConfig, 20000000, 1668000000, ID{Hash: checksumToBytes(0xdce96c2d), Next: 0}, nil},
|
||||||
// TODO(karalabe): Enable this when Cancun is specced
|
|
||||||
//{params.MainnetChainConfig, 20000000, 1668000000, ID{Hash: checksumToBytes(0x71147644), Next: 0}, nil},
|
|
||||||
|
|
||||||
// Local is mainnet currently in Shanghai only (so it's aware of Cancun), remote announces
|
// 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
|
// 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.
|
// don't know if Cancun passed yet (will pass) or not.
|
||||||
//
|
{params.MainnetChainConfig, 20000000, 1668000000, ID{Hash: checksumToBytes(0xdce96c2d), Next: 1710338135}, nil},
|
||||||
// TODO(karalabe): Enable this when Cancun is specced and update next timestamp
|
|
||||||
//{params.MainnetChainConfig, 20000000, 1668000000, ID{Hash: checksumToBytes(0x71147644), Next: 1678000000}, nil},
|
|
||||||
|
|
||||||
// Local is mainnet currently in Shanghai only (so it's aware of Cancun), remote announces
|
// 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
|
// 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.
|
// neither forks passed at neither nodes, they may mismatch, but we still connect for now.
|
||||||
//
|
{params.MainnetChainConfig, 20000000, 1668000000, ID{Hash: checksumToBytes(0xdce96c2d), Next: math.MaxUint64}, nil},
|
||||||
// TODO(karalabe): Enable this when Cancun is specced
|
|
||||||
//{params.MainnetChainConfig, 20000000, 1668000000, ID{Hash: checksumToBytes(0x71147644), Next: math.MaxUint64}, nil},
|
|
||||||
|
|
||||||
// Local is mainnet exactly on Cancun, remote announces Shanghai + knowledge about Cancun. Remote
|
// Local is mainnet exactly on Cancun, remote announces Shanghai + knowledge about Cancun. Remote
|
||||||
// is simply out of sync, accept.
|
// is simply out of sync, accept.
|
||||||
//
|
{params.MainnetChainConfig, 21000000, 1710338135, ID{Hash: checksumToBytes(0xdce96c2d), Next: 1710338135}, nil},
|
||||||
// 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},
|
|
||||||
|
|
||||||
// Local is mainnet Cancun, remote announces Shanghai + knowledge about Cancun. Remote
|
// Local is mainnet Cancun, remote announces Shanghai + knowledge about Cancun. Remote
|
||||||
// is simply out of sync, accept.
|
// is simply out of sync, accept.
|
||||||
// TODO(karalabe): Enable this when Cancun is specced, update local head and time, next timestamp
|
{params.MainnetChainConfig, 21123456, 1710338136, ID{Hash: checksumToBytes(0xdce96c2d), Next: 1710338135}, nil},
|
||||||
//{params.MainnetChainConfig, 21123456, 1678123456, ID{Hash: checksumToBytes(0x71147644), Next: 1678000000}, nil},
|
|
||||||
|
|
||||||
// Local is mainnet Prague, remote announces Shanghai + knowledge about Cancun. Remote
|
// 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.
|
// 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},
|
//{params.MainnetChainConfig, 0, 0, ID{Hash: checksumToBytes(0x3edd5b10), Next: 4370000}, nil},
|
||||||
|
|
||||||
// Local is mainnet Shanghai, remote announces Cancun. Local is out of sync, accept.
|
// Local is mainnet Shanghai, remote announces Cancun. Local is out of sync, accept.
|
||||||
//
|
{params.MainnetChainConfig, 21000000, 1700000000, ID{Hash: checksumToBytes(0x9f3d2254), Next: 0}, nil},
|
||||||
// TODO(karalabe): Enable this when Cancun is specced, update remote checksum
|
|
||||||
//{params.MainnetChainConfig, 21000000, 1678000000, ID{Hash: checksumToBytes(0x00000000), Next: 0}, nil},
|
|
||||||
|
|
||||||
// Local is mainnet Shanghai, remote announces Cancun, but is not aware of Prague. Local
|
// 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.
|
// 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.
|
// Local is mainnet Cancun. remote announces Shanghai but is not aware of further forks.
|
||||||
// Remote needs software update.
|
// Remote needs software update.
|
||||||
//
|
{params.MainnetChainConfig, 21000000, 1710338135, ID{Hash: checksumToBytes(0xdce96c2d), Next: 0}, ErrRemoteStale},
|
||||||
// TODO(karalabe): Enable this when Cancun is specced, update local head and time
|
|
||||||
//{params.MainnetChainConfig, 21000000, 1678000000, ID{Hash: checksumToBytes(0x71147644), Next: 0}, ErrRemoteStale},
|
|
||||||
|
|
||||||
// Local is mainnet Shanghai, and isn't aware of more forks. Remote announces Shanghai +
|
// Local is mainnet Shanghai, and isn't aware of more forks. Remote announces Shanghai +
|
||||||
// 0xffffffff. Local needs software update, reject.
|
// 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 +
|
// Local is mainnet Shanghai, and is aware of Cancun. Remote announces Cancun +
|
||||||
// 0xffffffff. Local needs software update, reject.
|
// 0xffffffff. Local needs software update, reject.
|
||||||
//
|
{params.MainnetChainConfig, 20000000, 1668000000, ID{Hash: checksumToBytes(checksumUpdate(0x9f3d2254, math.MaxUint64)), Next: 0}, ErrLocalIncompatibleOrStale},
|
||||||
// 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},
|
|
||||||
|
|
||||||
// Local is mainnet Shanghai, remote is random Shanghai.
|
// Local is mainnet Shanghai, remote is random Shanghai.
|
||||||
{params.MainnetChainConfig, 20000000, 1681338455, ID{Hash: checksumToBytes(0x12345678), Next: 0}, ErrLocalIncompatibleOrStale},
|
{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.
|
// 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).
|
// 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
|
// Local is mainnet Shanghai. Remote is also in Shanghai, but announces Gopherium (non existing
|
||||||
// fork) at timestamp 1668000000, before Cancun. Local is incompatible.
|
// fork) at timestamp 1668000000, before Cancun. Local is incompatible.
|
||||||
//
|
{params.MainnetChainConfig, 20999999, 1699999999, ID{Hash: checksumToBytes(0x71147644), Next: 1700000000}, ErrLocalIncompatibleOrStale},
|
||||||
// TODO(karalabe): Enable this when Cancun is specced
|
|
||||||
//{params.MainnetChainConfig, 20999999, 1677999999, ID{Hash: checksumToBytes(0x71147644), Next: 1678000000}, ErrLocalIncompatibleOrStale},
|
|
||||||
}
|
}
|
||||||
genesis := core.DefaultGenesisBlock().ToBlock()
|
genesis := core.DefaultGenesisBlock().ToBlock()
|
||||||
for i, tt := range tests {
|
for i, tt := range tests {
|
||||||
|
|
|
||||||
|
|
@ -413,6 +413,8 @@ func (g *Genesis) configOrDefault(ghash common.Hash) *params.ChainConfig {
|
||||||
return g.Config
|
return g.Config
|
||||||
case ghash == params.MainnetGenesisHash:
|
case ghash == params.MainnetGenesisHash:
|
||||||
return params.MainnetChainConfig
|
return params.MainnetChainConfig
|
||||||
|
case ghash == params.HoleskyGenesisHash:
|
||||||
|
return params.HoleskyChainConfig
|
||||||
case ghash == params.SepoliaGenesisHash:
|
case ghash == params.SepoliaGenesisHash:
|
||||||
return params.SepoliaChainConfig
|
return params.SepoliaChainConfig
|
||||||
case ghash == params.GoerliGenesisHash:
|
case ghash == params.GoerliGenesisHash:
|
||||||
|
|
|
||||||
|
|
@ -93,7 +93,7 @@ type stateObject struct {
|
||||||
|
|
||||||
// empty returns whether the account is considered empty.
|
// empty returns whether the account is considered empty.
|
||||||
func (s *stateObject) empty() bool {
|
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.
|
// newObject creates a state object.
|
||||||
|
|
@ -408,7 +408,7 @@ func (s *stateObject) commit() (*trienode.NodeSet, error) {
|
||||||
func (s *stateObject) AddBalance(amount *uint256.Int) {
|
func (s *stateObject) AddBalance(amount *uint256.Int) {
|
||||||
// EIP161: We must check emptiness for the objects such that the account
|
// EIP161: We must check emptiness for the objects such that the account
|
||||||
// clearing (0,0,0 objects) can take effect.
|
// clearing (0,0,0 objects) can take effect.
|
||||||
if amount.Sign() == 0 {
|
if amount.IsZero() {
|
||||||
if s.empty() {
|
if s.empty() {
|
||||||
s.touch()
|
s.touch()
|
||||||
}
|
}
|
||||||
|
|
@ -420,7 +420,7 @@ func (s *stateObject) AddBalance(amount *uint256.Int) {
|
||||||
// SubBalance removes amount from s's balance.
|
// SubBalance removes amount from s's balance.
|
||||||
// It is used to remove funds from the origin account of a transfer.
|
// It is used to remove funds from the origin account of a transfer.
|
||||||
func (s *stateObject) SubBalance(amount *uint256.Int) {
|
func (s *stateObject) SubBalance(amount *uint256.Int) {
|
||||||
if amount.Sign() == 0 {
|
if amount.IsZero() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
s.SetBalance(new(uint256.Int).Sub(s.Balance(), amount))
|
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"
|
||||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||||
|
"github.com/ethereum/go-ethereum/crypto/kzg4844"
|
||||||
"github.com/holiman/uint256"
|
"github.com/holiman/uint256"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -47,6 +48,11 @@ type txJSON struct {
|
||||||
S *hexutil.Big `json:"s"`
|
S *hexutil.Big `json:"s"`
|
||||||
YParity *hexutil.Uint64 `json:"yParity,omitempty"`
|
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:
|
// Only used for encoding:
|
||||||
Hash common.Hash `json:"hash"`
|
Hash common.Hash `json:"hash"`
|
||||||
}
|
}
|
||||||
|
|
@ -142,6 +148,11 @@ func (tx *Transaction) MarshalJSON() ([]byte, error) {
|
||||||
enc.S = (*hexutil.Big)(itx.S.ToBig())
|
enc.S = (*hexutil.Big)(itx.S.ToBig())
|
||||||
yparity := itx.V.Uint64()
|
yparity := itx.V.Uint64()
|
||||||
enc.YParity = (*hexutil.Uint64)(&yparity)
|
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)
|
return json.Marshal(&enc)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -182,7 +182,7 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas
|
||||||
return nil, gas, ErrDepth
|
return nil, gas, ErrDepth
|
||||||
}
|
}
|
||||||
// Fail if we're trying to transfer more than the available balance
|
// 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
|
return nil, gas, ErrInsufficientBalance
|
||||||
}
|
}
|
||||||
snapshot := evm.StateDB.Snapshot()
|
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
|
debug := evm.Config.Tracer != nil
|
||||||
|
|
||||||
if !evm.StateDB.Exist(addr) {
|
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
|
// Calling a non existing account, don't do anything, but ping the tracer
|
||||||
if debug {
|
if debug {
|
||||||
if evm.depth == 0 {
|
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) {
|
func opCodeSize(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
||||||
l := new(uint256.Int)
|
scope.Stack.push(new(uint256.Int).SetUint64(uint64(len(scope.Contract.Code))))
|
||||||
l.SetUint64(uint64(len(scope.Contract.Code)))
|
|
||||||
scope.Stack.push(l)
|
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -166,7 +166,7 @@ type G2 struct {
|
||||||
p *twistPoint
|
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) {
|
func RandomG2(r io.Reader) (*big.Int, *G2, error) {
|
||||||
var k *big.Int
|
var k *big.Int
|
||||||
var err error
|
var err error
|
||||||
|
|
|
||||||
|
|
@ -21,21 +21,60 @@ import (
|
||||||
"embed"
|
"embed"
|
||||||
"errors"
|
"errors"
|
||||||
"hash"
|
"hash"
|
||||||
|
"reflect"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||||
)
|
)
|
||||||
|
|
||||||
//go:embed trusted_setup.json
|
//go:embed trusted_setup.json
|
||||||
var content embed.FS
|
var content embed.FS
|
||||||
|
|
||||||
|
var (
|
||||||
|
blobT = reflect.TypeOf(Blob{})
|
||||||
|
commitmentT = reflect.TypeOf(Commitment{})
|
||||||
|
proofT = reflect.TypeOf(Proof{})
|
||||||
|
)
|
||||||
|
|
||||||
// Blob represents a 4844 data blob.
|
// Blob represents a 4844 data blob.
|
||||||
type Blob [131072]byte
|
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.
|
// Commitment is a serialized commitment to a polynomial.
|
||||||
type Commitment [48]byte
|
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.
|
// Proof is a serialized commitment to the quotient polynomial.
|
||||||
type Proof [48]byte
|
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.
|
// Point is a BLS field element.
|
||||||
type Point [32]byte
|
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 TestCanonicalSynchronisation68Full(t *testing.T) { testCanonSync(t, eth.ETH68, FullSync) }
|
||||||
func TestCanonicalSynchronisation68Snap(t *testing.T) { testCanonSync(t, eth.ETH68, SnapSync) }
|
func TestCanonicalSynchronisation68Snap(t *testing.T) { testCanonSync(t, eth.ETH68, SnapSync) }
|
||||||
func TestCanonicalSynchronisation68Light(t *testing.T) { testCanonSync(t, eth.ETH68, LightSync) }
|
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) {
|
func testCanonSync(t *testing.T, protocol uint, mode SyncMode) {
|
||||||
tester := newTester(t)
|
tester := newTester(t)
|
||||||
|
|
@ -463,8 +460,6 @@ func testCanonSync(t *testing.T, protocol uint, mode SyncMode) {
|
||||||
// until the cached blocks are retrieved.
|
// until the cached blocks are retrieved.
|
||||||
func TestThrottling68Full(t *testing.T) { testThrottling(t, eth.ETH68, FullSync) }
|
func TestThrottling68Full(t *testing.T) { testThrottling(t, eth.ETH68, FullSync) }
|
||||||
func TestThrottling68Snap(t *testing.T) { testThrottling(t, eth.ETH68, SnapSync) }
|
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) {
|
func testThrottling(t *testing.T, protocol uint, mode SyncMode) {
|
||||||
tester := newTester(t)
|
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 TestForkedSync68Full(t *testing.T) { testForkedSync(t, eth.ETH68, FullSync) }
|
||||||
func TestForkedSync68Snap(t *testing.T) { testForkedSync(t, eth.ETH68, SnapSync) }
|
func TestForkedSync68Snap(t *testing.T) { testForkedSync(t, eth.ETH68, SnapSync) }
|
||||||
func TestForkedSync68Light(t *testing.T) { testForkedSync(t, eth.ETH68, LightSync) }
|
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) {
|
func testForkedSync(t *testing.T, protocol uint, mode SyncMode) {
|
||||||
tester := newTester(t)
|
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 TestHeavyForkedSync68Full(t *testing.T) { testHeavyForkedSync(t, eth.ETH68, FullSync) }
|
||||||
func TestHeavyForkedSync68Snap(t *testing.T) { testHeavyForkedSync(t, eth.ETH68, SnapSync) }
|
func TestHeavyForkedSync68Snap(t *testing.T) { testHeavyForkedSync(t, eth.ETH68, SnapSync) }
|
||||||
func TestHeavyForkedSync68Light(t *testing.T) { testHeavyForkedSync(t, eth.ETH68, LightSync) }
|
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) {
|
func testHeavyForkedSync(t *testing.T, protocol uint, mode SyncMode) {
|
||||||
tester := newTester(t)
|
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 TestBoundedForkedSync68Full(t *testing.T) { testBoundedForkedSync(t, eth.ETH68, FullSync) }
|
||||||
func TestBoundedForkedSync68Snap(t *testing.T) { testBoundedForkedSync(t, eth.ETH68, SnapSync) }
|
func TestBoundedForkedSync68Snap(t *testing.T) { testBoundedForkedSync(t, eth.ETH68, SnapSync) }
|
||||||
func TestBoundedForkedSync68Light(t *testing.T) { testBoundedForkedSync(t, eth.ETH68, LightSync) }
|
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) {
|
func testBoundedForkedSync(t *testing.T, protocol uint, mode SyncMode) {
|
||||||
tester := newTester(t)
|
tester := newTester(t)
|
||||||
|
|
@ -645,15 +631,6 @@ func TestBoundedHeavyForkedSync68Snap(t *testing.T) {
|
||||||
func TestBoundedHeavyForkedSync68Light(t *testing.T) {
|
func TestBoundedHeavyForkedSync68Light(t *testing.T) {
|
||||||
testBoundedHeavyForkedSync(t, eth.ETH68, LightSync)
|
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) {
|
func testBoundedHeavyForkedSync(t *testing.T, protocol uint, mode SyncMode) {
|
||||||
tester := newTester(t)
|
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 TestCancel68Full(t *testing.T) { testCancel(t, eth.ETH68, FullSync) }
|
||||||
func TestCancel68Snap(t *testing.T) { testCancel(t, eth.ETH68, SnapSync) }
|
func TestCancel68Snap(t *testing.T) { testCancel(t, eth.ETH68, SnapSync) }
|
||||||
func TestCancel68Light(t *testing.T) { testCancel(t, eth.ETH68, LightSync) }
|
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) {
|
func testCancel(t *testing.T, protocol uint, mode SyncMode) {
|
||||||
tester := newTester(t)
|
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 TestMultiSynchronisation68Full(t *testing.T) { testMultiSynchronisation(t, eth.ETH68, FullSync) }
|
||||||
func TestMultiSynchronisation68Snap(t *testing.T) { testMultiSynchronisation(t, eth.ETH68, SnapSync) }
|
func TestMultiSynchronisation68Snap(t *testing.T) { testMultiSynchronisation(t, eth.ETH68, SnapSync) }
|
||||||
func TestMultiSynchronisation68Light(t *testing.T) { testMultiSynchronisation(t, eth.ETH68, LightSync) }
|
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) {
|
func testMultiSynchronisation(t *testing.T, protocol uint, mode SyncMode) {
|
||||||
tester := newTester(t)
|
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 TestMultiProtoSynchronisation68Full(t *testing.T) { testMultiProtoSync(t, eth.ETH68, FullSync) }
|
||||||
func TestMultiProtoSynchronisation68Snap(t *testing.T) { testMultiProtoSync(t, eth.ETH68, SnapSync) }
|
func TestMultiProtoSynchronisation68Snap(t *testing.T) { testMultiProtoSync(t, eth.ETH68, SnapSync) }
|
||||||
func TestMultiProtoSynchronisation68Light(t *testing.T) { testMultiProtoSync(t, eth.ETH68, LightSync) }
|
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) {
|
func testMultiProtoSync(t *testing.T, protocol uint, mode SyncMode) {
|
||||||
tester := newTester(t)
|
tester := newTester(t)
|
||||||
|
|
@ -751,7 +719,6 @@ func testMultiProtoSync(t *testing.T, protocol uint, mode SyncMode) {
|
||||||
|
|
||||||
// Create peers of every type
|
// Create peers of every type
|
||||||
tester.newPeer("peer 68", eth.ETH68, chain.blocks[1:])
|
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
|
// 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 {
|
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))
|
assertOwnChain(t, tester, len(chain.blocks))
|
||||||
|
|
||||||
// Check that no peers have been dropped off
|
// 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)
|
peer := fmt.Sprintf("peer %d", version)
|
||||||
if _, ok := tester.peers[peer]; !ok {
|
if _, ok := tester.peers[peer]; !ok {
|
||||||
t.Errorf("%s dropped", peer)
|
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 TestEmptyShortCircuit68Full(t *testing.T) { testEmptyShortCircuit(t, eth.ETH68, FullSync) }
|
||||||
func TestEmptyShortCircuit68Snap(t *testing.T) { testEmptyShortCircuit(t, eth.ETH68, SnapSync) }
|
func TestEmptyShortCircuit68Snap(t *testing.T) { testEmptyShortCircuit(t, eth.ETH68, SnapSync) }
|
||||||
func TestEmptyShortCircuit68Light(t *testing.T) { testEmptyShortCircuit(t, eth.ETH68, LightSync) }
|
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) {
|
func testEmptyShortCircuit(t *testing.T, protocol uint, mode SyncMode) {
|
||||||
tester := newTester(t)
|
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 TestMissingHeaderAttack68Full(t *testing.T) { testMissingHeaderAttack(t, eth.ETH68, FullSync) }
|
||||||
func TestMissingHeaderAttack68Snap(t *testing.T) { testMissingHeaderAttack(t, eth.ETH68, SnapSync) }
|
func TestMissingHeaderAttack68Snap(t *testing.T) { testMissingHeaderAttack(t, eth.ETH68, SnapSync) }
|
||||||
func TestMissingHeaderAttack68Light(t *testing.T) { testMissingHeaderAttack(t, eth.ETH68, LightSync) }
|
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) {
|
func testMissingHeaderAttack(t *testing.T, protocol uint, mode SyncMode) {
|
||||||
tester := newTester(t)
|
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 TestShiftedHeaderAttack68Full(t *testing.T) { testShiftedHeaderAttack(t, eth.ETH68, FullSync) }
|
||||||
func TestShiftedHeaderAttack68Snap(t *testing.T) { testShiftedHeaderAttack(t, eth.ETH68, SnapSync) }
|
func TestShiftedHeaderAttack68Snap(t *testing.T) { testShiftedHeaderAttack(t, eth.ETH68, SnapSync) }
|
||||||
func TestShiftedHeaderAttack68Light(t *testing.T) { testShiftedHeaderAttack(t, eth.ETH68, LightSync) }
|
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) {
|
func testShiftedHeaderAttack(t *testing.T, protocol uint, mode SyncMode) {
|
||||||
tester := newTester(t)
|
tester := newTester(t)
|
||||||
|
|
@ -889,15 +847,6 @@ func TestHighTDStarvationAttack68Snap(t *testing.T) {
|
||||||
func TestHighTDStarvationAttack68Light(t *testing.T) {
|
func TestHighTDStarvationAttack68Light(t *testing.T) {
|
||||||
testHighTDStarvationAttack(t, eth.ETH68, LightSync)
|
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) {
|
func testHighTDStarvationAttack(t *testing.T, protocol uint, mode SyncMode) {
|
||||||
tester := newTester(t)
|
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.
|
// Tests that misbehaving peers are disconnected, whilst behaving ones are not.
|
||||||
func TestBlockHeaderAttackerDropping68(t *testing.T) { testBlockHeaderAttackerDropping(t, eth.ETH68) }
|
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) {
|
func testBlockHeaderAttackerDropping(t *testing.T, protocol uint) {
|
||||||
// Define the disconnection requirement for individual hash fetch errors
|
// 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 TestSyncProgress68Full(t *testing.T) { testSyncProgress(t, eth.ETH68, FullSync) }
|
||||||
func TestSyncProgress68Snap(t *testing.T) { testSyncProgress(t, eth.ETH68, SnapSync) }
|
func TestSyncProgress68Snap(t *testing.T) { testSyncProgress(t, eth.ETH68, SnapSync) }
|
||||||
func TestSyncProgress68Light(t *testing.T) { testSyncProgress(t, eth.ETH68, LightSync) }
|
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) {
|
func testSyncProgress(t *testing.T, protocol uint, mode SyncMode) {
|
||||||
tester := newTester(t)
|
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 TestForkedSyncProgress68Full(t *testing.T) { testForkedSyncProgress(t, eth.ETH68, FullSync) }
|
||||||
func TestForkedSyncProgress68Snap(t *testing.T) { testForkedSyncProgress(t, eth.ETH68, SnapSync) }
|
func TestForkedSyncProgress68Snap(t *testing.T) { testForkedSyncProgress(t, eth.ETH68, SnapSync) }
|
||||||
func TestForkedSyncProgress68Light(t *testing.T) { testForkedSyncProgress(t, eth.ETH68, LightSync) }
|
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) {
|
func testForkedSyncProgress(t *testing.T, protocol uint, mode SyncMode) {
|
||||||
tester := newTester(t)
|
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 TestFailedSyncProgress68Full(t *testing.T) { testFailedSyncProgress(t, eth.ETH68, FullSync) }
|
||||||
func TestFailedSyncProgress68Snap(t *testing.T) { testFailedSyncProgress(t, eth.ETH68, SnapSync) }
|
func TestFailedSyncProgress68Snap(t *testing.T) { testFailedSyncProgress(t, eth.ETH68, SnapSync) }
|
||||||
func TestFailedSyncProgress68Light(t *testing.T) { testFailedSyncProgress(t, eth.ETH68, LightSync) }
|
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) {
|
func testFailedSyncProgress(t *testing.T, protocol uint, mode SyncMode) {
|
||||||
tester := newTester(t)
|
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 TestFakedSyncProgress68Full(t *testing.T) { testFakedSyncProgress(t, eth.ETH68, FullSync) }
|
||||||
func TestFakedSyncProgress68Snap(t *testing.T) { testFakedSyncProgress(t, eth.ETH68, SnapSync) }
|
func TestFakedSyncProgress68Snap(t *testing.T) { testFakedSyncProgress(t, eth.ETH68, SnapSync) }
|
||||||
func TestFakedSyncProgress68Light(t *testing.T) { testFakedSyncProgress(t, eth.ETH68, LightSync) }
|
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) {
|
func testFakedSyncProgress(t *testing.T, protocol uint, mode SyncMode) {
|
||||||
tester := newTester(t)
|
tester := newTester(t)
|
||||||
|
|
@ -1332,8 +1268,6 @@ func TestRemoteHeaderRequestSpan(t *testing.T) {
|
||||||
// being fast-synced from, avoiding potential cheap eclipse attacks.
|
// being fast-synced from, avoiding potential cheap eclipse attacks.
|
||||||
func TestBeaconSync68Full(t *testing.T) { testBeaconSync(t, eth.ETH68, FullSync) }
|
func TestBeaconSync68Full(t *testing.T) { testBeaconSync(t, eth.ETH68, FullSync) }
|
||||||
func TestBeaconSync68Snap(t *testing.T) { testBeaconSync(t, eth.ETH68, SnapSync) }
|
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) {
|
func testBeaconSync(t *testing.T, protocol uint, mode SyncMode) {
|
||||||
//log.Root().SetHandler(log.LvlFilterHandler(log.LvlInfo, log.StreamHandler(os.Stderr, log.TerminalFormat(true))))
|
//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
|
// Create a peer set to feed headers through
|
||||||
peerset := newPeerSet()
|
peerset := newPeerSet()
|
||||||
for _, peer := range tt.peers {
|
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
|
// Create a peer dropper to track malicious peers
|
||||||
dropped := make(map[string]int)
|
dropped := make(map[string]int)
|
||||||
|
|
@ -913,7 +913,7 @@ func TestSkeletonSyncRetrievals(t *testing.T) {
|
||||||
skeleton.Sync(tt.newHead, nil, true)
|
skeleton.Sync(tt.newHead, nil, true)
|
||||||
}
|
}
|
||||||
if tt.newPeer != nil {
|
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)
|
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 {
|
if p < 0 || p > 100 {
|
||||||
return common.Big0, nil, nil, nil, fmt.Errorf("%w: %f", errInvalidPercentile, p)
|
return common.Big0, nil, nil, nil, fmt.Errorf("%w: %f", errInvalidPercentile, p)
|
||||||
}
|
}
|
||||||
if i > 0 && p < rewardPercentiles[i-1] {
|
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)
|
return common.Big0, nil, nil, nil, fmt.Errorf("%w: #%d:%f >= #%d:%f", errInvalidPercentile, i-1, rewardPercentiles[i-1], i, p)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var (
|
var (
|
||||||
|
|
|
||||||
|
|
@ -67,10 +67,7 @@ func (h *ethHandler) Handle(peer *eth.Peer, packet eth.Packet) error {
|
||||||
case *eth.NewBlockPacket:
|
case *eth.NewBlockPacket:
|
||||||
return h.handleBlockBroadcast(peer, packet.Block, packet.TD)
|
return h.handleBlockBroadcast(peer, packet.Block, packet.TD)
|
||||||
|
|
||||||
case *eth.NewPooledTransactionHashesPacket67:
|
case *eth.NewPooledTransactionHashesPacket:
|
||||||
return h.txFetcher.Notify(peer.ID(), nil, nil, *packet)
|
|
||||||
|
|
||||||
case *eth.NewPooledTransactionHashesPacket68:
|
|
||||||
return h.txFetcher.Notify(peer.ID(), packet.Types, packet.Sizes, packet.Hashes)
|
return h.txFetcher.Notify(peer.ID(), packet.Types, packet.Sizes, packet.Hashes)
|
||||||
|
|
||||||
case *eth.TransactionsPacket:
|
case *eth.TransactionsPacket:
|
||||||
|
|
|
||||||
|
|
@ -58,11 +58,7 @@ func (h *testEthHandler) Handle(peer *eth.Peer, packet eth.Packet) error {
|
||||||
h.blockBroadcasts.Send(packet.Block)
|
h.blockBroadcasts.Send(packet.Block)
|
||||||
return nil
|
return nil
|
||||||
|
|
||||||
case *eth.NewPooledTransactionHashesPacket67:
|
case *eth.NewPooledTransactionHashesPacket:
|
||||||
h.txAnnounces.Send(([]common.Hash)(*packet))
|
|
||||||
return nil
|
|
||||||
|
|
||||||
case *eth.NewPooledTransactionHashesPacket68:
|
|
||||||
h.txAnnounces.Send(packet.Hashes)
|
h.txAnnounces.Send(packet.Hashes)
|
||||||
return nil
|
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
|
// Tests that peers are correctly accepted (or rejected) based on the advertised
|
||||||
// fork IDs in the protocol handshake.
|
// 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 TestForkIDSplit68(t *testing.T) { testForkIDSplit(t, eth.ETH68) }
|
||||||
|
|
||||||
func testForkIDSplit(t *testing.T, protocol uint) {
|
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.
|
// 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 TestRecvTransactions68(t *testing.T) { testRecvTransactions(t, eth.ETH68) }
|
||||||
|
|
||||||
func testRecvTransactions(t *testing.T, protocol uint) {
|
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.
|
// 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 TestSendTransactions68(t *testing.T) { testSendTransactions(t, eth.ETH68) }
|
||||||
|
|
||||||
func testSendTransactions(t *testing.T, protocol uint) {
|
func testSendTransactions(t *testing.T, protocol uint) {
|
||||||
|
|
@ -353,7 +346,7 @@ func testSendTransactions(t *testing.T, protocol uint) {
|
||||||
seen := make(map[common.Hash]struct{})
|
seen := make(map[common.Hash]struct{})
|
||||||
for len(seen) < len(insert) {
|
for len(seen) < len(insert) {
|
||||||
switch protocol {
|
switch protocol {
|
||||||
case 67, 68:
|
case 68:
|
||||||
select {
|
select {
|
||||||
case hashes := <-anns:
|
case hashes := <-anns:
|
||||||
for _, hash := range hashes {
|
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
|
// Tests that transactions get propagated to all attached peers, either via direct
|
||||||
// broadcasts or via announcements/retrievals.
|
// broadcasts or via announcements/retrievals.
|
||||||
func TestTransactionPropagation67(t *testing.T) { testTransactionPropagation(t, eth.ETH67) }
|
|
||||||
func TestTransactionPropagation68(t *testing.T) { testTransactionPropagation(t, eth.ETH68) }
|
func TestTransactionPropagation68(t *testing.T) { testTransactionPropagation(t, eth.ETH68) }
|
||||||
|
|
||||||
func testTransactionPropagation(t *testing.T, protocol uint) {
|
func testTransactionPropagation(t *testing.T, protocol uint) {
|
||||||
|
|
@ -486,8 +478,8 @@ func testBroadcastBlock(t *testing.T, peers, bcasts int) {
|
||||||
defer sourcePipe.Close()
|
defer sourcePipe.Close()
|
||||||
defer sinkPipe.Close()
|
defer sinkPipe.Close()
|
||||||
|
|
||||||
sourcePeer := eth.NewPeer(eth.ETH67, p2p.NewPeerPipe(enode.ID{byte(i)}, "", nil, sourcePipe), sourcePipe, nil)
|
sourcePeer := eth.NewPeer(eth.ETH68, p2p.NewPeerPipe(enode.ID{byte(i)}, "", nil, sourcePipe), sourcePipe, nil)
|
||||||
sinkPeer := eth.NewPeer(eth.ETH67, p2p.NewPeerPipe(enode.ID{0}, "", nil, sinkPipe), sinkPipe, nil)
|
sinkPeer := eth.NewPeer(eth.ETH68, p2p.NewPeerPipe(enode.ID{0}, "", nil, sinkPipe), sinkPipe, nil)
|
||||||
defer sourcePeer.Close()
|
defer sourcePeer.Close()
|
||||||
defer sinkPeer.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
|
// Tests that a propagated malformed block (uncles or transactions don't match
|
||||||
// with the hashes in the header) gets discarded and not broadcast forward.
|
// 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 TestBroadcastMalformedBlock68(t *testing.T) { testBroadcastMalformedBlock(t, eth.ETH68) }
|
||||||
|
|
||||||
func testBroadcastMalformedBlock(t *testing.T, protocol uint) {
|
func testBroadcastMalformedBlock(t *testing.T, protocol uint) {
|
||||||
|
|
|
||||||
|
|
@ -163,16 +163,9 @@ func (p *Peer) announceTransactions() {
|
||||||
if len(pending) > 0 {
|
if len(pending) > 0 {
|
||||||
done = make(chan struct{})
|
done = make(chan struct{})
|
||||||
go func() {
|
go func() {
|
||||||
if p.version >= ETH68 {
|
if err := p.sendPooledTransactionHashes(pending, pendingTypes, pendingSizes); err != nil {
|
||||||
if err := p.sendPooledTransactionHashes68(pending, pendingTypes, pendingSizes); err != nil {
|
fail <- err
|
||||||
fail <- err
|
return
|
||||||
return
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if err := p.sendPooledTransactionHashes66(pending); err != nil {
|
|
||||||
fail <- err
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
close(done)
|
close(done)
|
||||||
p.Log().Trace("Sent transaction announcements", "count", len(pending))
|
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 {
|
func MakeProtocols(backend Backend, network uint64, dnsdisc enode.Iterator) []p2p.Protocol {
|
||||||
protocols := make([]p2p.Protocol, 0, len(ProtocolVersions))
|
protocols := make([]p2p.Protocol, 0, len(ProtocolVersions))
|
||||||
for _, version := range 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
|
version := version // Closure
|
||||||
|
|
||||||
protocols = append(protocols, p2p.Protocol{
|
protocols = append(protocols, p2p.Protocol{
|
||||||
|
|
@ -166,26 +162,11 @@ type Decoder interface {
|
||||||
Time() time.Time
|
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{
|
var eth68 = map[uint64]msgHandler{
|
||||||
NewBlockHashesMsg: handleNewBlockhashes,
|
NewBlockHashesMsg: handleNewBlockhashes,
|
||||||
NewBlockMsg: handleNewBlock,
|
NewBlockMsg: handleNewBlock,
|
||||||
TransactionsMsg: handleTransactions,
|
TransactionsMsg: handleTransactions,
|
||||||
NewPooledTransactionHashesMsg: handleNewPooledTransactionHashes68,
|
NewPooledTransactionHashesMsg: handleNewPooledTransactionHashes,
|
||||||
GetBlockHeadersMsg: handleGetBlockHeaders,
|
GetBlockHeadersMsg: handleGetBlockHeaders,
|
||||||
BlockHeadersMsg: handleBlockHeaders,
|
BlockHeadersMsg: handleBlockHeaders,
|
||||||
GetBlockBodiesMsg: handleGetBlockBodies,
|
GetBlockBodiesMsg: handleGetBlockBodies,
|
||||||
|
|
@ -209,10 +190,8 @@ func handleMessage(backend Backend, peer *Peer) error {
|
||||||
}
|
}
|
||||||
defer msg.Discard()
|
defer msg.Discard()
|
||||||
|
|
||||||
var handlers = eth67
|
var handlers = eth68
|
||||||
if peer.Version() >= ETH68 {
|
|
||||||
handlers = eth68
|
|
||||||
}
|
|
||||||
// Track the amount of time it takes to serve the request and run the handler
|
// Track the amount of time it takes to serve the request and run the handler
|
||||||
if metrics.Enabled {
|
if metrics.Enabled {
|
||||||
h := fmt.Sprintf("%s/%s/%d/%#02x", p2p.HandleHistName, ProtocolName, peer.Version(), msg.Code)
|
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.
|
// 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 TestGetBlockHeaders68(t *testing.T) { testGetBlockHeaders(t, ETH68) }
|
||||||
|
|
||||||
func testGetBlockHeaders(t *testing.T, protocol uint) {
|
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.
|
// 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 TestGetBlockBodies68(t *testing.T) { testGetBlockBodies(t, ETH68) }
|
||||||
|
|
||||||
func testGetBlockBodies(t *testing.T, protocol uint) {
|
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.
|
// 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 TestGetBlockReceipts68(t *testing.T) { testGetBlockReceipts(t, ETH68) }
|
||||||
|
|
||||||
func testGetBlockReceipts(t *testing.T, protocol uint) {
|
func testGetBlockReceipts(t *testing.T, protocol uint) {
|
||||||
|
|
|
||||||
|
|
@ -383,30 +383,13 @@ func handleReceipts(backend Backend, msg Decoder, peer *Peer) error {
|
||||||
}, metadata)
|
}, 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
|
// New transaction announcement arrived, make sure we have
|
||||||
// a valid and fresh chain to handle them
|
// a valid and fresh chain to handle them
|
||||||
if !backend.AcceptTxs() {
|
if !backend.AcceptTxs() {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
ann := new(NewPooledTransactionHashesPacket67)
|
ann := new(NewPooledTransactionHashesPacket)
|
||||||
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)
|
|
||||||
if err := msg.Decode(ann); err != nil {
|
if err := msg.Decode(ann); err != nil {
|
||||||
return fmt.Errorf("%w: message %v: %v", errDecode, msg, err)
|
return fmt.Errorf("%w: message %v: %v", errDecode, msg, err)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,6 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
// Tests that handshake failures are detected and reported correctly.
|
// 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 TestHandshake68(t *testing.T) { testHandshake(t, ETH68) }
|
||||||
|
|
||||||
func testHandshake(t *testing.T, protocol uint) {
|
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
|
// sendPooledTransactionHashes sends transaction hashes (tagged with their type
|
||||||
// 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
|
|
||||||
// and size) to the peer and includes them in its transaction hash set for future
|
// and size) to the peer and includes them in its transaction hash set for future
|
||||||
// reference.
|
// reference.
|
||||||
//
|
//
|
||||||
// This method is a helper used by the async transaction announcer. Don't call it
|
// 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
|
// directly as the queueing (memory) and transmission (bandwidth) costs should
|
||||||
// not be managed directly.
|
// 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
|
// Mark all the transactions as known, but ensure we don't overflow our limits
|
||||||
p.knownTxs.Add(hashes...)
|
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
|
// AsyncSendPooledTransactionHashes queues a list of transactions hashes to eventually
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,6 @@ import (
|
||||||
|
|
||||||
// Constants to match up protocol versions and messages
|
// Constants to match up protocol versions and messages
|
||||||
const (
|
const (
|
||||||
ETH67 = 67
|
|
||||||
ETH68 = 68
|
ETH68 = 68
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -40,11 +39,11 @@ const ProtocolName = "eth"
|
||||||
|
|
||||||
// ProtocolVersions are the supported versions of the `eth` protocol (first
|
// ProtocolVersions are the supported versions of the `eth` protocol (first
|
||||||
// is primary).
|
// is primary).
|
||||||
var ProtocolVersions = []uint{ETH68, ETH67}
|
var ProtocolVersions = []uint{ETH68}
|
||||||
|
|
||||||
// protocolLengths are the number of implemented message corresponding to
|
// protocolLengths are the number of implemented message corresponding to
|
||||||
// different protocol versions.
|
// 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.
|
// maxMessageSize is the maximum cap on the size of a protocol message.
|
||||||
const maxMessageSize = 10 * 1024 * 1024
|
const maxMessageSize = 10 * 1024 * 1024
|
||||||
|
|
@ -283,11 +282,8 @@ type ReceiptsRLPPacket struct {
|
||||||
ReceiptsRLPResponse
|
ReceiptsRLPResponse
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewPooledTransactionHashesPacket67 represents a transaction announcement packet on eth/67.
|
// NewPooledTransactionHashesPacket represents a transaction announcement packet on eth/68 and newer.
|
||||||
type NewPooledTransactionHashesPacket67 []common.Hash
|
type NewPooledTransactionHashesPacket struct {
|
||||||
|
|
||||||
// NewPooledTransactionHashesPacket68 represents a transaction announcement packet on eth/68 and newer.
|
|
||||||
type NewPooledTransactionHashesPacket68 struct {
|
|
||||||
Types []byte
|
Types []byte
|
||||||
Sizes []uint32
|
Sizes []uint32
|
||||||
Hashes []common.Hash
|
Hashes []common.Hash
|
||||||
|
|
@ -346,10 +342,8 @@ func (*BlockBodiesResponse) Kind() byte { return BlockBodiesMsg }
|
||||||
func (*NewBlockPacket) Name() string { return "NewBlock" }
|
func (*NewBlockPacket) Name() string { return "NewBlock" }
|
||||||
func (*NewBlockPacket) Kind() byte { return NewBlockMsg }
|
func (*NewBlockPacket) Kind() byte { return NewBlockMsg }
|
||||||
|
|
||||||
func (*NewPooledTransactionHashesPacket67) Name() string { return "NewPooledTransactionHashes" }
|
func (*NewPooledTransactionHashesPacket) Name() string { return "NewPooledTransactionHashes" }
|
||||||
func (*NewPooledTransactionHashesPacket67) Kind() byte { return NewPooledTransactionHashesMsg }
|
func (*NewPooledTransactionHashesPacket) Kind() byte { return NewPooledTransactionHashesMsg }
|
||||||
func (*NewPooledTransactionHashesPacket68) Name() string { return "NewPooledTransactionHashes" }
|
|
||||||
func (*NewPooledTransactionHashesPacket68) Kind() byte { return NewPooledTransactionHashesMsg }
|
|
||||||
|
|
||||||
func (*GetPooledTransactionsRequest) Name() string { return "GetPooledTransactions" }
|
func (*GetPooledTransactionsRequest) Name() string { return "GetPooledTransactions" }
|
||||||
func (*GetPooledTransactionsRequest) Kind() byte { return GetPooledTransactionsMsg }
|
func (*GetPooledTransactionsRequest) Kind() byte { return GetPooledTransactionsMsg }
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,6 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
// Tests that snap sync is disabled after a successful sync cycle.
|
// 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) }
|
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
|
// 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.
|
// gas tip for a transaction to be included.
|
||||||
//
|
//
|
||||||
// 0 is not possible as a live Geth node would reject that due to DoS protection,
|
// 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) {
|
func WithMinerMinTip(tip *big.Int) func(nodeConf *node.Config, ethConf *ethconfig.Config) {
|
||||||
if tip == nil || tip.Cmp(new(big.Int)) <= 0 {
|
if tip == nil || tip.Cmp(new(big.Int)) <= 0 {
|
||||||
panic("invalid miner minimum tip")
|
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/dop251/goja v0.0.0-20230806174421-c933cf95e127
|
||||||
github.com/ethereum/c-kzg-4844 v0.4.0
|
github.com/ethereum/c-kzg-4844 v0.4.0
|
||||||
github.com/fatih/color v1.13.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/gencodec v0.0.0-20230517082657-f9840df7b83e
|
||||||
github.com/fjl/memsize v0.0.2
|
github.com/fjl/memsize v0.0.2
|
||||||
github.com/fsnotify/fsnotify v1.6.0
|
github.com/fsnotify/fsnotify v1.6.0
|
||||||
|
|
@ -114,10 +115,12 @@ require (
|
||||||
github.com/jmespath/go-jmespath v0.4.0 // indirect
|
github.com/jmespath/go-jmespath v0.4.0 // indirect
|
||||||
github.com/kilic/bls12-381 v0.1.0 // indirect
|
github.com/kilic/bls12-381 v0.1.0 // indirect
|
||||||
github.com/klauspost/compress v1.15.15 // 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/pretty v0.3.1 // indirect
|
||||||
github.com/kr/text v0.2.0 // indirect
|
github.com/kr/text v0.2.0 // indirect
|
||||||
github.com/mattn/go-runewidth v0.0.13 // 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/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/mapstructure v1.4.1 // indirect
|
||||||
github.com/mitchellh/pointerstructure v1.2.0 // indirect
|
github.com/mitchellh/pointerstructure v1.2.0 // indirect
|
||||||
github.com/mmcloughlin/addchain v0.4.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 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w=
|
||||||
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
|
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/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 h1:bBLctRc7kr01YGvaDfgLbTwjFNW5jdp5y5rj8XXBHfY=
|
||||||
github.com/fjl/gencodec v0.0.0-20230517082657-f9840df7b83e/go.mod h1:AzA8Lj6YtixmJWL+wkKoBGsLWy9gFrAzi4g+5bCKwpY=
|
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=
|
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 h1:EF27CXIuDsYJ6mmvtBRlEuB2UVOqHG1tAXgZ7yIO+lw=
|
||||||
github.com/klauspost/compress v1.15.15/go.mod h1:ZcK2JAFqKOpnBlxcLsJzYfrS9X1akm9fHZNnD9+Vo/4=
|
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 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.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/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=
|
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/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/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/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/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.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||||
github.com/mitchellh/mapstructure v1.4.1 h1:CpVNEelQCZBooIPDn+AR3NpivK/TIKU8bDxdASFVQag=
|
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/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 h1:cZC+usqsYgHtlBaGulVnZ1hfKAi8iWtujBnRLQE698c=
|
||||||
github.com/protolambda/bls12-381-util v0.0.0-20220416220906-d8552aa452c7/go.mod h1:IToEjHuttnUzwZI5KBSM/LOOW3qLbbrHOEfp3SbECGY=
|
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 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
|
||||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
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=
|
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()
|
return (*hexutil.Big)(b), state.Error()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Result structs for GetProof
|
// AccountResult structs for GetProof
|
||||||
type AccountResult struct {
|
type AccountResult struct {
|
||||||
Address common.Address `json:"address"`
|
Address common.Address `json:"address"`
|
||||||
AccountProof []string `json:"accountProof"`
|
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
|
// on a given unsigned transaction, and returns it to the caller for further
|
||||||
// processing (signing + broadcast).
|
// processing (signing + broadcast).
|
||||||
func (s *TransactionAPI) FillTransaction(ctx context.Context, args TransactionArgs) (*SignTransactionResult, error) {
|
func (s *TransactionAPI) FillTransaction(ctx context.Context, args TransactionArgs) (*SignTransactionResult, error) {
|
||||||
|
args.blobSidecarAllowed = true
|
||||||
|
|
||||||
// Set some sanity defaults and terminate on failure
|
// Set some sanity defaults and terminate on failure
|
||||||
if err := args.setDefaults(ctx, s.b); err != nil {
|
if err := args.setDefaults(ctx, s.b); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
// Assemble the transaction and obtain rlp
|
// Assemble the transaction and obtain rlp
|
||||||
tx := args.toTransaction()
|
tx := args.toTransaction()
|
||||||
// TODO(s1na): fill in blob proofs, commitments
|
|
||||||
data, err := tx.MarshalBinary()
|
data, err := tx.MarshalBinary()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"crypto/ecdsa"
|
"crypto/ecdsa"
|
||||||
|
"crypto/sha256"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
@ -45,6 +46,7 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
"github.com/ethereum/go-ethereum/core/vm"
|
"github.com/ethereum/go-ethereum/core/vm"
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
|
"github.com/ethereum/go-ethereum/crypto/kzg4844"
|
||||||
"github.com/ethereum/go-ethereum/ethdb"
|
"github.com/ethereum/go-ethereum/ethdb"
|
||||||
"github.com/ethereum/go-ethereum/event"
|
"github.com/ethereum/go-ethereum/event"
|
||||||
"github.com/ethereum/go-ethereum/internal/blocktest"
|
"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 {
|
func argsFromTransaction(tx *types.Transaction, from common.Address) TransactionArgs {
|
||||||
var (
|
var (
|
||||||
gas = tx.Gas()
|
gas = tx.Gas()
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ package ethapi
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
|
"crypto/sha256"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/big"
|
"math/big"
|
||||||
|
|
@ -29,11 +30,17 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/consensus/misc/eip4844"
|
"github.com/ethereum/go-ethereum/consensus/misc/eip4844"
|
||||||
"github.com/ethereum/go-ethereum/core"
|
"github.com/ethereum/go-ethereum/core"
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
"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/log"
|
||||||
|
"github.com/ethereum/go-ethereum/params"
|
||||||
"github.com/ethereum/go-ethereum/rpc"
|
"github.com/ethereum/go-ethereum/rpc"
|
||||||
"github.com/holiman/uint256"
|
"github.com/holiman/uint256"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
maxBlobsPerTransaction = params.MaxBlobGasPerBlock / params.BlobTxBlobGasPerBlob
|
||||||
|
)
|
||||||
|
|
||||||
// TransactionArgs represents the arguments to construct a new transaction
|
// TransactionArgs represents the arguments to construct a new transaction
|
||||||
// or a message call.
|
// or a message call.
|
||||||
type TransactionArgs struct {
|
type TransactionArgs struct {
|
||||||
|
|
@ -56,9 +63,17 @@ type TransactionArgs struct {
|
||||||
AccessList *types.AccessList `json:"accessList,omitempty"`
|
AccessList *types.AccessList `json:"accessList,omitempty"`
|
||||||
ChainID *hexutil.Big `json:"chainId,omitempty"`
|
ChainID *hexutil.Big `json:"chainId,omitempty"`
|
||||||
|
|
||||||
// Introduced by EIP-4844.
|
// For BlobTxType
|
||||||
BlobFeeCap *hexutil.Big `json:"maxFeePerBlobGas"`
|
BlobFeeCap *hexutil.Big `json:"maxFeePerBlobGas"`
|
||||||
BlobHashes []common.Hash `json:"blobVersionedHashes,omitempty"`
|
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.
|
// from retrieves the transaction sender address.
|
||||||
|
|
@ -82,9 +97,13 @@ func (args *TransactionArgs) data() []byte {
|
||||||
|
|
||||||
// setDefaults fills in default values for unspecified tx fields.
|
// setDefaults fills in default values for unspecified tx fields.
|
||||||
func (args *TransactionArgs) setDefaults(ctx context.Context, b Backend) error {
|
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 {
|
if err := args.setFeeDefaults(ctx, b); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if args.Value == nil {
|
if args.Value == nil {
|
||||||
args.Value = new(hexutil.Big)
|
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) {
|
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`)
|
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 {
|
if args.BlobHashes != nil && len(args.BlobHashes) == 0 {
|
||||||
return errors.New(`need at least 1 blob for a blob transaction`)
|
return errors.New(`need at least 1 blob for a blob transaction`)
|
||||||
}
|
}
|
||||||
if args.To == nil && len(args.data()) == 0 {
|
if args.BlobHashes != nil && len(args.BlobHashes) > maxBlobsPerTransaction {
|
||||||
return errors.New(`contract creation without any data provided`)
|
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.
|
// Estimate the gas usage if necessary.
|
||||||
if args.Gas == nil {
|
if args.Gas == nil {
|
||||||
// These fields are immutable during the estimation, safe to
|
// 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,
|
Value: args.Value,
|
||||||
Data: (*hexutil.Bytes)(&data),
|
Data: (*hexutil.Bytes)(&data),
|
||||||
AccessList: args.AccessList,
|
AccessList: args.AccessList,
|
||||||
|
BlobFeeCap: args.BlobFeeCap,
|
||||||
|
BlobHashes: args.BlobHashes,
|
||||||
}
|
}
|
||||||
latestBlockNr := rpc.BlockNumberOrHashWithNumber(rpc.LatestBlockNumber)
|
latestBlockNr := rpc.BlockNumberOrHashWithNumber(rpc.LatestBlockNumber)
|
||||||
estimated, err := DoEstimateGas(ctx, b, callArgs, latestBlockNr, nil, b.RPCGasCap())
|
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
|
args.Gas = &estimated
|
||||||
log.Trace("Estimate gas usage automatically", "gas", args.Gas)
|
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
|
// If chain id is provided, ensure it matches the local chain id. Otherwise, set the local
|
||||||
// chain id as the default.
|
// chain id as the default.
|
||||||
want := b.ChainConfig().ChainID
|
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
|
return nil // No need to set anything, user already set MaxFeePerGas and MaxPriorityFeePerGas
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sanity check the EIP-4844 fee parameters.
|
// Sanity check the EIP-4844 fee parameters.
|
||||||
if args.BlobFeeCap != nil && args.BlobFeeCap.ToInt().Sign() == 0 {
|
if args.BlobFeeCap != nil && args.BlobFeeCap.ToInt().Sign() == 0 {
|
||||||
return errors.New("maxFeePerBlobGas must be non-zero")
|
return errors.New("maxFeePerBlobGas must be non-zero")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sanity check the non-EIP-1559 fee parameters.
|
// Sanity check the non-EIP-1559 fee parameters.
|
||||||
head := b.CurrentHeader()
|
head := b.CurrentHeader()
|
||||||
isLondon := b.ChainConfig().IsLondon(head.Number)
|
isLondon := b.ChainConfig().IsLondon(head.Number)
|
||||||
|
|
@ -250,6 +284,81 @@ func (args *TransactionArgs) setLondonFeeDefaults(ctx context.Context, head *typ
|
||||||
return nil
|
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
|
// 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
|
// core evm. This method is used in calls and traces that do not require a real
|
||||||
// live transaction.
|
// live transaction.
|
||||||
|
|
@ -363,6 +472,14 @@ func (args *TransactionArgs) toTransaction() *types.Transaction {
|
||||||
BlobHashes: args.BlobHashes,
|
BlobHashes: args.BlobHashes,
|
||||||
BlobFeeCap: uint256.MustFromBig((*big.Int)(args.BlobFeeCap)),
|
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:
|
case args.MaxFeePerGas != nil:
|
||||||
al := types.AccessList{}
|
al := types.AccessList{}
|
||||||
if args.AccessList != nil {
|
if args.AccessList != nil {
|
||||||
|
|
@ -379,6 +496,7 @@ func (args *TransactionArgs) toTransaction() *types.Transaction {
|
||||||
Data: args.data(),
|
Data: args.data(),
|
||||||
AccessList: al,
|
AccessList: al,
|
||||||
}
|
}
|
||||||
|
|
||||||
case args.AccessList != nil:
|
case args.AccessList != nil:
|
||||||
data = &types.AccessListTx{
|
data = &types.AccessListTx{
|
||||||
To: args.To,
|
To: args.To,
|
||||||
|
|
@ -390,6 +508,7 @@ func (args *TransactionArgs) toTransaction() *types.Transaction {
|
||||||
Data: args.data(),
|
Data: args.data(),
|
||||||
AccessList: *args.AccessList,
|
AccessList: *args.AccessList,
|
||||||
}
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
data = &types.LegacyTx{
|
data = &types.LegacyTx{
|
||||||
To: args.To,
|
To: args.To,
|
||||||
|
|
@ -403,12 +522,6 @@ func (args *TransactionArgs) toTransaction() *types.Transaction {
|
||||||
return types.NewTx(data)
|
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.
|
// IsEIP4844 returns an indicator if the args contains EIP4844 fields.
|
||||||
func (args *TransactionArgs) IsEIP4844() bool {
|
func (args *TransactionArgs) IsEIP4844() bool {
|
||||||
return args.BlobHashes != nil || args.BlobFeeCap != nil
|
return args.BlobHashes != nil || args.BlobFeeCap != nil
|
||||||
|
|
|
||||||
|
|
@ -192,7 +192,7 @@ func (h *GlogHandler) Handle(_ context.Context, r slog.Record) error {
|
||||||
frame, _ := fs.Next()
|
frame, _ := fs.Next()
|
||||||
|
|
||||||
for _, rule := range h.patterns {
|
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
|
h.siteCache[r.PC], lvl, ok = rule.level, rule.level, true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ type CounterSnapshot interface {
|
||||||
Count() int64
|
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 {
|
type Counter interface {
|
||||||
Clear()
|
Clear()
|
||||||
Dec(int64)
|
Dec(int64)
|
||||||
|
|
|
||||||
|
|
@ -2,12 +2,12 @@ package metrics
|
||||||
|
|
||||||
import "sync/atomic"
|
import "sync/atomic"
|
||||||
|
|
||||||
// gaugeSnapshot contains a readonly int64.
|
// GaugeSnapshot contains a readonly int64.
|
||||||
type GaugeSnapshot interface {
|
type GaugeSnapshot interface {
|
||||||
Value() int64
|
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 {
|
type Gauge interface {
|
||||||
Snapshot() GaugeSnapshot
|
Snapshot() GaugeSnapshot
|
||||||
Update(int64)
|
Update(int64)
|
||||||
|
|
|
||||||
|
|
@ -48,7 +48,7 @@ type gaugeFloat64Snapshot float64
|
||||||
// Value returns the value at the time the snapshot was taken.
|
// Value returns the value at the time the snapshot was taken.
|
||||||
func (g gaugeFloat64Snapshot) Value() float64 { return float64(g) }
|
func (g gaugeFloat64Snapshot) Value() float64 { return float64(g) }
|
||||||
|
|
||||||
// NilGauge is a no-op Gauge.
|
// NilGaugeFloat64 is a no-op Gauge.
|
||||||
type NilGaugeFloat64 struct{}
|
type NilGaugeFloat64 struct{}
|
||||||
|
|
||||||
func (NilGaugeFloat64) Snapshot() GaugeFloat64Snapshot { return NilGaugeFloat64{} }
|
func (NilGaugeFloat64) Snapshot() GaugeFloat64Snapshot { return NilGaugeFloat64{} }
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ type GaugeInfoSnapshot interface {
|
||||||
Value() GaugeInfoValue
|
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 {
|
type GaugeInfo interface {
|
||||||
Update(GaugeInfoValue)
|
Update(GaugeInfoValue)
|
||||||
Snapshot() GaugeInfoSnapshot
|
Snapshot() GaugeInfoSnapshot
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
package metrics
|
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 {
|
type Healthcheck interface {
|
||||||
Check()
|
Check()
|
||||||
Error() error
|
Error() error
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ type HistogramSnapshot interface {
|
||||||
SampleSnapshot
|
SampleSnapshot
|
||||||
}
|
}
|
||||||
|
|
||||||
// Histograms calculate distribution statistics from a series of int64 values.
|
// Histogram calculates distribution statistics from a series of int64 values.
|
||||||
type Histogram interface {
|
type Histogram interface {
|
||||||
Clear()
|
Clear()
|
||||||
Update(int64)
|
Update(int64)
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ type v2Reporter struct {
|
||||||
write api.WriteAPI
|
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) {
|
func InfluxDBV2WithTags(r metrics.Registry, d time.Duration, endpoint string, token string, bucket string, organization string, namespace string, tags map[string]string) {
|
||||||
rep := &v2Reporter{
|
rep := &v2Reporter{
|
||||||
reg: r,
|
reg: r,
|
||||||
|
|
|
||||||
|
|
@ -41,6 +41,7 @@ const (
|
||||||
// needs of all CLs.
|
// needs of all CLs.
|
||||||
engineAPIBatchItemLimit = 2000
|
engineAPIBatchItemLimit = 2000
|
||||||
engineAPIBatchResponseSizeLimit = 250 * 1000 * 1000
|
engineAPIBatchResponseSizeLimit = 250 * 1000 * 1000
|
||||||
|
engineAPIBodyLimit = 128 * 1024 * 1024
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|
|
||||||
|
|
@ -453,14 +453,16 @@ func (n *Node) startRPC() error {
|
||||||
jwtSecret: secret,
|
jwtSecret: secret,
|
||||||
batchItemLimit: engineAPIBatchItemLimit,
|
batchItemLimit: engineAPIBatchItemLimit,
|
||||||
batchResponseSizeLimit: engineAPIBatchResponseSizeLimit,
|
batchResponseSizeLimit: engineAPIBatchResponseSizeLimit,
|
||||||
|
httpBodyLimit: engineAPIBodyLimit,
|
||||||
}
|
}
|
||||||
if err := server.enableRPC(allAPIs, httpConfig{
|
err := server.enableRPC(allAPIs, httpConfig{
|
||||||
CorsAllowedOrigins: DefaultAuthCors,
|
CorsAllowedOrigins: DefaultAuthCors,
|
||||||
Vhosts: n.config.AuthVirtualHosts,
|
Vhosts: n.config.AuthVirtualHosts,
|
||||||
Modules: DefaultAuthModules,
|
Modules: DefaultAuthModules,
|
||||||
prefix: DefaultAuthPrefix,
|
prefix: DefaultAuthPrefix,
|
||||||
rpcEndpointConfig: sharedConfig,
|
rpcEndpointConfig: sharedConfig,
|
||||||
}); err != nil {
|
})
|
||||||
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
servers = append(servers, server)
|
servers = append(servers, server)
|
||||||
|
|
|
||||||
|
|
@ -56,6 +56,7 @@ type rpcEndpointConfig struct {
|
||||||
jwtSecret []byte // optional JWT secret
|
jwtSecret []byte // optional JWT secret
|
||||||
batchItemLimit int
|
batchItemLimit int
|
||||||
batchResponseSizeLimit int
|
batchResponseSizeLimit int
|
||||||
|
httpBodyLimit int
|
||||||
}
|
}
|
||||||
|
|
||||||
type rpcHandler struct {
|
type rpcHandler struct {
|
||||||
|
|
@ -304,6 +305,9 @@ func (h *httpServer) enableRPC(apis []rpc.API, config httpConfig) error {
|
||||||
// Create RPC server and handler.
|
// Create RPC server and handler.
|
||||||
srv := rpc.NewServer()
|
srv := rpc.NewServer()
|
||||||
srv.SetBatchLimits(config.batchItemLimit, config.batchResponseSizeLimit)
|
srv.SetBatchLimits(config.batchItemLimit, config.batchResponseSizeLimit)
|
||||||
|
if config.httpBodyLimit > 0 {
|
||||||
|
srv.SetHTTPBodyLimit(config.httpBodyLimit)
|
||||||
|
}
|
||||||
if err := RegisterApis(apis, config.Modules, srv); err != nil {
|
if err := RegisterApis(apis, config.Modules, srv); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
@ -336,6 +340,9 @@ func (h *httpServer) enableWS(apis []rpc.API, config wsConfig) error {
|
||||||
// Create RPC server and handler.
|
// Create RPC server and handler.
|
||||||
srv := rpc.NewServer()
|
srv := rpc.NewServer()
|
||||||
srv.SetBatchLimits(config.batchItemLimit, config.batchResponseSizeLimit)
|
srv.SetBatchLimits(config.batchItemLimit, config.batchResponseSizeLimit)
|
||||||
|
if config.httpBodyLimit > 0 {
|
||||||
|
srv.SetHTTPBodyLimit(config.httpBodyLimit)
|
||||||
|
}
|
||||||
if err := RegisterApis(apis, config.Modules, srv); err != nil {
|
if err := RegisterApis(apis, config.Modules, srv); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -58,7 +58,7 @@ func newMeteredConn(conn UDPConn) UDPConn {
|
||||||
return &meteredUdpConn{UDPConn: conn}
|
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) {
|
func (c *meteredUdpConn) ReadFromUDP(b []byte) (n int, addr *net.UDPAddr, err error) {
|
||||||
n, addr, err = c.UDPConn.ReadFromUDP(b)
|
n, addr, err = c.UDPConn.ReadFromUDP(b)
|
||||||
ingressTrafficMeter.Mark(int64(n))
|
ingressTrafficMeter.Mark(int64(n))
|
||||||
|
|
|
||||||
|
|
@ -58,6 +58,7 @@ var (
|
||||||
TerminalTotalDifficulty: MainnetTerminalTotalDifficulty, // 58_750_000_000_000_000_000_000
|
TerminalTotalDifficulty: MainnetTerminalTotalDifficulty, // 58_750_000_000_000_000_000_000
|
||||||
TerminalTotalDifficultyPassed: true,
|
TerminalTotalDifficultyPassed: true,
|
||||||
ShanghaiTime: newUint64(1681338455),
|
ShanghaiTime: newUint64(1681338455),
|
||||||
|
CancunTime: newUint64(1710338135),
|
||||||
Ethash: new(EthashConfig),
|
Ethash: new(EthashConfig),
|
||||||
}
|
}
|
||||||
// HoleskyChainConfig contains the chain parameters to run a node on the Holesky test network.
|
// 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)
|
lastFork.name, cur.name, cur.block)
|
||||||
} else {
|
} else {
|
||||||
return fmt.Errorf("unsupported fork ordering: %v not enabled, but %v enabled at timestamp %v",
|
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
|
// 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)
|
lastFork.name, lastFork.block, cur.name, cur.block)
|
||||||
} else if lastFork.timestamp != nil && *lastFork.timestamp > *cur.timestamp {
|
} 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",
|
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
|
// Timestamp based forks can follow block based ones, but not the other way around
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ import (
|
||||||
const (
|
const (
|
||||||
VersionMajor = 1 // Major version component of the current release
|
VersionMajor = 1 // Major version component of the current release
|
||||||
VersionMinor = 13 // Minor 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
|
VersionMeta = "unstable" // Version metadata to append to the version string
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
18
rpc/http.go
18
rpc/http.go
|
|
@ -33,8 +33,8 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
maxRequestContentLength = 1024 * 1024 * 5
|
defaultBodyLimit = 5 * 1024 * 1024
|
||||||
contentType = "application/json"
|
contentType = "application/json"
|
||||||
)
|
)
|
||||||
|
|
||||||
// https://www.jsonrpc.org/historical/json-rpc-over-http.html#id13
|
// https://www.jsonrpc.org/historical/json-rpc-over-http.html#id13
|
||||||
|
|
@ -253,8 +253,8 @@ type httpServerConn struct {
|
||||||
r *http.Request
|
r *http.Request
|
||||||
}
|
}
|
||||||
|
|
||||||
func newHTTPServerConn(r *http.Request, w http.ResponseWriter) ServerCodec {
|
func (s *Server) newHTTPServerConn(r *http.Request, w http.ResponseWriter) ServerCodec {
|
||||||
body := io.LimitReader(r.Body, maxRequestContentLength)
|
body := io.LimitReader(r.Body, int64(s.httpBodyLimit))
|
||||||
conn := &httpServerConn{Reader: body, Writer: w, r: r}
|
conn := &httpServerConn{Reader: body, Writer: w, r: r}
|
||||||
|
|
||||||
encoder := func(v any, isErrorResponse bool) error {
|
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)
|
w.WriteHeader(http.StatusOK)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if code, err := validateRequest(r); err != nil {
|
if code, err := s.validateRequest(r); err != nil {
|
||||||
http.Error(w, err.Error(), code)
|
http.Error(w, err.Error(), code)
|
||||||
return
|
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
|
// until EOF, writes the response to w, and orders the server to process a
|
||||||
// single request.
|
// single request.
|
||||||
w.Header().Set("content-type", contentType)
|
w.Header().Set("content-type", contentType)
|
||||||
codec := newHTTPServerConn(r, w)
|
codec := s.newHTTPServerConn(r, w)
|
||||||
defer codec.close()
|
defer codec.close()
|
||||||
s.serveSingleRequest(ctx, codec)
|
s.serveSingleRequest(ctx, codec)
|
||||||
}
|
}
|
||||||
|
|
||||||
// validateRequest returns a non-zero response code and error message if the
|
// validateRequest returns a non-zero response code and error message if the
|
||||||
// request is invalid.
|
// 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 {
|
if r.Method == http.MethodPut || r.Method == http.MethodDelete {
|
||||||
return http.StatusMethodNotAllowed, errors.New("method not allowed")
|
return http.StatusMethodNotAllowed, errors.New("method not allowed")
|
||||||
}
|
}
|
||||||
if r.ContentLength > maxRequestContentLength {
|
if r.ContentLength > int64(s.httpBodyLimit) {
|
||||||
err := fmt.Errorf("content length too large (%d>%d)", r.ContentLength, maxRequestContentLength)
|
err := fmt.Errorf("content length too large (%d>%d)", r.ContentLength, s.httpBodyLimit)
|
||||||
return http.StatusRequestEntityTooLarge, err
|
return http.StatusRequestEntityTooLarge, err
|
||||||
}
|
}
|
||||||
// Allow OPTIONS (regardless of content-type)
|
// 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) {
|
func confirmRequestValidationCode(t *testing.T, method, contentType, body string, expectedStatusCode int) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
|
s := NewServer()
|
||||||
request := httptest.NewRequest(method, "http://url.com", strings.NewReader(body))
|
request := httptest.NewRequest(method, "http://url.com", strings.NewReader(body))
|
||||||
if len(contentType) > 0 {
|
if len(contentType) > 0 {
|
||||||
request.Header.Set("Content-Type", contentType)
|
request.Header.Set("Content-Type", contentType)
|
||||||
}
|
}
|
||||||
code, err := validateRequest(request)
|
code, err := s.validateRequest(request)
|
||||||
if code == 0 {
|
if code == 0 {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("validation: got error %v, expected nil", err)
|
t.Errorf("validation: got error %v, expected nil", err)
|
||||||
|
|
@ -64,7 +66,7 @@ func TestHTTPErrorResponseWithPut(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestHTTPErrorResponseWithMaxContentLength(t *testing.T) {
|
func TestHTTPErrorResponseWithMaxContentLength(t *testing.T) {
|
||||||
body := make([]rune, maxRequestContentLength+1)
|
body := make([]rune, defaultBodyLimit+1)
|
||||||
confirmRequestValidationCode(t,
|
confirmRequestValidationCode(t,
|
||||||
http.MethodPost, contentType, string(body), http.StatusRequestEntityTooLarge)
|
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.
|
// This checks that maxRequestContentLength is not applied to the response of a request.
|
||||||
func TestHTTPRespBodyUnlimited(t *testing.T) {
|
func TestHTTPRespBodyUnlimited(t *testing.T) {
|
||||||
const respLength = maxRequestContentLength * 3
|
const respLength = defaultBodyLimit * 3
|
||||||
|
|
||||||
s := NewServer()
|
s := NewServer()
|
||||||
defer s.Stop()
|
defer s.Stop()
|
||||||
|
|
|
||||||
|
|
@ -51,13 +51,15 @@ type Server struct {
|
||||||
run atomic.Bool
|
run atomic.Bool
|
||||||
batchItemLimit int
|
batchItemLimit int
|
||||||
batchResponseLimit int
|
batchResponseLimit int
|
||||||
|
httpBodyLimit int
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewServer creates a new server instance with no registered handlers.
|
// NewServer creates a new server instance with no registered handlers.
|
||||||
func NewServer() *Server {
|
func NewServer() *Server {
|
||||||
server := &Server{
|
server := &Server{
|
||||||
idgen: randomIDGenerator(),
|
idgen: randomIDGenerator(),
|
||||||
codecs: make(map[ServerCodec]struct{}),
|
codecs: make(map[ServerCodec]struct{}),
|
||||||
|
httpBodyLimit: defaultBodyLimit,
|
||||||
}
|
}
|
||||||
server.run.Store(true)
|
server.run.Store(true)
|
||||||
// Register the default service providing meta information about the RPC service such
|
// 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
|
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
|
// 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
|
// 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
|
// 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.
|
// This call sends slightly less than the limit and should work.
|
||||||
var result echoResult
|
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 {
|
if err := client.Call(&result, "test_echo", arg, 1); err != nil {
|
||||||
t.Fatalf("valid call didn't work: %v", err)
|
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.
|
// 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)
|
err = client.Call(&result, "test_echo", arg)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Fatal("no error for too large call")
|
t.Fatal("no error for too large call")
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue