mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-02-26 15:47:21 +00:00
This implements a backing store for chain history based on era1 files. The new store is integrated with the freezer. Queries for blocks and receipts below the current freezer tail are handled by the era store. --------- Co-authored-by: Gary Rong <garyrong0905@gmail.com> Co-authored-by: Felix Lange <fjl@twurst.com> Co-authored-by: lightclient <lightclient@protonmail.com>
448 lines
12 KiB
Go
448 lines
12 KiB
Go
// Copyright 2024 The go-ethereum Authors
|
|
// This file is part of the go-ethereum library.
|
|
//
|
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU Lesser General Public License as published by
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// The go-ethereum library is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU Lesser General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU Lesser General Public License
|
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
package core
|
|
|
|
import (
|
|
"math/big"
|
|
"testing"
|
|
|
|
"github.com/ethereum/go-ethereum/common"
|
|
"github.com/ethereum/go-ethereum/consensus/ethash"
|
|
"github.com/ethereum/go-ethereum/core/rawdb"
|
|
"github.com/ethereum/go-ethereum/core/types"
|
|
"github.com/ethereum/go-ethereum/crypto"
|
|
"github.com/ethereum/go-ethereum/ethdb"
|
|
"github.com/ethereum/go-ethereum/params"
|
|
)
|
|
|
|
func verifyIndexes(t *testing.T, db ethdb.Database, block *types.Block, exist bool) {
|
|
for _, tx := range block.Transactions() {
|
|
lookup := rawdb.ReadTxLookupEntry(db, tx.Hash())
|
|
if exist && lookup == nil {
|
|
t.Fatalf("missing %d %x", block.NumberU64(), tx.Hash().Hex())
|
|
}
|
|
if !exist && lookup != nil {
|
|
t.Fatalf("unexpected %d %x", block.NumberU64(), tx.Hash().Hex())
|
|
}
|
|
}
|
|
}
|
|
|
|
func verify(t *testing.T, db ethdb.Database, blocks []*types.Block, expTail uint64) {
|
|
tail := rawdb.ReadTxIndexTail(db)
|
|
if tail == nil {
|
|
t.Fatal("Failed to write tx index tail")
|
|
return
|
|
}
|
|
if *tail != expTail {
|
|
t.Fatalf("Unexpected tx index tail, want %v, got %d", expTail, *tail)
|
|
}
|
|
for _, b := range blocks {
|
|
if b.Number().Uint64() < *tail {
|
|
verifyIndexes(t, db, b, false)
|
|
} else {
|
|
verifyIndexes(t, db, b, true)
|
|
}
|
|
}
|
|
}
|
|
|
|
func verifyNoIndex(t *testing.T, db ethdb.Database, blocks []*types.Block) {
|
|
tail := rawdb.ReadTxIndexTail(db)
|
|
if tail != nil {
|
|
t.Fatalf("Unexpected tx index tail %d", *tail)
|
|
}
|
|
for _, b := range blocks {
|
|
verifyIndexes(t, db, b, false)
|
|
}
|
|
}
|
|
|
|
// TestTxIndexer tests the functionalities for managing transaction indexes.
|
|
func TestTxIndexer(t *testing.T) {
|
|
var (
|
|
testBankKey, _ = crypto.GenerateKey()
|
|
testBankAddress = crypto.PubkeyToAddress(testBankKey.PublicKey)
|
|
testBankFunds = big.NewInt(1000000000000000000)
|
|
|
|
gspec = &Genesis{
|
|
Config: params.TestChainConfig,
|
|
Alloc: types.GenesisAlloc{testBankAddress: {Balance: testBankFunds}},
|
|
BaseFee: big.NewInt(params.InitialBaseFee),
|
|
}
|
|
engine = ethash.NewFaker()
|
|
nonce = uint64(0)
|
|
chainHead = uint64(128)
|
|
)
|
|
_, blocks, receipts := GenerateChainWithGenesis(gspec, engine, int(chainHead), func(i int, gen *BlockGen) {
|
|
tx, _ := types.SignTx(types.NewTransaction(nonce, common.HexToAddress("0xdeadbeef"), big.NewInt(1000), params.TxGas, big.NewInt(10*params.InitialBaseFee), nil), types.HomesteadSigner{}, testBankKey)
|
|
gen.AddTx(tx)
|
|
nonce += 1
|
|
})
|
|
var cases = []struct {
|
|
limits []uint64
|
|
tails []uint64
|
|
}{
|
|
{
|
|
limits: []uint64{0, 1, 64, 129, 0},
|
|
tails: []uint64{0, 128, 65, 0, 0},
|
|
},
|
|
{
|
|
limits: []uint64{64, 1, 64, 0},
|
|
tails: []uint64{65, 128, 65, 0},
|
|
},
|
|
{
|
|
limits: []uint64{127, 1, 64, 0},
|
|
tails: []uint64{2, 128, 65, 0},
|
|
},
|
|
{
|
|
limits: []uint64{128, 1, 64, 0},
|
|
tails: []uint64{1, 128, 65, 0},
|
|
},
|
|
{
|
|
limits: []uint64{129, 1, 64, 0},
|
|
tails: []uint64{0, 128, 65, 0},
|
|
},
|
|
}
|
|
for _, c := range cases {
|
|
db, _ := rawdb.Open(rawdb.NewMemoryDatabase(), rawdb.OpenOptions{})
|
|
rawdb.WriteAncientBlocks(db, append([]*types.Block{gspec.ToBlock()}, blocks...), types.EncodeBlockReceiptLists(append([]types.Receipts{{}}, receipts...)))
|
|
|
|
// Index the initial blocks from ancient store
|
|
indexer := &txIndexer{
|
|
limit: 0,
|
|
db: db,
|
|
}
|
|
for i, limit := range c.limits {
|
|
indexer.limit = limit
|
|
indexer.run(chainHead, make(chan struct{}), make(chan struct{}))
|
|
verify(t, db, blocks, c.tails[i])
|
|
}
|
|
db.Close()
|
|
}
|
|
}
|
|
|
|
func TestTxIndexerRepair(t *testing.T) {
|
|
var (
|
|
testBankKey, _ = crypto.GenerateKey()
|
|
testBankAddress = crypto.PubkeyToAddress(testBankKey.PublicKey)
|
|
testBankFunds = big.NewInt(1000000000000000000)
|
|
|
|
gspec = &Genesis{
|
|
Config: params.TestChainConfig,
|
|
Alloc: types.GenesisAlloc{testBankAddress: {Balance: testBankFunds}},
|
|
BaseFee: big.NewInt(params.InitialBaseFee),
|
|
}
|
|
engine = ethash.NewFaker()
|
|
nonce = uint64(0)
|
|
chainHead = uint64(128)
|
|
)
|
|
_, blocks, receipts := GenerateChainWithGenesis(gspec, engine, int(chainHead), func(i int, gen *BlockGen) {
|
|
tx, _ := types.SignTx(types.NewTransaction(nonce, common.HexToAddress("0xdeadbeef"), big.NewInt(1000), params.TxGas, big.NewInt(10*params.InitialBaseFee), nil), types.HomesteadSigner{}, testBankKey)
|
|
gen.AddTx(tx)
|
|
nonce += 1
|
|
})
|
|
tailPointer := func(n uint64) *uint64 {
|
|
return &n
|
|
}
|
|
var cases = []struct {
|
|
limit uint64
|
|
head uint64
|
|
cutoff uint64
|
|
expTail *uint64
|
|
}{
|
|
// if *tail > head => purge indexes
|
|
{
|
|
limit: 0,
|
|
head: chainHead / 2,
|
|
cutoff: 0,
|
|
expTail: tailPointer(0),
|
|
},
|
|
{
|
|
limit: 1, // tail = 128
|
|
head: chainHead / 2, // newhead = 64
|
|
cutoff: 0,
|
|
expTail: nil,
|
|
},
|
|
{
|
|
limit: 64, // tail = 65
|
|
head: chainHead / 2, // newhead = 64
|
|
cutoff: 0,
|
|
expTail: nil,
|
|
},
|
|
{
|
|
limit: 65, // tail = 64
|
|
head: chainHead / 2, // newhead = 64
|
|
cutoff: 0,
|
|
expTail: tailPointer(64),
|
|
},
|
|
{
|
|
limit: 66, // tail = 63
|
|
head: chainHead / 2, // newhead = 64
|
|
cutoff: 0,
|
|
expTail: tailPointer(63),
|
|
},
|
|
|
|
// if tail < cutoff => remove indexes below cutoff
|
|
{
|
|
limit: 0, // tail = 0
|
|
head: chainHead, // head = 128
|
|
cutoff: chainHead, // cutoff = 128
|
|
expTail: tailPointer(chainHead),
|
|
},
|
|
{
|
|
limit: 1, // tail = 128
|
|
head: chainHead, // head = 128
|
|
cutoff: chainHead, // cutoff = 128
|
|
expTail: tailPointer(128),
|
|
},
|
|
{
|
|
limit: 2, // tail = 127
|
|
head: chainHead, // head = 128
|
|
cutoff: chainHead, // cutoff = 128
|
|
expTail: tailPointer(chainHead),
|
|
},
|
|
{
|
|
limit: 2, // tail = 127
|
|
head: chainHead, // head = 128
|
|
cutoff: chainHead / 2, // cutoff = 64
|
|
expTail: tailPointer(127),
|
|
},
|
|
|
|
// if head < cutoff => purge indexes
|
|
{
|
|
limit: 0, // tail = 0
|
|
head: chainHead, // head = 128
|
|
cutoff: 2 * chainHead, // cutoff = 256
|
|
expTail: nil,
|
|
},
|
|
{
|
|
limit: 64, // tail = 65
|
|
head: chainHead, // head = 128
|
|
cutoff: chainHead / 2, // cutoff = 64
|
|
expTail: tailPointer(65),
|
|
},
|
|
}
|
|
for _, c := range cases {
|
|
db, _ := rawdb.Open(rawdb.NewMemoryDatabase(), rawdb.OpenOptions{})
|
|
encReceipts := types.EncodeBlockReceiptLists(append([]types.Receipts{{}}, receipts...))
|
|
rawdb.WriteAncientBlocks(db, append([]*types.Block{gspec.ToBlock()}, blocks...), encReceipts)
|
|
|
|
// Index the initial blocks from ancient store
|
|
indexer := &txIndexer{
|
|
limit: c.limit,
|
|
db: db,
|
|
}
|
|
indexer.run(chainHead, make(chan struct{}), make(chan struct{}))
|
|
|
|
indexer.cutoff = c.cutoff
|
|
indexer.repair(c.head)
|
|
|
|
if c.expTail == nil {
|
|
verifyNoIndex(t, db, blocks)
|
|
} else {
|
|
verify(t, db, blocks, *c.expTail)
|
|
}
|
|
db.Close()
|
|
}
|
|
}
|
|
|
|
func TestTxIndexerReport(t *testing.T) {
|
|
var (
|
|
testBankKey, _ = crypto.GenerateKey()
|
|
testBankAddress = crypto.PubkeyToAddress(testBankKey.PublicKey)
|
|
testBankFunds = big.NewInt(1000000000000000000)
|
|
|
|
gspec = &Genesis{
|
|
Config: params.TestChainConfig,
|
|
Alloc: types.GenesisAlloc{testBankAddress: {Balance: testBankFunds}},
|
|
BaseFee: big.NewInt(params.InitialBaseFee),
|
|
}
|
|
engine = ethash.NewFaker()
|
|
nonce = uint64(0)
|
|
chainHead = uint64(128)
|
|
)
|
|
_, blocks, receipts := GenerateChainWithGenesis(gspec, engine, int(chainHead), func(i int, gen *BlockGen) {
|
|
tx, _ := types.SignTx(types.NewTransaction(nonce, common.HexToAddress("0xdeadbeef"), big.NewInt(1000), params.TxGas, big.NewInt(10*params.InitialBaseFee), nil), types.HomesteadSigner{}, testBankKey)
|
|
gen.AddTx(tx)
|
|
nonce += 1
|
|
})
|
|
tailPointer := func(n uint64) *uint64 {
|
|
return &n
|
|
}
|
|
var cases = []struct {
|
|
head uint64
|
|
limit uint64
|
|
cutoff uint64
|
|
tail *uint64
|
|
expIndexed uint64
|
|
expRemaining uint64
|
|
}{
|
|
// The entire chain is supposed to be indexed
|
|
{
|
|
// head = 128, limit = 0, cutoff = 0 => all: 129
|
|
head: chainHead,
|
|
limit: 0,
|
|
cutoff: 0,
|
|
|
|
// tail = 0
|
|
tail: tailPointer(0),
|
|
expIndexed: 129,
|
|
expRemaining: 0,
|
|
},
|
|
{
|
|
// head = 128, limit = 0, cutoff = 0 => all: 129
|
|
head: chainHead,
|
|
limit: 0,
|
|
cutoff: 0,
|
|
|
|
// tail = 1
|
|
tail: tailPointer(1),
|
|
expIndexed: 128,
|
|
expRemaining: 1,
|
|
},
|
|
{
|
|
// head = 128, limit = 0, cutoff = 0 => all: 129
|
|
head: chainHead,
|
|
limit: 0,
|
|
cutoff: 0,
|
|
|
|
// tail = 128
|
|
tail: tailPointer(chainHead),
|
|
expIndexed: 1,
|
|
expRemaining: 128,
|
|
},
|
|
{
|
|
// head = 128, limit = 256, cutoff = 0 => all: 129
|
|
head: chainHead,
|
|
limit: 256,
|
|
cutoff: 0,
|
|
|
|
// tail = 0
|
|
tail: tailPointer(0),
|
|
expIndexed: 129,
|
|
expRemaining: 0,
|
|
},
|
|
|
|
// The chain with specific range is supposed to be indexed
|
|
{
|
|
// head = 128, limit = 64, cutoff = 0 => index: [65, 128]
|
|
head: chainHead,
|
|
limit: 64,
|
|
cutoff: 0,
|
|
|
|
// tail = 0, part of them need to be unindexed
|
|
tail: tailPointer(0),
|
|
expIndexed: 129,
|
|
expRemaining: 0,
|
|
},
|
|
{
|
|
// head = 128, limit = 64, cutoff = 0 => index: [65, 128]
|
|
head: chainHead,
|
|
limit: 64,
|
|
cutoff: 0,
|
|
|
|
// tail = 64, one of them needs to be unindexed
|
|
tail: tailPointer(64),
|
|
expIndexed: 65,
|
|
expRemaining: 0,
|
|
},
|
|
{
|
|
// head = 128, limit = 64, cutoff = 0 => index: [65, 128]
|
|
head: chainHead,
|
|
limit: 64,
|
|
cutoff: 0,
|
|
|
|
// tail = 65, all of them have been indexed
|
|
tail: tailPointer(65),
|
|
expIndexed: 64,
|
|
expRemaining: 0,
|
|
},
|
|
{
|
|
// head = 128, limit = 64, cutoff = 0 => index: [65, 128]
|
|
head: chainHead,
|
|
limit: 64,
|
|
cutoff: 0,
|
|
|
|
// tail = 66, one of them has to be indexed
|
|
tail: tailPointer(66),
|
|
expIndexed: 63,
|
|
expRemaining: 1,
|
|
},
|
|
|
|
// The chain with configured cutoff, the chain range could be capped
|
|
{
|
|
// head = 128, limit = 64, cutoff = 66 => index: [66, 128]
|
|
head: chainHead,
|
|
limit: 64,
|
|
cutoff: 66,
|
|
|
|
// tail = 0, part of them need to be unindexed
|
|
tail: tailPointer(0),
|
|
expIndexed: 129,
|
|
expRemaining: 0,
|
|
},
|
|
{
|
|
// head = 128, limit = 64, cutoff = 66 => index: [66, 128]
|
|
head: chainHead,
|
|
limit: 64,
|
|
cutoff: 66,
|
|
|
|
// tail = 66, all of them have been indexed
|
|
tail: tailPointer(66),
|
|
expIndexed: 63,
|
|
expRemaining: 0,
|
|
},
|
|
{
|
|
// head = 128, limit = 64, cutoff = 66 => index: [66, 128]
|
|
head: chainHead,
|
|
limit: 64,
|
|
cutoff: 66,
|
|
|
|
// tail = 67, one of them has to be indexed
|
|
tail: tailPointer(67),
|
|
expIndexed: 62,
|
|
expRemaining: 1,
|
|
},
|
|
{
|
|
// head = 128, limit = 64, cutoff = 256 => index: [66, 128]
|
|
head: chainHead,
|
|
limit: 0,
|
|
cutoff: 256,
|
|
tail: nil,
|
|
expIndexed: 0,
|
|
expRemaining: 0,
|
|
},
|
|
}
|
|
for _, c := range cases {
|
|
db, _ := rawdb.Open(rawdb.NewMemoryDatabase(), rawdb.OpenOptions{})
|
|
encReceipts := types.EncodeBlockReceiptLists(append([]types.Receipts{{}}, receipts...))
|
|
rawdb.WriteAncientBlocks(db, append([]*types.Block{gspec.ToBlock()}, blocks...), encReceipts)
|
|
|
|
// Index the initial blocks from ancient store
|
|
indexer := &txIndexer{
|
|
limit: c.limit,
|
|
cutoff: c.cutoff,
|
|
db: db,
|
|
}
|
|
p := indexer.report(c.head, c.tail)
|
|
if p.Indexed != c.expIndexed {
|
|
t.Fatalf("Unexpected indexed: %d, expected: %d", p.Indexed, c.expIndexed)
|
|
}
|
|
if p.Remaining != c.expRemaining {
|
|
t.Fatalf("Unexpected remaining: %d, expected: %d", p.Remaining, c.expRemaining)
|
|
}
|
|
db.Close()
|
|
}
|
|
}
|