mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-06-12 18:01:36 +00:00
cmd/utils: validate imported history before insertion
This commit is contained in:
parent
5d0e18f775
commit
c2299d4cb1
2 changed files with 205 additions and 0 deletions
|
|
@ -49,6 +49,7 @@ import (
|
|||
"github.com/ethereum/go-ethereum/node"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
"github.com/ethereum/go-ethereum/trie"
|
||||
"github.com/ethereum/go-ethereum/triedb"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
|
@ -249,6 +250,47 @@ func readList(filename string) ([]string, error) {
|
|||
return strings.Split(string(b), "\n"), nil
|
||||
}
|
||||
|
||||
func validateImportedHistoryBlock(block *types.Block, receipts types.Receipts) error {
|
||||
header := block.Header()
|
||||
|
||||
if hash := types.CalcUncleHash(block.Uncles()); hash != header.UncleHash {
|
||||
return fmt.Errorf("uncle root hash mismatch (header value %x, calculated %x)", header.UncleHash, hash)
|
||||
}
|
||||
if hash := types.DeriveSha(block.Transactions(), trie.NewStackTrie(nil)); hash != header.TxHash {
|
||||
return fmt.Errorf("transaction root hash mismatch (header value %x, calculated %x)", header.TxHash, hash)
|
||||
}
|
||||
if header.WithdrawalsHash != nil {
|
||||
if block.Withdrawals() == nil {
|
||||
return errors.New("missing withdrawals in block body")
|
||||
}
|
||||
if hash := types.DeriveSha(block.Withdrawals(), trie.NewStackTrie(nil)); hash != *header.WithdrawalsHash {
|
||||
return fmt.Errorf("withdrawals root hash mismatch (header value %x, calculated %x)", *header.WithdrawalsHash, hash)
|
||||
}
|
||||
} else if block.Withdrawals() != nil {
|
||||
return errors.New("withdrawals present in block body")
|
||||
}
|
||||
|
||||
var blobs int
|
||||
for i, tx := range block.Transactions() {
|
||||
blobs += len(tx.BlobHashes())
|
||||
if tx.BlobTxSidecar() != nil {
|
||||
return fmt.Errorf("unexpected blob sidecar in transaction at index %d", i)
|
||||
}
|
||||
}
|
||||
if header.BlobGasUsed != nil {
|
||||
if want := *header.BlobGasUsed / params.BlobTxBlobGasPerBlob; uint64(blobs) != want {
|
||||
return fmt.Errorf("blob gas used mismatch (header %v, calculated %v)", *header.BlobGasUsed, blobs*params.BlobTxBlobGasPerBlob)
|
||||
}
|
||||
} else if blobs > 0 {
|
||||
return errors.New("data blobs present in block body")
|
||||
}
|
||||
|
||||
if hash := types.DeriveSha(receipts, trie.NewStackTrie(nil)); hash != header.ReceiptHash {
|
||||
return fmt.Errorf("receipt root hash mismatch (header value %x, calculated %x)", header.ReceiptHash, hash)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ImportHistory imports Era1 files containing historical block information,
|
||||
// starting from genesis. The assumption is held that the provided chain
|
||||
// segment in Era1 file should all be canonical and verified.
|
||||
|
|
@ -346,6 +388,9 @@ func ImportHistory(chain *core.BlockChain, dir string, network string, from func
|
|||
if err != nil {
|
||||
return fmt.Errorf("error reading receipts %d: %w", it.Number(), err)
|
||||
}
|
||||
if err := validateImportedHistoryBlock(block, receipts); err != nil {
|
||||
return fmt.Errorf("error validating block %d: %w", it.Number(), err)
|
||||
}
|
||||
blocks = append(blocks, block)
|
||||
receiptsList = append(receiptsList, receipts)
|
||||
if len(blocks) == importBatchSize {
|
||||
|
|
|
|||
|
|
@ -27,6 +27,8 @@ import (
|
|||
"testing"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/consensus"
|
||||
"github.com/ethereum/go-ethereum/consensus/beacon"
|
||||
"github.com/ethereum/go-ethereum/consensus/ethash"
|
||||
"github.com/ethereum/go-ethereum/core"
|
||||
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||
|
|
@ -191,3 +193,161 @@ func TestHistoryImportAndExport(t *testing.T) {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestImportHistoryRejectsForgedTxRootArchive(t *testing.T) {
|
||||
genesis, honest, honestReceipts, _ := makeHistorySingleTxFixture(t)
|
||||
header := honest.Header()
|
||||
header.TxHash = common.HexToHash("0x1234")
|
||||
if header.TxHash == honest.TxHash() {
|
||||
t.Fatal("fixture is not malformed: tx root unexpectedly matches original block")
|
||||
}
|
||||
poisoned := honest.WithSeal(header)
|
||||
|
||||
dir := t.TempDir()
|
||||
if err := writeEra1(dir, poisoned, honestReceipts); err != nil {
|
||||
t.Fatalf("failed to build forged-tx-root era1 archive: %v", err)
|
||||
}
|
||||
imported := newHistoryImportChain(t, genesis, ethash.NewFaker())
|
||||
|
||||
if err := ImportHistory(imported, dir, "mainnet", onedb.From); err == nil {
|
||||
t.Fatal("ImportHistory unexpectedly accepted forged tx root")
|
||||
} else if !strings.Contains(err.Error(), "transaction root hash mismatch") {
|
||||
t.Fatalf("unexpected ImportHistory error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestImportHistoryRejectsForgedWithdrawalsArchive(t *testing.T) {
|
||||
genesis, honest, honestReceipts := makeMergedHistoryFixture(t)
|
||||
header := honest.Header()
|
||||
if header.WithdrawalsHash == nil {
|
||||
t.Fatal("fixture does not have a withdrawals root")
|
||||
}
|
||||
forgedWithdrawalsHash := common.HexToHash("0x5678")
|
||||
if forgedWithdrawalsHash == *header.WithdrawalsHash {
|
||||
t.Fatal("fixture is not malformed: withdrawals root unexpectedly matches original block")
|
||||
}
|
||||
header.WithdrawalsHash = &forgedWithdrawalsHash
|
||||
poisoned := honest.WithSeal(header)
|
||||
|
||||
dir := t.TempDir()
|
||||
if err := writeEra1(dir, poisoned, honestReceipts); err != nil {
|
||||
t.Fatalf("failed to build forged-withdrawals era1 archive: %v", err)
|
||||
}
|
||||
imported := newHistoryImportChain(t, genesis, beacon.New(ethash.NewFaker()))
|
||||
|
||||
if err := ImportHistory(imported, dir, "mainnet", onedb.From); err == nil {
|
||||
t.Fatal("ImportHistory unexpectedly accepted forged withdrawals root")
|
||||
} else if !strings.Contains(err.Error(), "withdrawals root hash mismatch") {
|
||||
t.Fatalf("unexpected ImportHistory error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestImportHistoryRejectsForgedReceiptArchive(t *testing.T) {
|
||||
genesis, honest, honestReceipts, _ := makeHistorySingleTxFixture(t)
|
||||
forgedReceipt := new(types.Receipt)
|
||||
*forgedReceipt = *honestReceipts[0]
|
||||
forgedReceipt.Status = types.ReceiptStatusFailed
|
||||
if got := types.DeriveSha(types.Receipts{forgedReceipt}, trie.NewStackTrie(nil)); got == honest.ReceiptHash() {
|
||||
t.Fatalf("fixture is not malformed: receipt root unexpectedly matches header %s", got)
|
||||
}
|
||||
|
||||
dir := t.TempDir()
|
||||
if err := writeEra1(dir, honest, types.Receipts{forgedReceipt}); err != nil {
|
||||
t.Fatalf("failed to build forged-receipt era1 archive: %v", err)
|
||||
}
|
||||
imported := newHistoryImportChain(t, genesis, ethash.NewFaker())
|
||||
|
||||
if err := ImportHistory(imported, dir, "mainnet", onedb.From); err == nil {
|
||||
t.Fatal("ImportHistory unexpectedly accepted forged receipt root")
|
||||
} else if !strings.Contains(err.Error(), "receipt root hash mismatch") {
|
||||
t.Fatalf("unexpected ImportHistory error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func writeEra1(dir string, block *types.Block, receipts types.Receipts) error {
|
||||
buf := new(bytes.Buffer)
|
||||
builder := onedb.NewBuilder(buf)
|
||||
|
||||
td := new(big.Int)
|
||||
if diff := block.Difficulty(); diff != nil {
|
||||
td.Set(diff)
|
||||
}
|
||||
root, err := func() (common.Hash, error) {
|
||||
if err := builder.Add(block, receipts, td); err != nil {
|
||||
return common.Hash{}, err
|
||||
}
|
||||
return builder.Finalize()
|
||||
}()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
filename := filepath.Join(dir, onedb.Filename("mainnet", 0, root))
|
||||
if err := os.WriteFile(filename, buf.Bytes(), 0o644); err != nil {
|
||||
return err
|
||||
}
|
||||
sum := sha256.Sum256(buf.Bytes())
|
||||
return os.WriteFile(filepath.Join(dir, "checksums.txt"), []byte(common.BytesToHash(sum[:]).Hex()), 0o644)
|
||||
}
|
||||
|
||||
func makeHistorySingleTxFixture(t *testing.T) (*core.Genesis, *types.Block, types.Receipts, *types.Transaction) {
|
||||
t.Helper()
|
||||
|
||||
key, err := crypto.GenerateKey()
|
||||
if err != nil {
|
||||
t.Fatalf("generate key: %v", err)
|
||||
}
|
||||
address := crypto.PubkeyToAddress(key.PublicKey)
|
||||
genesis := &core.Genesis{
|
||||
Config: params.TestChainConfig,
|
||||
Alloc: types.GenesisAlloc{address: {Balance: big.NewInt(1000000000000000000)}},
|
||||
}
|
||||
signer := types.LatestSigner(genesis.Config)
|
||||
_, blocks, receipts := core.GenerateChainWithGenesis(genesis, ethash.NewFaker(), 1, func(i int, g *core.BlockGen) {
|
||||
tx, err := types.SignNewTx(key, signer, &types.LegacyTx{
|
||||
Nonce: uint64(i),
|
||||
To: &common.Address{0xaa},
|
||||
Value: big.NewInt(1),
|
||||
Gas: 50_000,
|
||||
GasPrice: big.NewInt(1_000_000_000),
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("sign tx: %v", err)
|
||||
}
|
||||
g.AddTx(tx)
|
||||
})
|
||||
if len(blocks) != 1 || len(receipts) != 1 || len(receipts[0]) != 1 || len(blocks[0].Transactions()) != 1 {
|
||||
t.Fatalf("unexpected fixture lengths: blocks=%d receiptBlocks=%d receipts=%d txs=%d", len(blocks), len(receipts), len(receipts[0]), len(blocks[0].Transactions()))
|
||||
}
|
||||
return genesis, blocks[0], receipts[0], blocks[0].Transactions()[0]
|
||||
}
|
||||
|
||||
func makeMergedHistoryFixture(t *testing.T) (*core.Genesis, *types.Block, types.Receipts) {
|
||||
t.Helper()
|
||||
|
||||
config := *params.MergedTestChainConfig
|
||||
genesis := &core.Genesis{Config: &config}
|
||||
_, blocks, receipts := core.GenerateChainWithGenesis(genesis, beacon.New(ethash.NewFaker()), 1, nil)
|
||||
if len(blocks) != 1 || len(receipts) != 1 {
|
||||
t.Fatalf("unexpected merged fixture lengths: blocks=%d receiptBlocks=%d", len(blocks), len(receipts))
|
||||
}
|
||||
return genesis, blocks[0], receipts[0]
|
||||
}
|
||||
|
||||
func newHistoryImportChain(t *testing.T, genesis *core.Genesis, engine consensus.Engine) *core.BlockChain {
|
||||
t.Helper()
|
||||
|
||||
db, err := rawdb.Open(rawdb.NewMemoryDatabase(), rawdb.OpenOptions{})
|
||||
if err != nil {
|
||||
t.Fatalf("failed to open import db: %v", err)
|
||||
}
|
||||
t.Cleanup(func() { db.Close() })
|
||||
|
||||
genesis.MustCommit(db, triedb.NewDatabase(db, triedb.HashDefaults))
|
||||
imported, err := core.NewBlockChain(db, genesis, engine, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to initialize imported chain: %v", err)
|
||||
}
|
||||
t.Cleanup(imported.Stop)
|
||||
return imported
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue