cmd/utils: export chain with block-level accessList (#35369)
Some checks are pending
/ Linux Build (push) Waiting to run
/ Linux Build (arm) (push) Waiting to run
/ Keeper Build (push) Waiting to run
/ Windows Build (push) Waiting to run
/ Docker Image (push) Waiting to run

This commit is contained in:
rjl493456442 2026-07-18 00:54:07 +08:00 committed by GitHub
parent 80b58f649a
commit dddbaa4bf3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 96 additions and 7 deletions

View file

@ -40,6 +40,7 @@ import (
"github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/state/snapshot"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/types/bal"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/eth/ethconfig"
"github.com/ethereum/go-ethereum/ethdb"
@ -57,6 +58,38 @@ const (
importBatchSize = 2500
)
// exportBlock is the on-disk representation used by geth export and import.
//
// It intentionally differs from types.Block's network encoding: a block access
// list is stored as an optional trailing field so that an exported chain retains
// EIP-7928 data. Older export files, which end at Withdrawals, remain valid.
type exportBlock struct {
Header *types.Header
Txs []*types.Transaction
Uncles []*types.Header
Withdrawals []*types.Withdrawal `rlp:"optional"`
AccessList *bal.BlockAccessList `rlp:"optional"`
}
func makeExportBlock(block *types.Block) exportBlock {
return exportBlock{
Header: block.Header(),
Txs: block.Transactions(),
Uncles: block.Uncles(),
Withdrawals: block.Withdrawals(),
AccessList: block.AccessList(),
}
}
func (b exportBlock) block() *types.Block {
block := types.NewBlockWithHeader(b.Header).WithBody(types.Body{
Transactions: b.Txs,
Uncles: b.Uncles,
Withdrawals: b.Withdrawals,
})
return block.WithAccessListUnsafe(b.AccessList)
}
type EraFileFormat int
// ErrImportInterrupted is returned when the user interrupts the import process.
@ -202,18 +235,19 @@ func ImportChain(chain *core.BlockChain, fn string) error {
}
i := 0
for ; i < importBatchSize; i++ {
var b types.Block
if err := stream.Decode(&b); err == io.EOF {
var record exportBlock
if err := stream.Decode(&record); err == io.EOF {
break
} else if err != nil {
return fmt.Errorf("at block %d: %v", n, err)
}
b := record.block()
// don't import first block
if b.NumberU64() == 0 {
i--
continue
}
blocks[i] = &b
blocks[i] = b
n++
}
if i == 0 {
@ -411,8 +445,7 @@ func ExportChain(blockchain *core.BlockChain, fn string) error {
writer = gzip.NewWriter(writer)
defer writer.(*gzip.Writer).Close()
}
// Iterate over the blocks and export them
if err := blockchain.Export(writer); err != nil {
if err := exportChain(blockchain, writer, 0, blockchain.CurrentBlock().Number.Uint64()); err != nil {
return err
}
log.Info("Exported blockchain", "file", fn)
@ -436,14 +469,34 @@ func ExportAppendChain(blockchain *core.BlockChain, fn string, first uint64, las
writer = gzip.NewWriter(writer)
defer writer.(*gzip.Writer).Close()
}
// Iterate over the blocks and export them
if err := blockchain.ExportN(writer, first, last); err != nil {
if err := exportChain(blockchain, writer, first, last); err != nil {
return err
}
log.Info("Exported blockchain to", "file", fn)
return nil
}
func exportChain(chain *core.BlockChain, writer io.Writer, first, last uint64) error {
if first > last {
return fmt.Errorf("export failed: first (%d) is greater than last (%d)", first, last)
}
var parentHash common.Hash
for number := first; number <= last; number++ {
block := chain.GetBlockByNumber(number)
if block == nil {
return fmt.Errorf("export failed on #%d: not found", number)
}
if number > first && block.ParentHash() != parentHash {
return errors.New("export failed: chain reorg during export")
}
parentHash = block.Hash()
if err := rlp.Encode(writer, makeExportBlock(block)); err != nil {
return err
}
}
return nil
}
// ExportHistory exports blockchain history into the specified directory,
// following the Era format.
func ExportHistory(bc *core.BlockChain, dir string, first, last uint64, newBuilder func(io.Writer) era.Builder, filename func(network string, epoch int, lastBlockHash common.Hash) string) error {

View file

@ -18,12 +18,17 @@ package utils
import (
"fmt"
"math/big"
"os"
"reflect"
"strings"
"testing"
"time"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/types/bal"
"github.com/ethereum/go-ethereum/rlp"
)
@ -38,6 +43,37 @@ func TestExportGzip(t *testing.T) {
testExport(t, f)
}
func TestExportBlockAccessList(t *testing.T) {
accessList := &bal.BlockAccessList{{Address: common.HexToAddress("0x1234")}}
block := types.NewBlockWithHeader(&types.Header{Number: big.NewInt(1)}).WithAccessListUnsafe(accessList)
encoded, err := rlp.EncodeToBytes(makeExportBlock(block))
if err != nil {
t.Fatal(err)
}
var decoded exportBlock
if err := rlp.DecodeBytes(encoded, &decoded); err != nil {
t.Fatal(err)
}
if got := decoded.block().AccessList(); !reflect.DeepEqual(got, accessList) {
t.Fatalf("access list = %#v, want %#v", got, accessList)
}
}
func TestImportLegacyBlock(t *testing.T) {
block := types.NewBlockWithHeader(&types.Header{Number: big.NewInt(1)})
encoded, err := rlp.EncodeToBytes(block)
if err != nil {
t.Fatal(err)
}
var decoded exportBlock
if err := rlp.DecodeBytes(encoded, &decoded); err != nil {
t.Fatal(err)
}
if got := decoded.block().AccessList(); got != nil {
t.Fatalf("access list = %#v, want nil", got)
}
}
type testIterator struct {
index int
}