mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-06-12 01:41:36 +00:00
core: clean txlookup entries on chain rewind(#33744)
This commit is contained in:
parent
01e33d14be
commit
f21fd30e7e
3 changed files with 208 additions and 15 deletions
|
|
@ -1068,8 +1068,19 @@ func (bc *BlockChain) setHeadBeyondRoot(head uint64, time uint64, root common.Ha
|
|||
}
|
||||
return headHeader, wipe // Only force wipe if full synced
|
||||
}
|
||||
deleteTxLookupEntries := func(db ethdb.KeyValueWriter, hash common.Hash, num uint64) {
|
||||
body := rawdb.ReadBody(bc.db, hash, num)
|
||||
if body == nil {
|
||||
return
|
||||
}
|
||||
for _, tx := range body.Transactions {
|
||||
rawdb.DeleteTxLookupEntry(db, tx.Hash())
|
||||
}
|
||||
}
|
||||
// Rewind the header chain, deleting all block bodies until then
|
||||
delFn := func(db ethdb.KeyValueWriter, hash common.Hash, num uint64) {
|
||||
deleteTxLookupEntries(db, hash, num)
|
||||
|
||||
// Ignore the error here since light client won't hit this path
|
||||
frozen, _ := bc.db.Ancients()
|
||||
if num+1 <= frozen {
|
||||
|
|
@ -1086,31 +1097,36 @@ func (bc *BlockChain) setHeadBeyondRoot(head uint64, time uint64, root common.Ha
|
|||
rawdb.DeleteBody(db, hash, num)
|
||||
rawdb.DeleteReceipts(db, hash, num)
|
||||
}
|
||||
// Todo(rjl493456442) txlookup, log index, etc
|
||||
// Todo(rjl493456442) log index, etc
|
||||
}
|
||||
// If SetHead was only called as a chain reparation method, try to skip
|
||||
// touching the header chain altogether, unless the freezer is broken
|
||||
if repair {
|
||||
if target, force := updateFn(bc.db, bc.CurrentBlock()); force {
|
||||
bc.hc.SetHead(target.Number.Uint64(), nil, delFn)
|
||||
}
|
||||
} else {
|
||||
// Rewind the chain to the requested head and keep going backwards until a
|
||||
// block with a state is found or snap sync pivot is passed
|
||||
if time > 0 {
|
||||
log.Warn("Rewinding blockchain to timestamp", "target", time)
|
||||
bc.hc.SetHeadWithTimestamp(time, updateFn, delFn)
|
||||
bc.txLookupLock.Lock()
|
||||
func() {
|
||||
defer bc.txLookupLock.Unlock()
|
||||
|
||||
if repair {
|
||||
if target, force := updateFn(bc.db, bc.CurrentBlock()); force {
|
||||
bc.hc.SetHead(target.Number.Uint64(), nil, delFn)
|
||||
}
|
||||
} else {
|
||||
log.Warn("Rewinding blockchain to block", "target", head)
|
||||
bc.hc.SetHead(head, updateFn, delFn)
|
||||
// Rewind the chain to the requested head and keep going backwards until a
|
||||
// block with a state is found or snap sync pivot is passed
|
||||
if time > 0 {
|
||||
log.Warn("Rewinding blockchain to timestamp", "target", time)
|
||||
bc.hc.SetHeadWithTimestamp(time, updateFn, delFn)
|
||||
} else {
|
||||
log.Warn("Rewinding blockchain to block", "target", head)
|
||||
bc.hc.SetHead(head, updateFn, delFn)
|
||||
}
|
||||
}
|
||||
}
|
||||
bc.txLookupCache.Purge()
|
||||
}()
|
||||
// Clear out any stale content from the caches
|
||||
bc.bodyCache.Purge()
|
||||
bc.bodyRLPCache.Purge()
|
||||
bc.receiptsCache.Purge()
|
||||
bc.blockCache.Purge()
|
||||
bc.txLookupCache.Purge()
|
||||
|
||||
// Clear safe block, finalized block if needed
|
||||
headBlock := bc.CurrentBlock()
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ import (
|
|||
"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/pebble"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"github.com/ethereum/go-ethereum/triedb"
|
||||
|
|
@ -2081,6 +2082,121 @@ func testSetHeadWithScheme(t *testing.T, tt *rewindTest, snapshots bool, scheme
|
|||
}
|
||||
}
|
||||
|
||||
func TestSetHeadTxLookupCleanupWithAncients(t *testing.T) {
|
||||
type freezer interface {
|
||||
Freeze() error
|
||||
Ancients() (uint64, error)
|
||||
}
|
||||
|
||||
datadir := t.TempDir()
|
||||
ancient := filepath.Join(datadir, "ancient")
|
||||
|
||||
pdb, err := pebble.New(datadir, 0, 0, "", false)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create persistent key-value database: %v", err)
|
||||
}
|
||||
db, err := rawdb.Open(pdb, rawdb.OpenOptions{Ancient: ancient})
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create persistent freezer database: %v", err)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
var (
|
||||
testBankKey, _ = crypto.GenerateKey()
|
||||
testBankAddress = crypto.PubkeyToAddress(testBankKey.PublicKey)
|
||||
gspec = &Genesis{
|
||||
BaseFee: big.NewInt(params.InitialBaseFee),
|
||||
Config: params.AllEthashProtocolChanges,
|
||||
Alloc: types.GenesisAlloc{testBankAddress: {Balance: big.NewInt(1000000000000000000)}},
|
||||
}
|
||||
engine = ethash.NewFullFaker()
|
||||
options = &BlockChainConfig{
|
||||
TrieCleanLimit: 256,
|
||||
TrieDirtyLimit: 256,
|
||||
TrieTimeLimit: 5 * time.Minute,
|
||||
SnapshotLimit: 0,
|
||||
TxLookupLimit: -1,
|
||||
StateScheme: rawdb.HashScheme,
|
||||
}
|
||||
signer = types.HomesteadSigner{}
|
||||
nonce = uint64(0)
|
||||
retainedTx *types.Transaction
|
||||
removedTx *types.Transaction
|
||||
)
|
||||
|
||||
chain, err := NewBlockChain(db, gspec, engine, options)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create chain: %v", err)
|
||||
}
|
||||
defer chain.Stop()
|
||||
|
||||
_, blocks, _ := GenerateChainWithGenesis(gspec, engine, 24, func(i int, b *BlockGen) {
|
||||
tx, err := types.SignTx(types.NewTransaction(nonce, common.HexToAddress("0xdeadbeef"), big.NewInt(1), params.TxGas, b.header.BaseFee, nil), signer, testBankKey)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to sign transaction: %v", err)
|
||||
}
|
||||
b.AddTx(tx)
|
||||
switch i {
|
||||
case 7:
|
||||
retainedTx = tx
|
||||
case 15:
|
||||
removedTx = tx
|
||||
}
|
||||
nonce++
|
||||
})
|
||||
if _, err := chain.InsertChain(blocks); err != nil {
|
||||
t.Fatalf("failed to insert canonical chain: %v", err)
|
||||
}
|
||||
if retainedTx == nil || removedTx == nil {
|
||||
t.Fatal("failed to capture test transactions")
|
||||
}
|
||||
|
||||
chain.triedb.Commit(blocks[7].Root(), false)
|
||||
chain.triedb.Close()
|
||||
chain.triedb = triedb.NewDatabase(chain.db, &triedb.Config{HashDB: hashdb.Defaults})
|
||||
|
||||
chain.SetFinalized(blocks[15].Header())
|
||||
if err := db.(freezer).Freeze(); err != nil {
|
||||
t.Fatalf("failed to freeze chain data: %v", err)
|
||||
}
|
||||
|
||||
if entry := rawdb.ReadTxLookupEntry(chain.db, retainedTx.Hash()); entry == nil || *entry != 8 {
|
||||
t.Fatalf("retained txlookup setup failed: have %v, want 8", entry)
|
||||
}
|
||||
if entry := rawdb.ReadTxLookupEntry(chain.db, removedTx.Hash()); entry == nil || *entry != 16 {
|
||||
t.Fatalf("removed txlookup setup failed: have %v, want 16", entry)
|
||||
}
|
||||
|
||||
if err := chain.SetHead(8); err != nil {
|
||||
t.Fatalf("failed to rewind chain: %v", err)
|
||||
}
|
||||
|
||||
if head := chain.CurrentBlock(); head.Number.Uint64() != 8 {
|
||||
t.Fatalf("head block mismatch after rewind: have %d, want 8", head.Number.Uint64())
|
||||
}
|
||||
if frozen, err := db.(freezer).Ancients(); err != nil {
|
||||
t.Fatalf("failed to retrieve ancient count: %v", err)
|
||||
} else if frozen != 9 {
|
||||
t.Fatalf("ancient count mismatch after rewind: have %d, want 9", frozen)
|
||||
}
|
||||
|
||||
if entry := rawdb.ReadTxLookupEntry(chain.db, removedTx.Hash()); entry != nil {
|
||||
t.Fatalf("removed txlookup still exists after rewind: %d", *entry)
|
||||
}
|
||||
if tx, _, _, _ := rawdb.ReadCanonicalTransaction(chain.db, removedTx.Hash()); tx != nil {
|
||||
t.Fatalf("removed canonical transaction still exists after rewind: %s", removedTx.Hash())
|
||||
}
|
||||
|
||||
if entry := rawdb.ReadTxLookupEntry(chain.db, retainedTx.Hash()); entry == nil || *entry != 8 {
|
||||
t.Fatalf("retained txlookup mismatch after rewind: have %v, want 8", entry)
|
||||
}
|
||||
if tx, _, blockNumber, _ := rawdb.ReadCanonicalTransaction(chain.db, retainedTx.Hash()); tx == nil {
|
||||
t.Fatal("retained canonical transaction missing after rewind")
|
||||
} else if blockNumber != 8 {
|
||||
t.Fatalf("retained canonical transaction number mismatch: have %d, want 8", blockNumber)
|
||||
}
|
||||
}
|
||||
|
||||
// verifyNoGaps checks that there are no gaps after the initial set of blocks in
|
||||
// the database and errors if found.
|
||||
func verifyNoGaps(t *testing.T, chain *BlockChain, canonical bool, inserted types.Blocks) {
|
||||
|
|
|
|||
|
|
@ -4544,3 +4544,64 @@ func TestSetHeadBeyondRootFinalizedBug(t *testing.T) {
|
|||
currentFinal.Number.Uint64())
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetHeadTxLookupCleanup(t *testing.T) {
|
||||
for _, scheme := range []string{rawdb.HashScheme, rawdb.PathScheme} {
|
||||
t.Run(scheme, func(t *testing.T) {
|
||||
_, _, blockchain, err := newCanonical(ethash.NewFaker(), 100, true, scheme)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create pristine chain: %v", err)
|
||||
}
|
||||
defer blockchain.Stop()
|
||||
|
||||
retainedBlock := blockchain.GetBlockByNumber(50)
|
||||
if retainedBlock == nil {
|
||||
t.Fatal("retained block not found")
|
||||
}
|
||||
removedBlock := blockchain.CurrentBlock()
|
||||
if removedBlock.Number.Uint64() != 100 {
|
||||
t.Fatalf("setup failed: expected head 100, got %d", removedBlock.Number.Uint64())
|
||||
}
|
||||
|
||||
retainedTx := types.NewTransaction(0, common.Address{0x01}, big.NewInt(0), params.TxGas, big.NewInt(1), nil)
|
||||
removedTx := types.NewTransaction(1, common.Address{0x02}, big.NewInt(0), params.TxGas, big.NewInt(1), nil)
|
||||
|
||||
rawdb.WriteBody(blockchain.db, retainedBlock.Hash(), retainedBlock.NumberU64(), &types.Body{Transactions: []*types.Transaction{retainedTx}})
|
||||
rawdb.WriteBody(blockchain.db, removedBlock.Hash(), removedBlock.Number.Uint64(), &types.Body{Transactions: []*types.Transaction{removedTx}})
|
||||
rawdb.WriteTxLookupEntries(blockchain.db, retainedBlock.NumberU64(), []common.Hash{retainedTx.Hash()})
|
||||
rawdb.WriteTxLookupEntries(blockchain.db, removedBlock.Number.Uint64(), []common.Hash{removedTx.Hash()})
|
||||
|
||||
if entry := rawdb.ReadTxLookupEntry(blockchain.db, retainedTx.Hash()); entry == nil || *entry != retainedBlock.NumberU64() {
|
||||
t.Fatalf("retained txlookup setup failed: have %v, want %d", entry, retainedBlock.NumberU64())
|
||||
}
|
||||
if entry := rawdb.ReadTxLookupEntry(blockchain.db, removedTx.Hash()); entry == nil || *entry != removedBlock.Number.Uint64() {
|
||||
t.Fatalf("removed txlookup setup failed: have %v, want %d", entry, removedBlock.Number.Uint64())
|
||||
}
|
||||
|
||||
if err := blockchain.SetHead(retainedBlock.NumberU64()); err != nil {
|
||||
t.Fatalf("failed to rewind chain: %v", err)
|
||||
}
|
||||
|
||||
if entry := rawdb.ReadTxLookupEntry(blockchain.db, removedTx.Hash()); entry != nil {
|
||||
t.Fatalf("removed txlookup still exists after rewind: %d", *entry)
|
||||
}
|
||||
if tx, _, _, _ := rawdb.ReadCanonicalTransaction(blockchain.db, removedTx.Hash()); tx != nil {
|
||||
t.Fatalf("removed canonical transaction still exists after rewind: %s", removedTx.Hash())
|
||||
}
|
||||
|
||||
if entry := rawdb.ReadTxLookupEntry(blockchain.db, retainedTx.Hash()); entry == nil || *entry != retainedBlock.NumberU64() {
|
||||
t.Fatalf("retained txlookup mismatch after rewind: have %v, want %d", entry, retainedBlock.NumberU64())
|
||||
}
|
||||
if tx, blockHash, blockNumber, _ := rawdb.ReadCanonicalTransaction(blockchain.db, retainedTx.Hash()); tx == nil {
|
||||
t.Fatal("retained canonical transaction missing after rewind")
|
||||
} else {
|
||||
if blockNumber != retainedBlock.NumberU64() {
|
||||
t.Fatalf("retained canonical transaction number mismatch: have %d, want %d", blockNumber, retainedBlock.NumberU64())
|
||||
}
|
||||
if blockHash != retainedBlock.Hash() {
|
||||
t.Fatalf("retained canonical transaction hash mismatch: have %s, want %s", blockHash, retainedBlock.Hash())
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue