Merge branch 'ethereum:master' into portal

This commit is contained in:
Chen Kai 2024-05-03 11:39:35 +08:00 committed by GitHub
commit e660629a78
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
87 changed files with 1733 additions and 2198 deletions

View file

@ -312,11 +312,10 @@ func (ks *KeyStore) Unlock(a accounts.Account, passphrase string) error {
// Lock removes the private key with the given address from memory. // Lock removes the private key with the given address from memory.
func (ks *KeyStore) Lock(addr common.Address) error { func (ks *KeyStore) Lock(addr common.Address) error {
ks.mu.Lock() ks.mu.Lock()
if unl, found := ks.unlocked[addr]; found { unl, found := ks.unlocked[addr]
ks.mu.Unlock() ks.mu.Unlock()
if found {
ks.expire(addr, unl, time.Duration(0)*time.Nanosecond) ks.expire(addr, unl, time.Duration(0)*time.Nanosecond)
} else {
ks.mu.Unlock()
} }
return nil return nil
} }

View file

@ -95,6 +95,7 @@ func (hub *Hub) readPairings() error {
} }
return err return err
} }
defer pairingFile.Close()
pairingData, err := io.ReadAll(pairingFile) pairingData, err := io.ReadAll(pairingFile)
if err != nil { if err != nil {

View file

@ -250,7 +250,7 @@ func ExecutableDataToBlock(params ExecutableData, versionedHashes []common.Hash,
BlobGasUsed: params.BlobGasUsed, BlobGasUsed: params.BlobGasUsed,
ParentBeaconRoot: beaconRoot, ParentBeaconRoot: beaconRoot,
} }
block := types.NewBlockWithHeader(header).WithBody(txs, nil /* uncles */).WithWithdrawals(params.Withdrawals) block := types.NewBlockWithHeader(header).WithBody(types.Body{Transactions: txs, Uncles: nil, Withdrawals: params.Withdrawals})
if block.Hash() != params.BlockHash { if block.Hash() != params.BlockHash {
return nil, fmt.Errorf("blockhash mismatch, want %x, got %x", params.BlockHash, block.Hash()) return nil, fmt.Errorf("blockhash mismatch, want %x, got %x", params.BlockHash, block.Hash())
} }

View file

@ -63,9 +63,7 @@ func convertPayload[T payloadType](payload T, parentRoot *zrntcommon.Root) (*typ
panic("unsupported block type") panic("unsupported block type")
} }
block := types.NewBlockWithHeader(&header) block := types.NewBlockWithHeader(&header).WithBody(types.Body{Transactions: transactions, Withdrawals: withdrawals})
block = block.WithBody(transactions, nil)
block = block.WithWithdrawals(withdrawals)
if hash := block.Hash(); hash != expectedHash { if hash := block.Hash(); hash != expectedHash {
return nil, fmt.Errorf("Sanity check failed, payload hash does not match (expected %x, got %x)", expectedHash, hash) return nil, fmt.Errorf("Sanity check failed, payload hash does not match (expected %x, got %x)", expectedHash, hash)
} }

View file

@ -32,7 +32,6 @@ import (
"github.com/ethereum/go-ethereum/internal/utesting" "github.com/ethereum/go-ethereum/internal/utesting"
"github.com/ethereum/go-ethereum/trie" "github.com/ethereum/go-ethereum/trie"
"github.com/ethereum/go-ethereum/trie/trienode" "github.com/ethereum/go-ethereum/trie/trienode"
"golang.org/x/crypto/sha3"
) )
func (c *Conn) snapRequest(code uint64, msg any) (any, error) { func (c *Conn) snapRequest(code uint64, msg any) (any, error) {
@ -905,7 +904,7 @@ func (s *Suite) snapGetByteCodes(t *utesting.T, tc *byteCodesTest) error {
// that the serving node is missing // that the serving node is missing
var ( var (
bytecodes = res.Codes bytecodes = res.Codes
hasher = sha3.NewLegacyKeccak256().(crypto.KeccakState) hasher = crypto.NewKeccakState()
hash = make([]byte, 32) hash = make([]byte, 32)
codes = make([][]byte, len(req.Hashes)) codes = make([][]byte, len(req.Hashes))
) )
@ -964,7 +963,7 @@ func (s *Suite) snapGetTrieNodes(t *utesting.T, tc *trieNodesTest) error {
// Cross reference the requested trienodes with the response to find gaps // Cross reference the requested trienodes with the response to find gaps
// that the serving node is missing // that the serving node is missing
hasher := sha3.NewLegacyKeccak256().(crypto.KeccakState) hasher := crypto.NewKeccakState()
hash := make([]byte, 32) hash := make([]byte, 32)
trienodes := res.Nodes trienodes := res.Nodes
if got, want := len(trienodes), len(tc.expHashes); got != want { if got, want := len(trienodes), len(tc.expHashes); got != want {

View file

@ -160,7 +160,7 @@ func (i *bbInput) ToBlock() *types.Block {
if i.Header.Difficulty != nil { if i.Header.Difficulty != nil {
header.Difficulty = i.Header.Difficulty header.Difficulty = i.Header.Difficulty
} }
return types.NewBlockWithHeader(header).WithBody(i.Txs, i.Ommers).WithWithdrawals(i.Withdrawals) return types.NewBlockWithHeader(header).WithBody(types.Body{Transactions: i.Txs, Uncles: i.Ommers, Withdrawals: i.Withdrawals})
} }
// SealBlock seals the given block using the configured engine. // SealBlock seals the given block using the configured engine.

View file

@ -296,7 +296,7 @@ func (g Alloc) OnAccount(addr *common.Address, dumpAccount state.DumpAccount) {
balance, _ := new(big.Int).SetString(dumpAccount.Balance, 0) balance, _ := new(big.Int).SetString(dumpAccount.Balance, 0)
var storage map[common.Hash]common.Hash var storage map[common.Hash]common.Hash
if dumpAccount.Storage != nil { if dumpAccount.Storage != nil {
storage = make(map[common.Hash]common.Hash) storage = make(map[common.Hash]common.Hash, len(dumpAccount.Storage))
for k, v := range dumpAccount.Storage { for k, v := range dumpAccount.Storage {
storage[k] = common.HexToHash(v) storage[k] = common.HexToHash(v)
} }

View file

@ -246,11 +246,17 @@ func removeDB(ctx *cli.Context) error {
ancientDir = config.Node.ResolvePath(ancientDir) ancientDir = config.Node.ResolvePath(ancientDir)
} }
// Delete state data // Delete state data
statePaths := []string{rootDir, filepath.Join(ancientDir, rawdb.StateFreezerName)} statePaths := []string{
rootDir,
filepath.Join(ancientDir, rawdb.StateFreezerName),
}
confirmAndRemoveDB(statePaths, "state data", ctx, removeStateDataFlag.Name) confirmAndRemoveDB(statePaths, "state data", ctx, removeStateDataFlag.Name)
// Delete ancient chain // Delete ancient chain
chainPaths := []string{filepath.Join(ancientDir, rawdb.ChainFreezerName)} chainPaths := []string{filepath.Join(
ancientDir,
rawdb.ChainFreezerName,
)}
confirmAndRemoveDB(chainPaths, "ancient chain", ctx, removeChainDataFlag.Name) confirmAndRemoveDB(chainPaths, "ancient chain", ctx, removeChainDataFlag.Name)
return nil return nil
} }

View file

@ -73,6 +73,7 @@ func testConsoleLogging(t *testing.T, format string, tStart, tEnd int) {
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
defer readFile.Close()
wantLines := split(readFile) wantLines := split(readFile)
haveLines := split(bytes.NewBuffer(haveB)) haveLines := split(bytes.NewBuffer(haveB))
for i, want := range wantLines { for i, want := range wantLines {
@ -109,6 +110,7 @@ func TestJsonLogging(t *testing.T) {
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
defer readFile.Close()
wantLines := split(readFile) wantLines := split(readFile)
haveLines := split(bytes.NewBuffer(haveB)) haveLines := split(bytes.NewBuffer(haveB))
for i, wantLine := range wantLines { for i, wantLine := range wantLines {

View file

@ -1940,13 +1940,15 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) {
Fatalf("Could not read genesis from database: %v", err) Fatalf("Could not read genesis from database: %v", err)
} }
if !genesis.Config.TerminalTotalDifficultyPassed { if !genesis.Config.TerminalTotalDifficultyPassed {
Fatalf("Bad developer-mode genesis configuration: terminalTotalDifficultyPassed must be true in developer mode") Fatalf("Bad developer-mode genesis configuration: terminalTotalDifficultyPassed must be true")
} }
if genesis.Config.TerminalTotalDifficulty == nil { if genesis.Config.TerminalTotalDifficulty == nil {
Fatalf("Bad developer-mode genesis configuration: terminalTotalDifficulty must be specified.") Fatalf("Bad developer-mode genesis configuration: terminalTotalDifficulty must be specified")
} else if genesis.Config.TerminalTotalDifficulty.Cmp(big.NewInt(0)) != 0 {
Fatalf("Bad developer-mode genesis configuration: terminalTotalDifficulty must be 0")
} }
if genesis.Difficulty.Cmp(genesis.Config.TerminalTotalDifficulty) != 1 { if genesis.Difficulty.Cmp(big.NewInt(0)) != 0 {
Fatalf("Bad developer-mode genesis configuration: genesis block difficulty must be > terminalTotalDifficulty") Fatalf("Bad developer-mode genesis configuration: difficulty must be 0")
} }
} }
chaindb.Close() chaindb.Close()

View file

@ -388,7 +388,7 @@ func (beacon *Beacon) FinalizeAndAssemble(chain consensus.ChainHeaderReader, hea
header.Root = state.IntermediateRoot(true) header.Root = state.IntermediateRoot(true)
// Assemble and return the final block. // Assemble and return the final block.
return types.NewBlockWithWithdrawals(header, body.Transactions, body.Uncles, receipts, body.Withdrawals, trie.NewStackTrie(nil)), nil return types.NewBlock(header, body, receipts, trie.NewStackTrie(nil)), nil
} }
// Seal generates a new sealing request for the given input block and pushes // Seal generates a new sealing request for the given input block and pushes

View file

@ -597,7 +597,7 @@ func (c *Clique) FinalizeAndAssemble(chain consensus.ChainHeaderReader, header *
header.Root = state.IntermediateRoot(chain.Config().IsEIP158(header.Number)) header.Root = state.IntermediateRoot(chain.Config().IsEIP158(header.Number))
// Assemble and return the final block for sealing. // Assemble and return the final block for sealing.
return types.NewBlock(header, body.Transactions, nil, receipts, trie.NewStackTrie(nil)), nil return types.NewBlock(header, &types.Body{Transactions: body.Transactions}, receipts, trie.NewStackTrie(nil)), nil
} }
// Authorize injects a private key into the consensus engine to mint new blocks // Authorize injects a private key into the consensus engine to mint new blocks

View file

@ -520,7 +520,7 @@ func (ethash *Ethash) FinalizeAndAssemble(chain consensus.ChainHeaderReader, hea
header.Root = state.IntermediateRoot(chain.Config().IsEIP158(header.Number)) header.Root = state.IntermediateRoot(chain.Config().IsEIP158(header.Number))
// Header seems complete, assemble into a block and return // Header seems complete, assemble into a block and return
return types.NewBlock(header, body.Transactions, body.Uncles, receipts, trie.NewStackTrie(nil)), nil return types.NewBlock(header, &types.Body{Transactions: body.Transactions, Uncles: body.Uncles}, receipts, trie.NewStackTrie(nil)), nil
} }
// SealHash returns the hash of a block prior to it being sealed. // SealHash returns the hash of a block prior to it being sealed.

View file

@ -1309,7 +1309,7 @@ func (bc *BlockChain) InsertReceiptChain(blockChain types.Blocks, receiptChain [
// Delete block data from the main database. // Delete block data from the main database.
var ( var (
batch = bc.db.NewBatch() batch = bc.db.NewBatch()
canonHashes = make(map[common.Hash]struct{}) canonHashes = make(map[common.Hash]struct{}, len(blockChain))
) )
for _, block := range blockChain { for _, block := range blockChain {
canonHashes[block.Hash()] = struct{}{} canonHashes[block.Hash()] = struct{}{}
@ -1963,7 +1963,7 @@ func (bc *BlockChain) processBlock(block *types.Block, statedb *state.StateDB, s
snapshotCommitTimer.Update(statedb.SnapshotCommits) // Snapshot commits are complete, we can mark them snapshotCommitTimer.Update(statedb.SnapshotCommits) // Snapshot commits are complete, we can mark them
triedbCommitTimer.Update(statedb.TrieDBCommits) // Trie database commits are complete, we can mark them triedbCommitTimer.Update(statedb.TrieDBCommits) // Trie database commits are complete, we can mark them
blockWriteTimer.Update(time.Since(wstart) - statedb.AccountCommits - statedb.StorageCommits - statedb.SnapshotCommits - statedb.TrieDBCommits) blockWriteTimer.Update(time.Since(wstart) - max(statedb.AccountCommits, statedb.StorageCommits) /* concurrent */ - statedb.SnapshotCommits - statedb.TrieDBCommits)
blockInsertTimer.UpdateSince(start) blockInsertTimer.UpdateSince(start)
return &blockProcessingResult{usedGas: usedGas, procTime: proctime, status: status}, nil return &blockProcessingResult{usedGas: usedGas, procTime: proctime, status: status}, nil

View file

@ -476,7 +476,7 @@ func (g *Genesis) ToBlock() *types.Block {
} }
} }
} }
return types.NewBlock(head, nil, nil, nil, trie.NewStackTrie(nil)).WithWithdrawals(withdrawals) return types.NewBlock(head, &types.Body{Withdrawals: withdrawals}, nil, trie.NewStackTrie(nil))
} }
// Commit writes the block and state of a genesis specification to the database. // Commit writes the block and state of a genesis specification to the database.

View file

@ -322,7 +322,7 @@ func TestVerkleGenesisCommit(t *testing.T) {
t.Fatalf("expected trie to be verkle") t.Fatalf("expected trie to be verkle")
} }
if !rawdb.ExistsAccountTrieNode(db, nil) { if !rawdb.HasAccountTrieNode(db, nil) {
t.Fatal("could not find node") t.Fatal("could not find node")
} }
} }

View file

@ -101,6 +101,7 @@ func main() {
if err != nil { if err != nil {
panic(err) panic(err)
} }
defer file.Close()
if err := json.NewDecoder(file).Decode(g); err != nil { if err := json.NewDecoder(file).Decode(g); err != nil {
panic(err) panic(err)
} }

View file

@ -753,7 +753,7 @@ func ReadBlock(db ethdb.Reader, hash common.Hash, number uint64) *types.Block {
if body == nil { if body == nil {
return nil return nil
} }
return types.NewBlockWithHeader(header).WithBody(body.Transactions, body.Uncles).WithWithdrawals(body.Withdrawals) return types.NewBlockWithHeader(header).WithBody(*body)
} }
// WriteBlock serializes a block into the database, header and body separately. // WriteBlock serializes a block into the database, header and body separately.
@ -843,7 +843,11 @@ func ReadBadBlock(db ethdb.Reader, hash common.Hash) *types.Block {
} }
for _, bad := range badBlocks { for _, bad := range badBlocks {
if bad.Header.Hash() == hash { if bad.Header.Hash() == hash {
return types.NewBlockWithHeader(bad.Header).WithBody(bad.Body.Transactions, bad.Body.Uncles).WithWithdrawals(bad.Body.Withdrawals) block := types.NewBlockWithHeader(bad.Header)
if bad.Body != nil {
block = block.WithBody(*bad.Body)
}
return block
} }
} }
return nil return nil
@ -862,7 +866,11 @@ func ReadAllBadBlocks(db ethdb.Reader) []*types.Block {
} }
var blocks []*types.Block var blocks []*types.Block
for _, bad := range badBlocks { for _, bad := range badBlocks {
blocks = append(blocks, types.NewBlockWithHeader(bad.Header).WithBody(bad.Body.Transactions, bad.Body.Uncles).WithWithdrawals(bad.Body.Withdrawals)) block := types.NewBlockWithHeader(bad.Header)
if bad.Body != nil {
block = block.WithBody(*bad.Body)
}
blocks = append(blocks, block)
} }
return blocks return blocks
} }

View file

@ -640,7 +640,7 @@ func makeTestBlocks(nblock int, txsPerBlock int) []*types.Block {
Number: big.NewInt(int64(i)), Number: big.NewInt(int64(i)),
Extra: []byte("test block"), Extra: []byte("test block"),
} }
blocks[i] = types.NewBlockWithHeader(header).WithBody(txs, nil) blocks[i] = types.NewBlockWithHeader(header).WithBody(types.Body{Transactions: txs})
blocks[i].Hash() // pre-cache the block hash blocks[i].Hash() // pre-cache the block hash
} }
return blocks return blocks

View file

@ -76,7 +76,7 @@ func TestLookupStorage(t *testing.T) {
tx3 := types.NewTransaction(3, common.BytesToAddress([]byte{0x33}), big.NewInt(333), 3333, big.NewInt(33333), []byte{0x33, 0x33, 0x33}) tx3 := types.NewTransaction(3, common.BytesToAddress([]byte{0x33}), big.NewInt(333), 3333, big.NewInt(33333), []byte{0x33, 0x33, 0x33})
txs := []*types.Transaction{tx1, tx2, tx3} txs := []*types.Transaction{tx1, tx2, tx3}
block := types.NewBlock(&types.Header{Number: big.NewInt(314)}, txs, nil, nil, newTestHasher()) block := types.NewBlock(&types.Header{Number: big.NewInt(314)}, &types.Body{Transactions: txs}, nil, newTestHasher())
// Check that no transactions entries are in a pristine database // Check that no transactions entries are in a pristine database
for i, tx := range txs { for i, tx := range txs {

View file

@ -24,7 +24,6 @@ import (
"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/log" "github.com/ethereum/go-ethereum/log"
"golang.org/x/crypto/sha3"
) )
// HashScheme is the legacy hash-based state scheme with which trie nodes are // HashScheme is the legacy hash-based state scheme with which trie nodes are
@ -50,7 +49,7 @@ const PathScheme = "path"
type hasher struct{ sha crypto.KeccakState } type hasher struct{ sha crypto.KeccakState }
var hasherPool = sync.Pool{ var hasherPool = sync.Pool{
New: func() interface{} { return &hasher{sha: sha3.NewLegacyKeccak256().(crypto.KeccakState)} }, New: func() interface{} { return &hasher{sha: crypto.NewKeccakState()} },
} }
func newHasher() *hasher { func newHasher() *hasher {
@ -65,33 +64,15 @@ func (h *hasher) release() {
hasherPool.Put(h) hasherPool.Put(h)
} }
// ReadAccountTrieNode retrieves the account trie node and the associated node // ReadAccountTrieNode retrieves the account trie node with the specified node path.
// hash with the specified node path. func ReadAccountTrieNode(db ethdb.KeyValueReader, path []byte) []byte {
func ReadAccountTrieNode(db ethdb.KeyValueReader, path []byte) ([]byte, common.Hash) { data, _ := db.Get(accountTrieNodeKey(path))
data, err := db.Get(accountTrieNodeKey(path)) return data
if err != nil {
return nil, common.Hash{}
}
h := newHasher()
defer h.release()
return data, h.hash(data)
} }
// HasAccountTrieNode checks the account trie node presence with the specified // HasAccountTrieNode checks the presence of the account trie node with the
// node path and the associated node hash.
func HasAccountTrieNode(db ethdb.KeyValueReader, path []byte, hash common.Hash) bool {
data, err := db.Get(accountTrieNodeKey(path))
if err != nil {
return false
}
h := newHasher()
defer h.release()
return h.hash(data) == hash
}
// ExistsAccountTrieNode checks the presence of the account trie node with the
// specified node path, regardless of the node hash. // specified node path, regardless of the node hash.
func ExistsAccountTrieNode(db ethdb.KeyValueReader, path []byte) bool { func HasAccountTrieNode(db ethdb.KeyValueReader, path []byte) bool {
has, err := db.Has(accountTrieNodeKey(path)) has, err := db.Has(accountTrieNodeKey(path))
if err != nil { if err != nil {
return false return false
@ -113,33 +94,15 @@ func DeleteAccountTrieNode(db ethdb.KeyValueWriter, path []byte) {
} }
} }
// ReadStorageTrieNode retrieves the storage trie node and the associated node // ReadStorageTrieNode retrieves the storage trie node with the specified node path.
// hash with the specified node path. func ReadStorageTrieNode(db ethdb.KeyValueReader, accountHash common.Hash, path []byte) []byte {
func ReadStorageTrieNode(db ethdb.KeyValueReader, accountHash common.Hash, path []byte) ([]byte, common.Hash) { data, _ := db.Get(storageTrieNodeKey(accountHash, path))
data, err := db.Get(storageTrieNodeKey(accountHash, path)) return data
if err != nil {
return nil, common.Hash{}
}
h := newHasher()
defer h.release()
return data, h.hash(data)
} }
// HasStorageTrieNode checks the storage trie node presence with the provided // HasStorageTrieNode checks the presence of the storage trie node with the
// node path and the associated node hash.
func HasStorageTrieNode(db ethdb.KeyValueReader, accountHash common.Hash, path []byte, hash common.Hash) bool {
data, err := db.Get(storageTrieNodeKey(accountHash, path))
if err != nil {
return false
}
h := newHasher()
defer h.release()
return h.hash(data) == hash
}
// ExistsStorageTrieNode checks the presence of the storage trie node with the
// specified account hash and node path, regardless of the node hash. // specified account hash and node path, regardless of the node hash.
func ExistsStorageTrieNode(db ethdb.KeyValueReader, accountHash common.Hash, path []byte) bool { func HasStorageTrieNode(db ethdb.KeyValueReader, accountHash common.Hash, path []byte) bool {
has, err := db.Has(storageTrieNodeKey(accountHash, path)) has, err := db.Has(storageTrieNodeKey(accountHash, path))
if err != nil { if err != nil {
return false return false
@ -198,10 +161,18 @@ func HasTrieNode(db ethdb.KeyValueReader, owner common.Hash, path []byte, hash c
case HashScheme: case HashScheme:
return HasLegacyTrieNode(db, hash) return HasLegacyTrieNode(db, hash)
case PathScheme: case PathScheme:
var blob []byte
if owner == (common.Hash{}) { if owner == (common.Hash{}) {
return HasAccountTrieNode(db, path, hash) blob = ReadAccountTrieNode(db, path)
} else {
blob = ReadStorageTrieNode(db, owner, path)
} }
return HasStorageTrieNode(db, owner, path, hash) if len(blob) == 0 {
return false
}
h := newHasher()
defer h.release()
return h.hash(blob) == hash // exists but not match
default: default:
panic(fmt.Sprintf("Unknown scheme %v", scheme)) panic(fmt.Sprintf("Unknown scheme %v", scheme))
} }
@ -209,43 +180,35 @@ func HasTrieNode(db ethdb.KeyValueReader, owner common.Hash, path []byte, hash c
// ReadTrieNode retrieves the trie node from database with the provided node info // ReadTrieNode retrieves the trie node from database with the provided node info
// and associated node hash. // and associated node hash.
// hashScheme-based lookup requires the following:
// - hash
//
// pathScheme-based lookup requires the following:
// - owner
// - path
func ReadTrieNode(db ethdb.KeyValueReader, owner common.Hash, path []byte, hash common.Hash, scheme string) []byte { func ReadTrieNode(db ethdb.KeyValueReader, owner common.Hash, path []byte, hash common.Hash, scheme string) []byte {
switch scheme { switch scheme {
case HashScheme: case HashScheme:
return ReadLegacyTrieNode(db, hash) return ReadLegacyTrieNode(db, hash)
case PathScheme: case PathScheme:
var ( var blob []byte
blob []byte
nHash common.Hash
)
if owner == (common.Hash{}) { if owner == (common.Hash{}) {
blob, nHash = ReadAccountTrieNode(db, path) blob = ReadAccountTrieNode(db, path)
} else { } else {
blob, nHash = ReadStorageTrieNode(db, owner, path) blob = ReadStorageTrieNode(db, owner, path)
} }
if nHash != hash { if len(blob) == 0 {
return nil return nil
} }
h := newHasher()
defer h.release()
if h.hash(blob) != hash {
return nil // exists but not match
}
return blob return blob
default: default:
panic(fmt.Sprintf("Unknown scheme %v", scheme)) panic(fmt.Sprintf("Unknown scheme %v", scheme))
} }
} }
// WriteTrieNode writes the trie node into database with the provided node info // WriteTrieNode writes the trie node into database with the provided node info.
// and associated node hash.
// hashScheme-based lookup requires the following:
// - hash
// //
// pathScheme-based lookup requires the following: // hash-scheme requires the node hash as the identifier.
// - owner // path-scheme requires the node owner and path as the identifier.
// - path
func WriteTrieNode(db ethdb.KeyValueWriter, owner common.Hash, path []byte, hash common.Hash, node []byte, scheme string) { func WriteTrieNode(db ethdb.KeyValueWriter, owner common.Hash, path []byte, hash common.Hash, node []byte, scheme string) {
switch scheme { switch scheme {
case HashScheme: case HashScheme:
@ -261,14 +224,10 @@ func WriteTrieNode(db ethdb.KeyValueWriter, owner common.Hash, path []byte, hash
} }
} }
// DeleteTrieNode deletes the trie node from database with the provided node info // DeleteTrieNode deletes the trie node from database with the provided node info.
// and associated node hash.
// hashScheme-based lookup requires the following:
// - hash
// //
// pathScheme-based lookup requires the following: // hash-scheme requires the node hash as the identifier.
// - owner // path-scheme requires the node owner and path as the identifier.
// - path
func DeleteTrieNode(db ethdb.KeyValueWriter, owner common.Hash, path []byte, hash common.Hash, scheme string) { func DeleteTrieNode(db ethdb.KeyValueWriter, owner common.Hash, path []byte, hash common.Hash, scheme string) {
switch scheme { switch scheme {
case HashScheme: case HashScheme:
@ -287,9 +246,8 @@ func DeleteTrieNode(db ethdb.KeyValueWriter, owner common.Hash, path []byte, has
// ReadStateScheme reads the state scheme of persistent state, or none // ReadStateScheme reads the state scheme of persistent state, or none
// if the state is not present in database. // if the state is not present in database.
func ReadStateScheme(db ethdb.Reader) string { func ReadStateScheme(db ethdb.Reader) string {
// Check if state in path-based scheme is present // Check if state in path-based scheme is present.
blob, _ := ReadAccountTrieNode(db, nil) if HasAccountTrieNode(db, nil) {
if len(blob) != 0 {
return PathScheme return PathScheme
} }
// The root node might be deleted during the initial snap sync, check // The root node might be deleted during the initial snap sync, check
@ -304,8 +262,7 @@ func ReadStateScheme(db ethdb.Reader) string {
if header == nil { if header == nil {
return "" // empty datadir return "" // empty datadir
} }
blob = ReadLegacyTrieNode(db, header.Root) if !HasLegacyTrieNode(db, header.Root) {
if len(blob) == 0 {
return "" // no state in disk return "" // no state in disk
} }
return HashScheme return HashScheme

View file

@ -16,7 +16,11 @@
package rawdb package rawdb
import "path/filepath" import (
"path/filepath"
"github.com/ethereum/go-ethereum/ethdb"
)
// The list of table names of chain freezer. // The list of table names of chain freezer.
const ( const (
@ -75,7 +79,15 @@ var (
// freezers the collections of all builtin freezers. // freezers the collections of all builtin freezers.
var freezers = []string{ChainFreezerName, StateFreezerName} var freezers = []string{ChainFreezerName, StateFreezerName}
// NewStateFreezer initializes the freezer for state history. // NewStateFreezer initializes the ancient store for state history.
func NewStateFreezer(ancientDir string, readOnly bool) (*ResettableFreezer, error) { //
return NewResettableFreezer(filepath.Join(ancientDir, StateFreezerName), "eth/db/state", readOnly, stateHistoryTableSize, stateFreezerNoSnappy) // - if the empty directory is given, initializes the pure in-memory
// state freezer (e.g. dev mode).
// - if non-empty directory is given, initializes the regular file-based
// state freezer.
func NewStateFreezer(ancientDir string, readOnly bool) (ethdb.ResettableAncientStore, error) {
if ancientDir == "" {
return NewMemoryFreezer(readOnly, stateFreezerNoSnappy), nil
}
return newResettableFreezer(filepath.Join(ancientDir, StateFreezerName), "eth/db/state", readOnly, stateHistoryTableSize, stateFreezerNoSnappy)
} }

View file

@ -89,20 +89,17 @@ func inspectFreezers(db ethdb.Database) ([]freezerInfo, error) {
infos = append(infos, info) infos = append(infos, info)
case StateFreezerName: case StateFreezerName:
if ReadStateScheme(db) != PathScheme {
continue
}
datadir, err := db.AncientDatadir() datadir, err := db.AncientDatadir()
if err != nil { if err != nil {
return nil, err return nil, err
} }
f, err := NewStateFreezer(datadir, true) f, err := NewStateFreezer(datadir, true)
if err != nil { if err != nil {
return nil, err continue // might be possible the state freezer is not existent
} }
defer f.Close() defer f.Close()
info, err := inspect(StateFreezerName, stateFreezerNoSnappy, f) info, err := inspect(freezer, stateFreezerNoSnappy, f)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View file

@ -0,0 +1,325 @@
// 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 ancienttest
import (
"bytes"
"reflect"
"testing"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/internal/testrand"
)
// TestAncientSuite runs a suite of tests against an ancient database
// implementation.
func TestAncientSuite(t *testing.T, newFn func(kinds []string) ethdb.AncientStore) {
// Test basic read methods
t.Run("BasicRead", func(t *testing.T) { basicRead(t, newFn) })
// Test batch read method
t.Run("BatchRead", func(t *testing.T) { batchRead(t, newFn) })
// Test basic write methods
t.Run("BasicWrite", func(t *testing.T) { basicWrite(t, newFn) })
// Test if data mutation is allowed after db write
t.Run("nonMutable", func(t *testing.T) { nonMutable(t, newFn) })
}
func basicRead(t *testing.T, newFn func(kinds []string) ethdb.AncientStore) {
var (
db = newFn([]string{"a"})
data = makeDataset(100, 32)
)
defer db.Close()
db.ModifyAncients(func(op ethdb.AncientWriteOp) error {
for i := 0; i < len(data); i++ {
op.AppendRaw("a", uint64(i), data[i])
}
return nil
})
db.TruncateTail(10)
db.TruncateHead(90)
// Test basic tail and head retrievals
tail, err := db.Tail()
if err != nil || tail != 10 {
t.Fatal("Failed to retrieve tail")
}
ancient, err := db.Ancients()
if err != nil || ancient != 90 {
t.Fatal("Failed to retrieve ancient")
}
// Test the deleted items shouldn't be reachable
var cases = []struct {
start int
limit int
}{
{0, 10},
{90, 100},
}
for _, c := range cases {
for i := c.start; i < c.limit; i++ {
exist, err := db.HasAncient("a", uint64(i))
if err != nil {
t.Fatalf("Failed to check presence, %v", err)
}
if exist {
t.Fatalf("Item %d is already truncated", uint64(i))
}
_, err = db.Ancient("a", uint64(i))
if err == nil {
t.Fatal("Error is expected for non-existent item")
}
}
}
// Test the items in range should be reachable
for i := 10; i < 90; i++ {
exist, err := db.HasAncient("a", uint64(i))
if err != nil {
t.Fatalf("Failed to check presence, %v", err)
}
if !exist {
t.Fatalf("Item %d is missing", uint64(i))
}
blob, err := db.Ancient("a", uint64(i))
if err != nil {
t.Fatalf("Failed to retrieve item, %v", err)
}
if !bytes.Equal(blob, data[i]) {
t.Fatalf("Unexpected item content, want: %v, got: %v", data[i], blob)
}
}
// Test the items in unknown table shouldn't be reachable
exist, err := db.HasAncient("b", uint64(0))
if err != nil {
t.Fatalf("Failed to check presence, %v", err)
}
if exist {
t.Fatal("Item in unknown table shouldn't be found")
}
_, err = db.Ancient("b", uint64(0))
if err == nil {
t.Fatal("Error is expected for unknown table")
}
}
func batchRead(t *testing.T, newFn func(kinds []string) ethdb.AncientStore) {
var (
db = newFn([]string{"a"})
data = makeDataset(100, 32)
)
defer db.Close()
db.ModifyAncients(func(op ethdb.AncientWriteOp) error {
for i := 0; i < 100; i++ {
op.AppendRaw("a", uint64(i), data[i])
}
return nil
})
db.TruncateTail(10)
db.TruncateHead(90)
// Test the items in range should be reachable
var cases = []struct {
start uint64
count uint64
maxSize uint64
expStart int
expLimit int
}{
// Items in range [10, 90) with no size limitation
{
10, 80, 0, 10, 90,
},
// Items in range [10, 90) with 32 size cap, single item is expected
{
10, 80, 32, 10, 11,
},
// Items in range [10, 90) with 31 size cap, single item is expected
{
10, 80, 31, 10, 11,
},
// Items in range [10, 90) with 32*80 size cap, all items are expected
{
10, 80, 32 * 80, 10, 90,
},
// Extra items above the last item are not returned
{
10, 90, 0, 10, 90,
},
}
for i, c := range cases {
batch, err := db.AncientRange("a", c.start, c.count, c.maxSize)
if err != nil {
t.Fatalf("Failed to retrieve item in range, %v", err)
}
if !reflect.DeepEqual(batch, data[c.expStart:c.expLimit]) {
t.Fatalf("Case %d, Batch content is not matched", i)
}
}
// Test out-of-range / zero-size retrieval should be rejected
_, err := db.AncientRange("a", 0, 1, 0)
if err == nil {
t.Fatal("Out-of-range retrieval should be rejected")
}
_, err = db.AncientRange("a", 90, 1, 0)
if err == nil {
t.Fatal("Out-of-range retrieval should be rejected")
}
_, err = db.AncientRange("a", 10, 0, 0)
if err == nil {
t.Fatal("Zero-size retrieval should be rejected")
}
// Test item in unknown table shouldn't be reachable
_, err = db.AncientRange("b", 10, 1, 0)
if err == nil {
t.Fatal("Item in unknown table shouldn't be found")
}
}
func basicWrite(t *testing.T, newFn func(kinds []string) ethdb.AncientStore) {
var (
db = newFn([]string{"a", "b"})
dataA = makeDataset(100, 32)
dataB = makeDataset(100, 32)
)
defer db.Close()
// The ancient write to tables should be aligned
_, err := db.ModifyAncients(func(op ethdb.AncientWriteOp) error {
for i := 0; i < 100; i++ {
op.AppendRaw("a", uint64(i), dataA[i])
}
return nil
})
if err == nil {
t.Fatal("Unaligned ancient write should be rejected")
}
// Test normal ancient write
size, err := db.ModifyAncients(func(op ethdb.AncientWriteOp) error {
for i := 0; i < 100; i++ {
op.AppendRaw("a", uint64(i), dataA[i])
op.AppendRaw("b", uint64(i), dataB[i])
}
return nil
})
if err != nil {
t.Fatalf("Failed to write ancient data %v", err)
}
wantSize := int64(6400)
if size != wantSize {
t.Fatalf("Ancient write size is not expected, want: %d, got: %d", wantSize, size)
}
// Write should work after head truncating
db.TruncateHead(90)
_, err = db.ModifyAncients(func(op ethdb.AncientWriteOp) error {
for i := 90; i < 100; i++ {
op.AppendRaw("a", uint64(i), dataA[i])
op.AppendRaw("b", uint64(i), dataB[i])
}
return nil
})
if err != nil {
t.Fatalf("Failed to write ancient data %v", err)
}
// Write should work after truncating everything
db.TruncateTail(0)
_, err = db.ModifyAncients(func(op ethdb.AncientWriteOp) error {
for i := 0; i < 100; i++ {
op.AppendRaw("a", uint64(i), dataA[i])
op.AppendRaw("b", uint64(i), dataB[i])
}
return nil
})
if err != nil {
t.Fatalf("Failed to write ancient data %v", err)
}
}
func nonMutable(t *testing.T, newFn func(kinds []string) ethdb.AncientStore) {
db := newFn([]string{"a"})
defer db.Close()
// We write 100 zero-bytes to the freezer and immediately mutate the slice
db.ModifyAncients(func(op ethdb.AncientWriteOp) error {
data := make([]byte, 100)
op.AppendRaw("a", uint64(0), data)
for i := range data {
data[i] = 0xff
}
return nil
})
// Now read it.
data, err := db.Ancient("a", uint64(0))
if err != nil {
t.Fatal(err)
}
for k, v := range data {
if v != 0 {
t.Fatalf("byte %d != 0: %x", k, v)
}
}
}
// TestResettableAncientSuite runs a suite of tests against a resettable ancient
// database implementation.
func TestResettableAncientSuite(t *testing.T, newFn func(kinds []string) ethdb.ResettableAncientStore) {
t.Run("Reset", func(t *testing.T) {
var (
db = newFn([]string{"a"})
data = makeDataset(100, 32)
)
defer db.Close()
db.ModifyAncients(func(op ethdb.AncientWriteOp) error {
for i := 0; i < 100; i++ {
op.AppendRaw("a", uint64(i), data[i])
}
return nil
})
db.TruncateTail(10)
db.TruncateHead(90)
// Ancient write should work after resetting
db.Reset()
db.ModifyAncients(func(op ethdb.AncientWriteOp) error {
for i := 0; i < 100; i++ {
op.AppendRaw("a", uint64(i), data[i])
}
return nil
})
})
}
func makeDataset(size, value int) [][]byte {
var vals [][]byte
for i := 0; i < size; i += 1 {
vals = append(vals, testrand.Bytes(value))
}
return vals
}

View file

@ -39,24 +39,38 @@ const (
freezerBatchLimit = 30000 freezerBatchLimit = 30000
) )
// chainFreezer is a wrapper of freezer with additional chain freezing feature. // chainFreezer is a wrapper of chain ancient store with additional chain freezing
// The background thread will keep moving ancient chain segments from key-value // feature. The background thread will keep moving ancient chain segments from
// database to flat files for saving space on live database. // key-value database to flat files for saving space on live database.
type chainFreezer struct { type chainFreezer struct {
*Freezer ethdb.AncientStore // Ancient store for storing cold chain segment
quit chan struct{} quit chan struct{}
wg sync.WaitGroup wg sync.WaitGroup
trigger chan chan struct{} // Manual blocking freeze trigger, test determinism trigger chan chan struct{} // Manual blocking freeze trigger, test determinism
} }
// newChainFreezer initializes the freezer for ancient chain data. // newChainFreezer initializes the freezer for ancient chain segment.
//
// - if the empty directory is given, initializes the pure in-memory
// state freezer (e.g. dev mode).
// - if non-empty directory is given, initializes the regular file-based
// state freezer.
func newChainFreezer(datadir string, namespace string, readonly bool) (*chainFreezer, error) { func newChainFreezer(datadir string, namespace string, readonly bool) (*chainFreezer, error) {
freezer, err := NewChainFreezer(datadir, namespace, readonly) var (
err error
freezer ethdb.AncientStore
)
if datadir == "" {
freezer = NewMemoryFreezer(readonly, chainFreezerNoSnappy)
} else {
freezer, err = NewFreezer(datadir, namespace, readonly, freezerTableSize, chainFreezerNoSnappy)
}
if err != nil { if err != nil {
return nil, err return nil, err
} }
return &chainFreezer{ return &chainFreezer{
Freezer: freezer, AncientStore: freezer,
quit: make(chan struct{}), quit: make(chan struct{}),
trigger: make(chan chan struct{}), trigger: make(chan chan struct{}),
}, nil }, nil
@ -70,7 +84,7 @@ func (f *chainFreezer) Close() error {
close(f.quit) close(f.quit)
} }
f.wg.Wait() f.wg.Wait()
return f.Freezer.Close() return f.AncientStore.Close()
} }
// readHeadNumber returns the number of chain head block. 0 is returned if the // readHeadNumber returns the number of chain head block. 0 is returned if the
@ -167,7 +181,7 @@ func (f *chainFreezer) freeze(db ethdb.KeyValueStore) {
log.Debug("Current full block not old enough to freeze", "err", err) log.Debug("Current full block not old enough to freeze", "err", err)
continue continue
} }
frozen := f.frozen.Load() frozen, _ := f.Ancients() // no error will occur, safe to ignore
// Short circuit if the blocks below threshold are already frozen. // Short circuit if the blocks below threshold are already frozen.
if frozen != 0 && frozen-1 >= threshold { if frozen != 0 && frozen-1 >= threshold {
@ -190,7 +204,7 @@ func (f *chainFreezer) freeze(db ethdb.KeyValueStore) {
backoff = true backoff = true
continue continue
} }
// Batch of blocks have been frozen, flush them before wiping from leveldb // Batch of blocks have been frozen, flush them before wiping from key-value store
if err := f.Sync(); err != nil { if err := f.Sync(); err != nil {
log.Crit("Failed to flush frozen tables", "err", err) log.Crit("Failed to flush frozen tables", "err", err)
} }
@ -210,7 +224,7 @@ func (f *chainFreezer) freeze(db ethdb.KeyValueStore) {
// Wipe out side chains also and track dangling side chains // Wipe out side chains also and track dangling side chains
var dangling []common.Hash var dangling []common.Hash
frozen = f.frozen.Load() // Needs reload after during freezeRange frozen, _ = f.Ancients() // Needs reload after during freezeRange
for number := first; number < frozen; number++ { for number := first; number < frozen; number++ {
// Always keep the genesis block in active database // Always keep the genesis block in active database
if number != 0 { if number != 0 {

View file

@ -34,7 +34,7 @@ func TestChainIterator(t *testing.T) {
var block *types.Block var block *types.Block
var txs []*types.Transaction var txs []*types.Transaction
to := common.BytesToAddress([]byte{0x11}) to := common.BytesToAddress([]byte{0x11})
block = types.NewBlock(&types.Header{Number: big.NewInt(int64(0))}, nil, nil, nil, newTestHasher()) // Empty genesis block block = types.NewBlock(&types.Header{Number: big.NewInt(int64(0))}, nil, nil, newTestHasher()) // Empty genesis block
WriteBlock(chainDb, block) WriteBlock(chainDb, block)
WriteCanonicalHash(chainDb, block.Hash(), block.NumberU64()) WriteCanonicalHash(chainDb, block.Hash(), block.NumberU64())
for i := uint64(1); i <= 10; i++ { for i := uint64(1); i <= 10; i++ {
@ -60,7 +60,7 @@ func TestChainIterator(t *testing.T) {
}) })
} }
txs = append(txs, tx) txs = append(txs, tx)
block = types.NewBlock(&types.Header{Number: big.NewInt(int64(i))}, []*types.Transaction{tx}, nil, nil, newTestHasher()) block = types.NewBlock(&types.Header{Number: big.NewInt(int64(i))}, &types.Body{Transactions: types.Transactions{tx}}, nil, newTestHasher())
WriteBlock(chainDb, block) WriteBlock(chainDb, block)
WriteCanonicalHash(chainDb, block.Hash(), block.NumberU64()) WriteCanonicalHash(chainDb, block.Hash(), block.NumberU64())
} }
@ -111,7 +111,7 @@ func TestIndexTransactions(t *testing.T) {
to := common.BytesToAddress([]byte{0x11}) to := common.BytesToAddress([]byte{0x11})
// Write empty genesis block // Write empty genesis block
block = types.NewBlock(&types.Header{Number: big.NewInt(int64(0))}, nil, nil, nil, newTestHasher()) block = types.NewBlock(&types.Header{Number: big.NewInt(int64(0))}, nil, nil, newTestHasher())
WriteBlock(chainDb, block) WriteBlock(chainDb, block)
WriteCanonicalHash(chainDb, block.Hash(), block.NumberU64()) WriteCanonicalHash(chainDb, block.Hash(), block.NumberU64())
@ -138,7 +138,7 @@ func TestIndexTransactions(t *testing.T) {
}) })
} }
txs = append(txs, tx) txs = append(txs, tx)
block = types.NewBlock(&types.Header{Number: big.NewInt(int64(i))}, []*types.Transaction{tx}, nil, nil, newTestHasher()) block = types.NewBlock(&types.Header{Number: big.NewInt(int64(i))}, &types.Body{Transactions: types.Transactions{tx}}, nil, newTestHasher())
WriteBlock(chainDb, block) WriteBlock(chainDb, block)
WriteCanonicalHash(chainDb, block.Hash(), block.NumberU64()) WriteCanonicalHash(chainDb, block.Hash(), block.NumberU64())
} }

View file

@ -34,11 +34,13 @@ import (
"github.com/olekukonko/tablewriter" "github.com/olekukonko/tablewriter"
) )
// freezerdb is a database wrapper that enables freezer data retrievals. // freezerdb is a database wrapper that enables ancient chain segment freezing.
type freezerdb struct { type freezerdb struct {
ancientRoot string
ethdb.KeyValueStore ethdb.KeyValueStore
ethdb.AncientStore *chainFreezer
readOnly bool
ancientRoot string
} }
// AncientDatadir returns the path of root ancient directory. // AncientDatadir returns the path of root ancient directory.
@ -50,7 +52,7 @@ func (frdb *freezerdb) AncientDatadir() (string, error) {
// the slow ancient tables. // the slow ancient tables.
func (frdb *freezerdb) Close() error { func (frdb *freezerdb) Close() error {
var errs []error var errs []error
if err := frdb.AncientStore.Close(); err != nil { if err := frdb.chainFreezer.Close(); err != nil {
errs = append(errs, err) errs = append(errs, err)
} }
if err := frdb.KeyValueStore.Close(); err != nil { if err := frdb.KeyValueStore.Close(); err != nil {
@ -66,12 +68,12 @@ func (frdb *freezerdb) Close() error {
// a freeze cycle completes, without having to sleep for a minute to trigger the // a freeze cycle completes, without having to sleep for a minute to trigger the
// automatic background run. // automatic background run.
func (frdb *freezerdb) Freeze() error { func (frdb *freezerdb) Freeze() error {
if frdb.AncientStore.(*chainFreezer).readonly { if frdb.readOnly {
return errReadOnly return errReadOnly
} }
// Trigger a freeze cycle and block until it's done // Trigger a freeze cycle and block until it's done
trigger := make(chan struct{}, 1) trigger := make(chan struct{}, 1)
frdb.AncientStore.(*chainFreezer).trigger <- trigger frdb.chainFreezer.trigger <- trigger
<-trigger <-trigger
return nil return nil
} }
@ -192,8 +194,14 @@ func resolveChainFreezerDir(ancient string) string {
// storage. The passed ancient indicates the path of root ancient directory // storage. The passed ancient indicates the path of root ancient directory
// where the chain freezer can be opened. // where the chain freezer can be opened.
func NewDatabaseWithFreezer(db ethdb.KeyValueStore, ancient string, namespace string, readonly bool) (ethdb.Database, error) { func NewDatabaseWithFreezer(db ethdb.KeyValueStore, ancient string, namespace string, readonly bool) (ethdb.Database, error) {
// Create the idle freezer instance // Create the idle freezer instance. If the given ancient directory is empty,
frdb, err := newChainFreezer(resolveChainFreezerDir(ancient), namespace, readonly) // in-memory chain freezer is used (e.g. dev mode); otherwise the regular
// file-based freezer is created.
chainFreezerDir := ancient
if chainFreezerDir != "" {
chainFreezerDir = resolveChainFreezerDir(chainFreezerDir)
}
frdb, err := newChainFreezer(chainFreezerDir, namespace, readonly)
if err != nil { if err != nil {
printChainMetadata(db) printChainMetadata(db)
return nil, err return nil, err
@ -277,7 +285,7 @@ func NewDatabaseWithFreezer(db ethdb.KeyValueStore, ancient string, namespace st
} }
} }
// Freezer is consistent with the key-value database, permit combining the two // Freezer is consistent with the key-value database, permit combining the two
if !frdb.readonly { if !readonly {
frdb.wg.Add(1) frdb.wg.Add(1)
go func() { go func() {
frdb.freeze(db) frdb.freeze(db)
@ -287,7 +295,7 @@ func NewDatabaseWithFreezer(db ethdb.KeyValueStore, ancient string, namespace st
return &freezerdb{ return &freezerdb{
ancientRoot: ancient, ancientRoot: ancient,
KeyValueStore: db, KeyValueStore: db,
AncientStore: frdb, chainFreezer: frdb,
}, nil }, nil
} }

View file

@ -62,7 +62,7 @@ const freezerTableSize = 2 * 1000 * 1000 * 1000
// reserving it for go-ethereum. This would also reduce the memory requirements // reserving it for go-ethereum. This would also reduce the memory requirements
// of Geth, and thus also GC overhead. // of Geth, and thus also GC overhead.
type Freezer struct { type Freezer struct {
frozen atomic.Uint64 // Number of blocks already frozen frozen atomic.Uint64 // Number of items already frozen
tail atomic.Uint64 // Number of the first stored item in the freezer tail atomic.Uint64 // Number of the first stored item in the freezer
// This lock synchronizes writers and the truncate operation, as well as // This lock synchronizes writers and the truncate operation, as well as
@ -76,12 +76,6 @@ type Freezer struct {
closeOnce sync.Once closeOnce sync.Once
} }
// NewChainFreezer is a small utility method around NewFreezer that sets the
// default parameters for the chain storage.
func NewChainFreezer(datadir string, namespace string, readonly bool) (*Freezer, error) {
return NewFreezer(datadir, namespace, readonly, freezerTableSize, chainFreezerNoSnappy)
}
// NewFreezer creates a freezer instance for maintaining immutable ordered // NewFreezer creates a freezer instance for maintaining immutable ordered
// data according to the given parameters. // data according to the given parameters.
// //

View file

@ -0,0 +1,428 @@
// 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 rawdb
import (
"errors"
"fmt"
"sync"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/math"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/rlp"
)
// memoryTable is used to store a list of sequential items in memory.
type memoryTable struct {
name string // Table name
items uint64 // Number of stored items in the table, including the deleted ones
offset uint64 // Number of deleted items from the table
data [][]byte // List of rlp-encoded items, sort in order
size uint64 // Total memory size occupied by the table
lock sync.RWMutex
}
// newMemoryTable initializes the memory table.
func newMemoryTable(name string) *memoryTable {
return &memoryTable{name: name}
}
// has returns an indicator whether the specified data exists.
func (t *memoryTable) has(number uint64) bool {
t.lock.RLock()
defer t.lock.RUnlock()
return number >= t.offset && number < t.items
}
// retrieve retrieves multiple items in sequence, starting from the index 'start'.
// It will return:
// - at most 'count' items,
// - if maxBytes is specified: at least 1 item (even if exceeding the maxByteSize),
// but will otherwise return as many items as fit into maxByteSize.
// - if maxBytes is not specified, 'count' items will be returned if they are present
func (t *memoryTable) retrieve(start uint64, count, maxBytes uint64) ([][]byte, error) {
t.lock.RLock()
defer t.lock.RUnlock()
var (
size uint64
batch [][]byte
)
// Ensure the start is written, not deleted from the tail, and that the
// caller actually wants something.
if t.items <= start || t.offset > start || count == 0 {
return nil, errOutOfBounds
}
// Cap the item count if the retrieval is out of bound.
if start+count > t.items {
count = t.items - start
}
for n := start; n < start+count; n++ {
index := n - t.offset
if len(batch) != 0 && maxBytes != 0 && size+uint64(len(t.data[index])) > maxBytes {
return batch, nil
}
batch = append(batch, t.data[index])
size += uint64(len(t.data[index]))
}
return batch, nil
}
// truncateHead discards any recent data above the provided threshold number.
func (t *memoryTable) truncateHead(items uint64) error {
t.lock.Lock()
defer t.lock.Unlock()
// Short circuit if nothing to delete.
if t.items <= items {
return nil
}
if items < t.offset {
return errors.New("truncation below tail")
}
t.data = t.data[:items-t.offset]
t.items = items
return nil
}
// truncateTail discards any recent data before the provided threshold number.
func (t *memoryTable) truncateTail(items uint64) error {
t.lock.Lock()
defer t.lock.Unlock()
// Short circuit if nothing to delete.
if t.offset >= items {
return nil
}
if t.items < items {
return errors.New("truncation above head")
}
t.data = t.data[items-t.offset:]
t.offset = items
return nil
}
// commit merges the given item batch into table. It's presumed that the
// batch is ordered and continuous with table.
func (t *memoryTable) commit(batch [][]byte) error {
t.lock.Lock()
defer t.lock.Unlock()
for _, item := range batch {
t.size += uint64(len(item))
}
t.data = append(t.data, batch...)
t.items += uint64(len(batch))
return nil
}
// memoryBatch is the singleton batch used for ancient write.
type memoryBatch struct {
data map[string][][]byte
next map[string]uint64
size map[string]int64
}
func newMemoryBatch() *memoryBatch {
return &memoryBatch{
data: make(map[string][][]byte),
next: make(map[string]uint64),
size: make(map[string]int64),
}
}
func (b *memoryBatch) reset(freezer *MemoryFreezer) {
b.data = make(map[string][][]byte)
b.next = make(map[string]uint64)
b.size = make(map[string]int64)
for name, table := range freezer.tables {
b.next[name] = table.items
}
}
// Append adds an RLP-encoded item.
func (b *memoryBatch) Append(kind string, number uint64, item interface{}) error {
if b.next[kind] != number {
return errOutOrderInsertion
}
blob, err := rlp.EncodeToBytes(item)
if err != nil {
return err
}
b.data[kind] = append(b.data[kind], blob)
b.next[kind]++
b.size[kind] += int64(len(blob))
return nil
}
// AppendRaw adds an item without RLP-encoding it.
func (b *memoryBatch) AppendRaw(kind string, number uint64, blob []byte) error {
if b.next[kind] != number {
return errOutOrderInsertion
}
b.data[kind] = append(b.data[kind], common.CopyBytes(blob))
b.next[kind]++
b.size[kind] += int64(len(blob))
return nil
}
// commit is called at the end of a write operation and writes all remaining
// data to tables.
func (b *memoryBatch) commit(freezer *MemoryFreezer) (items uint64, writeSize int64, err error) {
// Check that count agrees on all batches.
items = math.MaxUint64
for name, next := range b.next {
if items < math.MaxUint64 && next != items {
return 0, 0, fmt.Errorf("table %s is at item %d, want %d", name, next, items)
}
items = next
}
// Commit all table batches.
for name, batch := range b.data {
table := freezer.tables[name]
if err := table.commit(batch); err != nil {
return 0, 0, err
}
writeSize += b.size[name]
}
return items, writeSize, nil
}
// MemoryFreezer is an ephemeral ancient store. It implements the ethdb.AncientStore
// interface and can be used along with ephemeral key-value store.
type MemoryFreezer struct {
items uint64 // Number of items stored
tail uint64 // Number of the first stored item in the freezer
readonly bool // Flag if the freezer is only for reading
lock sync.RWMutex // Lock to protect fields
tables map[string]*memoryTable // Tables for storing everything
writeBatch *memoryBatch // Pre-allocated write batch
}
// NewMemoryFreezer initializes an in-memory freezer instance.
func NewMemoryFreezer(readonly bool, tableName map[string]bool) *MemoryFreezer {
tables := make(map[string]*memoryTable)
for name := range tableName {
tables[name] = newMemoryTable(name)
}
return &MemoryFreezer{
writeBatch: newMemoryBatch(),
readonly: readonly,
tables: tables,
}
}
// HasAncient returns an indicator whether the specified data exists.
func (f *MemoryFreezer) HasAncient(kind string, number uint64) (bool, error) {
f.lock.RLock()
defer f.lock.RUnlock()
if table := f.tables[kind]; table != nil {
return table.has(number), nil
}
return false, nil
}
// Ancient retrieves an ancient binary blob from the in-memory freezer.
func (f *MemoryFreezer) Ancient(kind string, number uint64) ([]byte, error) {
f.lock.RLock()
defer f.lock.RUnlock()
t := f.tables[kind]
if t == nil {
return nil, errUnknownTable
}
data, err := t.retrieve(number, 1, 0)
if err != nil {
return nil, err
}
return data[0], nil
}
// AncientRange retrieves multiple items in sequence, starting from the index 'start'.
// It will return
// - at most 'count' items,
// - if maxBytes is specified: at least 1 item (even if exceeding the maxByteSize),
// but will otherwise return as many items as fit into maxByteSize.
// - if maxBytes is not specified, 'count' items will be returned if they are present
func (f *MemoryFreezer) AncientRange(kind string, start, count, maxBytes uint64) ([][]byte, error) {
f.lock.RLock()
defer f.lock.RUnlock()
t := f.tables[kind]
if t == nil {
return nil, errUnknownTable
}
return t.retrieve(start, count, maxBytes)
}
// Ancients returns the ancient item numbers in the freezer.
func (f *MemoryFreezer) Ancients() (uint64, error) {
f.lock.RLock()
defer f.lock.RUnlock()
return f.items, nil
}
// Tail returns the number of first stored item in the freezer.
// This number can also be interpreted as the total deleted item numbers.
func (f *MemoryFreezer) Tail() (uint64, error) {
f.lock.RLock()
defer f.lock.RUnlock()
return f.tail, nil
}
// AncientSize returns the ancient size of the specified category.
func (f *MemoryFreezer) AncientSize(kind string) (uint64, error) {
f.lock.RLock()
defer f.lock.RUnlock()
if table := f.tables[kind]; table != nil {
return table.size, nil
}
return 0, errUnknownTable
}
// ReadAncients runs the given read operation while ensuring that no writes take place
// on the underlying freezer.
func (f *MemoryFreezer) ReadAncients(fn func(ethdb.AncientReaderOp) error) (err error) {
f.lock.RLock()
defer f.lock.RUnlock()
return fn(f)
}
// ModifyAncients runs the given write operation.
func (f *MemoryFreezer) ModifyAncients(fn func(ethdb.AncientWriteOp) error) (writeSize int64, err error) {
f.lock.Lock()
defer f.lock.Unlock()
if f.readonly {
return 0, errReadOnly
}
// Roll back all tables to the starting position in case of error.
defer func(old uint64) {
if err == nil {
return
}
// The write operation has failed. Go back to the previous item position.
for name, table := range f.tables {
err := table.truncateHead(old)
if err != nil {
log.Error("Freezer table roll-back failed", "table", name, "index", old, "err", err)
}
}
}(f.items)
// Modify the ancients in batch.
f.writeBatch.reset(f)
if err := fn(f.writeBatch); err != nil {
return 0, err
}
item, writeSize, err := f.writeBatch.commit(f)
if err != nil {
return 0, err
}
f.items = item
return writeSize, nil
}
// TruncateHead discards any recent data above the provided threshold number.
// It returns the previous head number.
func (f *MemoryFreezer) TruncateHead(items uint64) (uint64, error) {
f.lock.Lock()
defer f.lock.Unlock()
if f.readonly {
return 0, errReadOnly
}
old := f.items
if old <= items {
return old, nil
}
for _, table := range f.tables {
if err := table.truncateHead(items); err != nil {
return 0, err
}
}
f.items = items
return old, nil
}
// TruncateTail discards any recent data below the provided threshold number.
func (f *MemoryFreezer) TruncateTail(tail uint64) (uint64, error) {
f.lock.Lock()
defer f.lock.Unlock()
if f.readonly {
return 0, errReadOnly
}
old := f.tail
if old >= tail {
return old, nil
}
for _, table := range f.tables {
if err := table.truncateTail(tail); err != nil {
return 0, err
}
}
f.tail = tail
return old, nil
}
// Sync flushes all data tables to disk.
func (f *MemoryFreezer) Sync() error {
return nil
}
// MigrateTable processes and migrates entries of a given table to a new format.
// The second argument is a function that takes a raw entry and returns it
// in the newest format.
func (f *MemoryFreezer) MigrateTable(string, func([]byte) ([]byte, error)) error {
return errors.New("not implemented")
}
// Close releases all the sources held by the memory freezer. It will panic if
// any following invocation is made to a closed freezer.
func (f *MemoryFreezer) Close() error {
f.lock.Lock()
defer f.lock.Unlock()
f.tables = nil
f.writeBatch = nil
return nil
}
// Reset drops all the data cached in the memory freezer and reset itself
// back to default state.
func (f *MemoryFreezer) Reset() error {
f.lock.Lock()
defer f.lock.Unlock()
tables := make(map[string]*memoryTable)
for name := range f.tables {
tables[name] = newMemoryTable(name)
}
f.tables = tables
f.items, f.tail = 0, 0
return nil
}

View file

@ -0,0 +1,41 @@
// 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 rawdb
import (
"testing"
"github.com/ethereum/go-ethereum/core/rawdb/ancienttest"
"github.com/ethereum/go-ethereum/ethdb"
)
func TestMemoryFreezer(t *testing.T) {
ancienttest.TestAncientSuite(t, func(kinds []string) ethdb.AncientStore {
tables := make(map[string]bool)
for _, kind := range kinds {
tables[kind] = true
}
return NewMemoryFreezer(false, tables)
})
ancienttest.TestResettableAncientSuite(t, func(kinds []string) ethdb.ResettableAncientStore {
tables := make(map[string]bool)
for _, kind := range kinds {
tables[kind] = true
}
return NewMemoryFreezer(false, tables)
})
}

View file

@ -30,16 +30,16 @@ const tmpSuffix = ".tmp"
// freezerOpenFunc is the function used to open/create a freezer. // freezerOpenFunc is the function used to open/create a freezer.
type freezerOpenFunc = func() (*Freezer, error) type freezerOpenFunc = func() (*Freezer, error)
// ResettableFreezer is a wrapper of the freezer which makes the // resettableFreezer is a wrapper of the freezer which makes the
// freezer resettable. // freezer resettable.
type ResettableFreezer struct { type resettableFreezer struct {
freezer *Freezer freezer *Freezer
opener freezerOpenFunc opener freezerOpenFunc
datadir string datadir string
lock sync.RWMutex lock sync.RWMutex
} }
// NewResettableFreezer creates a resettable freezer, note freezer is // newResettableFreezer creates a resettable freezer, note freezer is
// only resettable if the passed file directory is exclusively occupied // only resettable if the passed file directory is exclusively occupied
// by the freezer. And also the user-configurable ancient root directory // by the freezer. And also the user-configurable ancient root directory
// is **not** supported for reset since it might be a mount and rename // is **not** supported for reset since it might be a mount and rename
@ -48,7 +48,7 @@ type ResettableFreezer struct {
// //
// The reset function will delete directory atomically and re-create the // The reset function will delete directory atomically and re-create the
// freezer from scratch. // freezer from scratch.
func NewResettableFreezer(datadir string, namespace string, readonly bool, maxTableSize uint32, tables map[string]bool) (*ResettableFreezer, error) { func newResettableFreezer(datadir string, namespace string, readonly bool, maxTableSize uint32, tables map[string]bool) (*resettableFreezer, error) {
if err := cleanup(datadir); err != nil { if err := cleanup(datadir); err != nil {
return nil, err return nil, err
} }
@ -59,7 +59,7 @@ func NewResettableFreezer(datadir string, namespace string, readonly bool, maxTa
if err != nil { if err != nil {
return nil, err return nil, err
} }
return &ResettableFreezer{ return &resettableFreezer{
freezer: freezer, freezer: freezer,
opener: opener, opener: opener,
datadir: datadir, datadir: datadir,
@ -70,7 +70,7 @@ func NewResettableFreezer(datadir string, namespace string, readonly bool, maxTa
// recreate the freezer from scratch. The atomicity of directory deletion // recreate the freezer from scratch. The atomicity of directory deletion
// is guaranteed by the rename operation, the leftover directory will be // is guaranteed by the rename operation, the leftover directory will be
// cleaned up in next startup in case crash happens after rename. // cleaned up in next startup in case crash happens after rename.
func (f *ResettableFreezer) Reset() error { func (f *resettableFreezer) Reset() error {
f.lock.Lock() f.lock.Lock()
defer f.lock.Unlock() defer f.lock.Unlock()
@ -93,7 +93,7 @@ func (f *ResettableFreezer) Reset() error {
} }
// Close terminates the chain freezer, unmapping all the data files. // Close terminates the chain freezer, unmapping all the data files.
func (f *ResettableFreezer) Close() error { func (f *resettableFreezer) Close() error {
f.lock.RLock() f.lock.RLock()
defer f.lock.RUnlock() defer f.lock.RUnlock()
@ -102,7 +102,7 @@ func (f *ResettableFreezer) Close() error {
// HasAncient returns an indicator whether the specified ancient data exists // HasAncient returns an indicator whether the specified ancient data exists
// in the freezer // in the freezer
func (f *ResettableFreezer) HasAncient(kind string, number uint64) (bool, error) { func (f *resettableFreezer) HasAncient(kind string, number uint64) (bool, error) {
f.lock.RLock() f.lock.RLock()
defer f.lock.RUnlock() defer f.lock.RUnlock()
@ -110,7 +110,7 @@ func (f *ResettableFreezer) HasAncient(kind string, number uint64) (bool, error)
} }
// Ancient retrieves an ancient binary blob from the append-only immutable files. // Ancient retrieves an ancient binary blob from the append-only immutable files.
func (f *ResettableFreezer) Ancient(kind string, number uint64) ([]byte, error) { func (f *resettableFreezer) Ancient(kind string, number uint64) ([]byte, error) {
f.lock.RLock() f.lock.RLock()
defer f.lock.RUnlock() defer f.lock.RUnlock()
@ -123,7 +123,7 @@ func (f *ResettableFreezer) Ancient(kind string, number uint64) ([]byte, error)
// - if maxBytes is specified: at least 1 item (even if exceeding the maxByteSize), // - if maxBytes is specified: at least 1 item (even if exceeding the maxByteSize),
// but will otherwise return as many items as fit into maxByteSize. // but will otherwise return as many items as fit into maxByteSize.
// - if maxBytes is not specified, 'count' items will be returned if they are present. // - if maxBytes is not specified, 'count' items will be returned if they are present.
func (f *ResettableFreezer) AncientRange(kind string, start, count, maxBytes uint64) ([][]byte, error) { func (f *resettableFreezer) AncientRange(kind string, start, count, maxBytes uint64) ([][]byte, error) {
f.lock.RLock() f.lock.RLock()
defer f.lock.RUnlock() defer f.lock.RUnlock()
@ -131,7 +131,7 @@ func (f *ResettableFreezer) AncientRange(kind string, start, count, maxBytes uin
} }
// Ancients returns the length of the frozen items. // Ancients returns the length of the frozen items.
func (f *ResettableFreezer) Ancients() (uint64, error) { func (f *resettableFreezer) Ancients() (uint64, error) {
f.lock.RLock() f.lock.RLock()
defer f.lock.RUnlock() defer f.lock.RUnlock()
@ -139,7 +139,7 @@ func (f *ResettableFreezer) Ancients() (uint64, error) {
} }
// Tail returns the number of first stored item in the freezer. // Tail returns the number of first stored item in the freezer.
func (f *ResettableFreezer) Tail() (uint64, error) { func (f *resettableFreezer) Tail() (uint64, error) {
f.lock.RLock() f.lock.RLock()
defer f.lock.RUnlock() defer f.lock.RUnlock()
@ -147,7 +147,7 @@ func (f *ResettableFreezer) Tail() (uint64, error) {
} }
// AncientSize returns the ancient size of the specified category. // AncientSize returns the ancient size of the specified category.
func (f *ResettableFreezer) AncientSize(kind string) (uint64, error) { func (f *resettableFreezer) AncientSize(kind string) (uint64, error) {
f.lock.RLock() f.lock.RLock()
defer f.lock.RUnlock() defer f.lock.RUnlock()
@ -156,7 +156,7 @@ func (f *ResettableFreezer) AncientSize(kind string) (uint64, error) {
// ReadAncients runs the given read operation while ensuring that no writes take place // ReadAncients runs the given read operation while ensuring that no writes take place
// on the underlying freezer. // on the underlying freezer.
func (f *ResettableFreezer) ReadAncients(fn func(ethdb.AncientReaderOp) error) (err error) { func (f *resettableFreezer) ReadAncients(fn func(ethdb.AncientReaderOp) error) (err error) {
f.lock.RLock() f.lock.RLock()
defer f.lock.RUnlock() defer f.lock.RUnlock()
@ -164,7 +164,7 @@ func (f *ResettableFreezer) ReadAncients(fn func(ethdb.AncientReaderOp) error) (
} }
// ModifyAncients runs the given write operation. // ModifyAncients runs the given write operation.
func (f *ResettableFreezer) ModifyAncients(fn func(ethdb.AncientWriteOp) error) (writeSize int64, err error) { func (f *resettableFreezer) ModifyAncients(fn func(ethdb.AncientWriteOp) error) (writeSize int64, err error) {
f.lock.RLock() f.lock.RLock()
defer f.lock.RUnlock() defer f.lock.RUnlock()
@ -173,7 +173,7 @@ func (f *ResettableFreezer) ModifyAncients(fn func(ethdb.AncientWriteOp) error)
// TruncateHead discards any recent data above the provided threshold number. // TruncateHead discards any recent data above the provided threshold number.
// It returns the previous head number. // It returns the previous head number.
func (f *ResettableFreezer) TruncateHead(items uint64) (uint64, error) { func (f *resettableFreezer) TruncateHead(items uint64) (uint64, error) {
f.lock.RLock() f.lock.RLock()
defer f.lock.RUnlock() defer f.lock.RUnlock()
@ -182,7 +182,7 @@ func (f *ResettableFreezer) TruncateHead(items uint64) (uint64, error) {
// TruncateTail discards any recent data below the provided threshold number. // TruncateTail discards any recent data below the provided threshold number.
// It returns the previous value // It returns the previous value
func (f *ResettableFreezer) TruncateTail(tail uint64) (uint64, error) { func (f *resettableFreezer) TruncateTail(tail uint64) (uint64, error) {
f.lock.RLock() f.lock.RLock()
defer f.lock.RUnlock() defer f.lock.RUnlock()
@ -190,7 +190,7 @@ func (f *ResettableFreezer) TruncateTail(tail uint64) (uint64, error) {
} }
// Sync flushes all data tables to disk. // Sync flushes all data tables to disk.
func (f *ResettableFreezer) Sync() error { func (f *resettableFreezer) Sync() error {
f.lock.RLock() f.lock.RLock()
defer f.lock.RUnlock() defer f.lock.RUnlock()
@ -199,7 +199,7 @@ func (f *ResettableFreezer) Sync() error {
// MigrateTable processes the entries in a given table in sequence // MigrateTable processes the entries in a given table in sequence
// converting them to a new format if they're of an old format. // converting them to a new format if they're of an old format.
func (f *ResettableFreezer) MigrateTable(kind string, convert convertLegacyFn) error { func (f *resettableFreezer) MigrateTable(kind string, convert convertLegacyFn) error {
f.lock.RLock() f.lock.RLock()
defer f.lock.RUnlock() defer f.lock.RUnlock()

View file

@ -33,7 +33,7 @@ func TestResetFreezer(t *testing.T) {
{1, bytes.Repeat([]byte{1}, 2048)}, {1, bytes.Repeat([]byte{1}, 2048)},
{2, bytes.Repeat([]byte{2}, 2048)}, {2, bytes.Repeat([]byte{2}, 2048)},
} }
f, _ := NewResettableFreezer(t.TempDir(), "", false, 2048, freezerTestTableDef) f, _ := newResettableFreezer(t.TempDir(), "", false, 2048, freezerTestTableDef)
defer f.Close() defer f.Close()
f.ModifyAncients(func(op ethdb.AncientWriteOp) error { f.ModifyAncients(func(op ethdb.AncientWriteOp) error {
@ -87,7 +87,7 @@ func TestFreezerCleanup(t *testing.T) {
{2, bytes.Repeat([]byte{2}, 2048)}, {2, bytes.Repeat([]byte{2}, 2048)},
} }
datadir := t.TempDir() datadir := t.TempDir()
f, _ := NewResettableFreezer(datadir, "", false, 2048, freezerTestTableDef) f, _ := newResettableFreezer(datadir, "", false, 2048, freezerTestTableDef)
f.ModifyAncients(func(op ethdb.AncientWriteOp) error { f.ModifyAncients(func(op ethdb.AncientWriteOp) error {
for _, item := range items { for _, item := range items {
op.AppendRaw("test", item.id, item.blob) op.AppendRaw("test", item.id, item.blob)
@ -98,7 +98,7 @@ func TestFreezerCleanup(t *testing.T) {
os.Rename(datadir, tmpName(datadir)) os.Rename(datadir, tmpName(datadir))
// Open the freezer again, trigger cleanup operation // Open the freezer again, trigger cleanup operation
f, _ = NewResettableFreezer(datadir, "", false, 2048, freezerTestTableDef) f, _ = newResettableFreezer(datadir, "", false, 2048, freezerTestTableDef)
f.Close() f.Close()
if _, err := os.Lstat(tmpName(datadir)); !os.IsNotExist(err) { if _, err := os.Lstat(tmpName(datadir)); !os.IsNotExist(err) {

View file

@ -27,6 +27,7 @@ import (
"sync" "sync"
"testing" "testing"
"github.com/ethereum/go-ethereum/core/rawdb/ancienttest"
"github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rlp"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
@ -480,3 +481,22 @@ func TestFreezerCloseSync(t *testing.T) {
t.Fatalf("want %v, have %v", have, want) t.Fatalf("want %v, have %v", have, want)
} }
} }
func TestFreezerSuite(t *testing.T) {
ancienttest.TestAncientSuite(t, func(kinds []string) ethdb.AncientStore {
tables := make(map[string]bool)
for _, kind := range kinds {
tables[kind] = true
}
f, _ := newFreezerForTesting(t, tables)
return f
})
ancienttest.TestResettableAncientSuite(t, func(kinds []string) ethdb.ResettableAncientStore {
tables := make(map[string]bool)
for _, kind := range kinds {
tables[kind] = true
}
f, _ := newResettableFreezer(t.TempDir(), "", false, 2048, tables)
return f
})
}

View file

@ -403,6 +403,9 @@ func (s *stateObject) updateRoot() {
// commit obtains a set of dirty storage trie nodes and updates the account data. // commit obtains a set of dirty storage trie nodes and updates the account data.
// The returned set can be nil if nothing to commit. This function assumes all // The returned set can be nil if nothing to commit. This function assumes all
// storage mutations have already been flushed into trie by updateRoot. // storage mutations have already been flushed into trie by updateRoot.
//
// Note, commit may run concurrently across all the state objects. Do not assume
// thread-safe access to the statedb.
func (s *stateObject) commit() (*trienode.NodeSet, error) { func (s *stateObject) commit() (*trienode.NodeSet, error) {
// Short circuit if trie is not even loaded, don't bother with committing anything // Short circuit if trie is not even loaded, don't bother with committing anything
if s.trie == nil { if s.trie == nil {

View file

@ -23,6 +23,7 @@ import (
"math/big" "math/big"
"slices" "slices"
"sort" "sort"
"sync"
"time" "time"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
@ -37,6 +38,7 @@ import (
"github.com/ethereum/go-ethereum/trie/trienode" "github.com/ethereum/go-ethereum/trie/trienode"
"github.com/ethereum/go-ethereum/trie/triestate" "github.com/ethereum/go-ethereum/trie/triestate"
"github.com/holiman/uint256" "github.com/holiman/uint256"
"golang.org/x/sync/errgroup"
) )
type revision struct { type revision struct {
@ -1146,66 +1148,108 @@ func (s *StateDB) Commit(block uint64, deleteEmptyObjects bool) (common.Hash, er
storageTrieNodesUpdated int storageTrieNodesUpdated int
storageTrieNodesDeleted int storageTrieNodesDeleted int
nodes = trienode.NewMergedNodeSet() nodes = trienode.NewMergedNodeSet()
codeWriter = s.db.DiskDB().NewBatch()
) )
// Handle all state deletions first // Handle all state deletions first
if err := s.handleDestruction(nodes); err != nil { if err := s.handleDestruction(nodes); err != nil {
return common.Hash{}, err return common.Hash{}, err
} }
// Handle all state updates afterwards // Handle all state updates afterwards, concurrently to one another to shave
// off some milliseconds from the commit operation. Also accumulate the code
// writes to run in parallel with the computations.
start := time.Now() start := time.Now()
var (
code = s.db.DiskDB().NewBatch()
lock sync.Mutex
root common.Hash
workers errgroup.Group
)
// Schedule the account trie first since that will be the biggest, so give
// it the most time to crunch.
//
// TODO(karalabe): This account trie commit is *very* heavy. 5-6ms at chain
// heads, which seems excessive given that it doesn't do hashing, it just
// shuffles some data. For comparison, the *hashing* at chain head is 2-3ms.
// We need to investigate what's happening as it seems something's wonky.
// Obviously it's not an end of the world issue, just something the original
// code didn't anticipate for.
workers.Go(func() error {
// Write the account trie changes, measuring the amount of wasted time
newroot, set, err := s.trie.Commit(true)
if err != nil {
return err
}
root = newroot
// Merge the dirty nodes of account trie into global set
lock.Lock()
defer lock.Unlock()
if set != nil {
if err = nodes.Merge(set); err != nil {
return err
}
accountTrieNodesUpdated, accountTrieNodesDeleted = set.Size()
}
s.AccountCommits = time.Since(start)
return nil
})
// Schedule each of the storage tries that need to be updated, so they can
// run concurrently to one another.
//
// TODO(karalabe): Experimentally, the account commit takes approximately the
// same time as all the storage commits combined, so we could maybe only have
// 2 threads in total. But that kind of depends on the account commit being
// more expensive than it should be, so let's fix that and revisit this todo.
for addr, op := range s.mutations { for addr, op := range s.mutations {
if op.isDelete() { if op.isDelete() {
continue continue
} }
obj := s.stateObjects[addr]
// Write any contract code associated with the state object // Write any contract code associated with the state object
obj := s.stateObjects[addr]
if obj.code != nil && obj.dirtyCode { if obj.code != nil && obj.dirtyCode {
rawdb.WriteCode(codeWriter, common.BytesToHash(obj.CodeHash()), obj.code) rawdb.WriteCode(code, common.BytesToHash(obj.CodeHash()), obj.code)
obj.dirtyCode = false obj.dirtyCode = false
} }
// Run the storage updates concurrently to one another
workers.Go(func() error {
// Write any storage changes in the state object to its storage trie // Write any storage changes in the state object to its storage trie
set, err := obj.commit() set, err := obj.commit()
if err != nil { if err != nil {
return common.Hash{}, err return err
} }
// Merge the dirty nodes of storage trie into global set. It is possible // Merge the dirty nodes of storage trie into global set. It is possible
// that the account was destructed and then resurrected in the same block. // that the account was destructed and then resurrected in the same block.
// In this case, the node set is shared by both accounts. // In this case, the node set is shared by both accounts.
lock.Lock()
defer lock.Unlock()
if set != nil { if set != nil {
if err := nodes.Merge(set); err != nil { if err = nodes.Merge(set); err != nil {
return common.Hash{}, err return err
} }
updates, deleted := set.Size() updates, deleted := set.Size()
storageTrieNodesUpdated += updates storageTrieNodesUpdated += updates
storageTrieNodesDeleted += deleted storageTrieNodesDeleted += deleted
} }
s.StorageCommits = time.Since(start) // overwrite with the longest storage commit runtime
return nil
})
} }
s.StorageCommits += time.Since(start) // Schedule the code commits to run concurrently too. This shouldn't really
// take much since we don't often commit code, but since it's disk access,
if codeWriter.ValueSize() > 0 { // it's always yolo.
if err := codeWriter.Write(); err != nil { workers.Go(func() error {
if code.ValueSize() > 0 {
if err := code.Write(); err != nil {
log.Crit("Failed to commit dirty codes", "error", err) log.Crit("Failed to commit dirty codes", "error", err)
} }
} }
// Write the account trie changes, measuring the amount of wasted time return nil
start = time.Now() })
// Wait for everything to finish and update the metrics
root, set, err := s.trie.Commit(true) if err := workers.Wait(); err != nil {
if err != nil {
return common.Hash{}, err return common.Hash{}, err
} }
// Merge the dirty nodes of account trie into global set
if set != nil {
if err := nodes.Merge(set); err != nil {
return common.Hash{}, err
}
accountTrieNodesUpdated, accountTrieNodesDeleted = set.Size()
}
// Report the commit metrics
s.AccountCommits += time.Since(start)
accountUpdatedMeter.Mark(int64(s.AccountUpdated)) accountUpdatedMeter.Mark(int64(s.AccountUpdated))
storageUpdatedMeter.Mark(int64(s.StorageUpdated)) storageUpdatedMeter.Mark(int64(s.StorageUpdated))
accountDeletedMeter.Mark(int64(s.AccountDeleted)) accountDeletedMeter.Mark(int64(s.AccountDeleted))

View file

@ -417,10 +417,11 @@ func GenerateBadBlock(parent *types.Block, engine consensus.Engine, txs types.Tr
header.ParentBeaconRoot = &beaconRoot header.ParentBeaconRoot = &beaconRoot
} }
// Assemble and return the final block for sealing // Assemble and return the final block for sealing
body := &types.Body{Transactions: txs}
if config.IsShanghai(header.Number, header.Time) { if config.IsShanghai(header.Number, header.Time) {
return types.NewBlockWithWithdrawals(header, txs, nil, receipts, []*types.Withdrawal{}, trie.NewStackTrie(nil)) body.Withdrawals = []*types.Withdrawal{}
} }
return types.NewBlock(header, txs, nil, receipts, trie.NewStackTrie(nil)) return types.NewBlock(header, body, receipts, trie.NewStackTrie(nil))
} }
var ( var (

View file

@ -87,7 +87,7 @@ func (bc *testBlockChain) CurrentBlock() *types.Header {
} }
func (bc *testBlockChain) GetBlock(hash common.Hash, number uint64) *types.Block { func (bc *testBlockChain) GetBlock(hash common.Hash, number uint64) *types.Block {
return types.NewBlock(bc.CurrentBlock(), nil, nil, nil, trie.NewStackTrie(nil)) return types.NewBlock(bc.CurrentBlock(), nil, nil, trie.NewStackTrie(nil))
} }
func (bc *testBlockChain) StateAt(common.Hash) (*state.StateDB, error) { func (bc *testBlockChain) StateAt(common.Hash) (*state.StateDB, error) {

View file

@ -23,6 +23,7 @@ import (
"io" "io"
"math/big" "math/big"
"reflect" "reflect"
"slices"
"sync/atomic" "sync/atomic"
"time" "time"
@ -217,13 +218,19 @@ type extblock struct {
// NewBlock creates a new block. The input data is copied, changes to header and to the // NewBlock creates a new block. The input data is copied, changes to header and to the
// field values will not affect the block. // field values will not affect the block.
// //
// The values of TxHash, UncleHash, ReceiptHash and Bloom in header // The body elements and the receipts are used to recompute and overwrite the
// are ignored and set to values derived from the given txs, uncles // relevant portions of the header.
// and receipts. func NewBlock(header *Header, body *Body, receipts []*Receipt, hasher TrieHasher) *Block {
func NewBlock(header *Header, txs []*Transaction, uncles []*Header, receipts []*Receipt, hasher TrieHasher) *Block { if body == nil {
b := &Block{header: CopyHeader(header)} body = &Body{}
}
var (
b = NewBlockWithHeader(header)
txs = body.Transactions
uncles = body.Uncles
withdrawals = body.Withdrawals
)
// TODO: panic if len(txs) != len(receipts)
if len(txs) == 0 { if len(txs) == 0 {
b.header.TxHash = EmptyTxsHash b.header.TxHash = EmptyTxsHash
} else { } else {
@ -249,27 +256,18 @@ func NewBlock(header *Header, txs []*Transaction, uncles []*Header, receipts []*
} }
} }
return b
}
// NewBlockWithWithdrawals creates a new block with withdrawals. The input data is copied,
// changes to header and to the field values will not affect the block.
//
// The values of TxHash, UncleHash, ReceiptHash and Bloom in header are ignored and set to
// values derived from the given txs, uncles and receipts.
func NewBlockWithWithdrawals(header *Header, txs []*Transaction, uncles []*Header, receipts []*Receipt, withdrawals []*Withdrawal, hasher TrieHasher) *Block {
b := NewBlock(header, txs, uncles, receipts, hasher)
if withdrawals == nil { if withdrawals == nil {
b.header.WithdrawalsHash = nil b.header.WithdrawalsHash = nil
} else if len(withdrawals) == 0 { } else if len(withdrawals) == 0 {
b.header.WithdrawalsHash = &EmptyWithdrawalsHash b.header.WithdrawalsHash = &EmptyWithdrawalsHash
b.withdrawals = Withdrawals{}
} else { } else {
h := DeriveSha(Withdrawals(withdrawals), hasher) hash := DeriveSha(Withdrawals(withdrawals), hasher)
b.header.WithdrawalsHash = &h b.header.WithdrawalsHash = &hash
b.withdrawals = slices.Clone(withdrawals)
} }
return b.WithWithdrawals(withdrawals) return b
} }
// CopyHeader creates a deep copy of a block header. // CopyHeader creates a deep copy of a block header.
@ -453,31 +451,17 @@ func (b *Block) WithSeal(header *Header) *Block {
} }
} }
// WithBody returns a copy of the block with the given transaction and uncle contents. // WithBody returns a new block with the original header and a deep copy of the
func (b *Block) WithBody(transactions []*Transaction, uncles []*Header) *Block { // provided body.
func (b *Block) WithBody(body Body) *Block {
block := &Block{ block := &Block{
header: b.header, header: b.header,
transactions: make([]*Transaction, len(transactions)), transactions: slices.Clone(body.Transactions),
uncles: make([]*Header, len(uncles)), uncles: make([]*Header, len(body.Uncles)),
withdrawals: b.withdrawals, withdrawals: slices.Clone(body.Withdrawals),
} }
copy(block.transactions, transactions) for i := range body.Uncles {
for i := range uncles { block.uncles[i] = CopyHeader(body.Uncles[i])
block.uncles[i] = CopyHeader(uncles[i])
}
return block
}
// WithWithdrawals returns a copy of the block containing the given withdrawals.
func (b *Block) WithWithdrawals(withdrawals []*Withdrawal) *Block {
block := &Block{
header: b.header,
transactions: b.transactions,
uncles: b.uncles,
}
if withdrawals != nil {
block.withdrawals = make([]*Withdrawal, len(withdrawals))
copy(block.withdrawals, withdrawals)
} }
return block return block
} }

View file

@ -254,7 +254,7 @@ func makeBenchBlock() *Block {
Extra: []byte("benchmark uncle"), Extra: []byte("benchmark uncle"),
} }
} }
return NewBlock(header, txs, uncles, receipts, blocktest.NewHasher()) return NewBlock(header, &Body{Transactions: txs, Uncles: uncles}, receipts, blocktest.NewHasher())
} }
func TestRlpDecodeParentHash(t *testing.T) { func TestRlpDecodeParentHash(t *testing.T) {

View file

@ -705,6 +705,8 @@ func (c *bls12381G1Add) Run(input []byte) ([]byte, error) {
return nil, err return nil, err
} }
// No need to check the subgroup here, as specified by EIP-2537
// Compute r = p_0 + p_1 // Compute r = p_0 + p_1
p0.Add(p0, p1) p0.Add(p0, p1)
@ -734,6 +736,11 @@ func (c *bls12381G1Mul) Run(input []byte) ([]byte, error) {
if p0, err = decodePointG1(input[:128]); err != nil { if p0, err = decodePointG1(input[:128]); err != nil {
return nil, err return nil, err
} }
// 'point is on curve' check already done,
// Here we need to apply subgroup checks.
if !p0.IsInSubGroup() {
return nil, errBLS12381G1PointSubgroup
}
// Decode scalar value // Decode scalar value
e := new(big.Int).SetBytes(input[128:]) e := new(big.Int).SetBytes(input[128:])
@ -787,6 +794,11 @@ func (c *bls12381G1MultiExp) Run(input []byte) ([]byte, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
// 'point is on curve' check already done,
// Here we need to apply subgroup checks.
if !p.IsInSubGroup() {
return nil, errBLS12381G1PointSubgroup
}
points[i] = *p points[i] = *p
// Decode scalar value // Decode scalar value
scalars[i] = *new(fr.Element).SetBytes(input[t1:t2]) scalars[i] = *new(fr.Element).SetBytes(input[t1:t2])
@ -827,6 +839,8 @@ func (c *bls12381G2Add) Run(input []byte) ([]byte, error) {
return nil, err return nil, err
} }
// No need to check the subgroup here, as specified by EIP-2537
// Compute r = p_0 + p_1 // Compute r = p_0 + p_1
r := new(bls12381.G2Affine) r := new(bls12381.G2Affine)
r.Add(p0, p1) r.Add(p0, p1)
@ -857,6 +871,11 @@ func (c *bls12381G2Mul) Run(input []byte) ([]byte, error) {
if p0, err = decodePointG2(input[:256]); err != nil { if p0, err = decodePointG2(input[:256]); err != nil {
return nil, err return nil, err
} }
// 'point is on curve' check already done,
// Here we need to apply subgroup checks.
if !p0.IsInSubGroup() {
return nil, errBLS12381G2PointSubgroup
}
// Decode scalar value // Decode scalar value
e := new(big.Int).SetBytes(input[256:]) e := new(big.Int).SetBytes(input[256:])
@ -910,6 +929,11 @@ func (c *bls12381G2MultiExp) Run(input []byte) ([]byte, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
// 'point is on curve' check already done,
// Here we need to apply subgroup checks.
if !p.IsInSubGroup() {
return nil, errBLS12381G2PointSubgroup
}
points[i] = *p points[i] = *p
// Decode scalar value // Decode scalar value
scalars[i] = *new(fr.Element).SetBytes(input[t1:t2]) scalars[i] = *new(fr.Element).SetBytes(input[t1:t2])
@ -1099,9 +1123,6 @@ func (c *bls12381MapG1) Run(input []byte) ([]byte, error) {
// Compute mapping // Compute mapping
r := bls12381.MapToG1(fe) r := bls12381.MapToG1(fe)
if err != nil {
return nil, err
}
// Encode the G1 point to 128 bytes // Encode the G1 point to 128 bytes
return encodePointG1(&r), nil return encodePointG1(&r), nil
@ -1135,9 +1156,6 @@ func (c *bls12381MapG2) Run(input []byte) ([]byte, error) {
// Compute mapping // Compute mapping
r := bls12381.MapToG2(bls12381.E2{A0: c0, A1: c1}) r := bls12381.MapToG2(bls12381.E2{A0: c0, A1: c1})
if err != nil {
return nil, err
}
// Encode the G2 point to 256 bytes // Encode the G2 point to 256 bytes
return encodePointG2(&r), nil return encodePointG2(&r), nil

View file

@ -173,11 +173,7 @@ func opByte(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byt
func opAddmod(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { func opAddmod(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
x, y, z := scope.Stack.pop(), scope.Stack.pop(), scope.Stack.peek() x, y, z := scope.Stack.pop(), scope.Stack.pop(), scope.Stack.peek()
if z.IsZero() {
z.Clear()
} else {
z.AddMod(&x, &y, z) z.AddMod(&x, &y, z)
}
return nil, nil return nil, nil
} }

View file

@ -779,7 +779,7 @@ func setBlockhash(data *engine.ExecutableData) *engine.ExecutableData {
Extra: data.ExtraData, Extra: data.ExtraData,
MixDigest: data.Random, MixDigest: data.Random,
} }
block := types.NewBlockWithHeader(header).WithBody(txs, nil /* uncles */) block := types.NewBlockWithHeader(header).WithBody(types.Body{Transactions: txs})
data.BlockHash = block.Hash() data.BlockHash = block.Hash()
return data return data
} }
@ -935,7 +935,7 @@ func TestNewPayloadOnInvalidTerminalBlock(t *testing.T) {
Extra: data.ExtraData, Extra: data.ExtraData,
MixDigest: data.Random, MixDigest: data.Random,
} }
block := types.NewBlockWithHeader(header).WithBody(txs, nil /* uncles */) block := types.NewBlockWithHeader(header).WithBody(types.Body{Transactions: txs})
data.BlockHash = block.Hash() data.BlockHash = block.Hash()
// Send the new payload // Send the new payload
resp2, err := api.NewPayloadV1(data) resp2, err := api.NewPayloadV1(data)
@ -1554,7 +1554,7 @@ func TestBlockToPayloadWithBlobs(t *testing.T) {
}, },
} }
block := types.NewBlock(&header, txs, nil, nil, trie.NewStackTrie(nil)) block := types.NewBlock(&header, &types.Body{Transactions: txs}, nil, trie.NewStackTrie(nil))
envelope := engine.BlockToExecutableData(block, nil, sidecars) envelope := engine.BlockToExecutableData(block, nil, sidecars)
var want int var want int
for _, tx := range txs { for _, tx := range txs {

View file

@ -106,7 +106,7 @@ func (b *beaconBackfiller) resume() {
}() }()
// If the downloader fails, report an error as in beacon chain mode there // If the downloader fails, report an error as in beacon chain mode there
// should be no errors as long as the chain we're syncing to is valid. // should be no errors as long as the chain we're syncing to is valid.
if err := b.downloader.synchronise("", common.Hash{}, nil, nil, mode, true, b.started); err != nil { if err := b.downloader.synchronise(mode, b.started); err != nil {
log.Error("Beacon backfilling failed", "err", err) log.Error("Beacon backfilling failed", "err", err)
return return
} }
@ -268,9 +268,9 @@ func (d *Downloader) findBeaconAncestor() (uint64, error) {
return start, nil return start, nil
} }
// fetchBeaconHeaders feeds skeleton headers to the downloader queue for scheduling // fetchHeaders feeds skeleton headers to the downloader queue for scheduling
// until sync errors or is finished. // until sync errors or is finished.
func (d *Downloader) fetchBeaconHeaders(from uint64) error { func (d *Downloader) fetchHeaders(from uint64) error {
var head *types.Header var head *types.Header
_, tail, _, err := d.skeleton.Bounds() _, tail, _, err := d.skeleton.Bounds()
if err != nil { if err != nil {

View file

@ -41,16 +41,13 @@ import (
var ( var (
MaxBlockFetch = 128 // Number of blocks to be fetched per retrieval request MaxBlockFetch = 128 // Number of blocks to be fetched per retrieval request
MaxHeaderFetch = 192 // Number of block headers to be fetched per retrieval request MaxHeaderFetch = 192 // Number of block headers to be fetched per retrieval request
MaxSkeletonSize = 128 // Number of header fetches needed for a skeleton assembly
MaxReceiptFetch = 256 // Number of transaction receipts to allow fetching per request MaxReceiptFetch = 256 // Number of transaction receipts to allow fetching per request
maxQueuedHeaders = 32 * 1024 // [eth/62] Maximum number of headers to queue for import (DOS protection) maxQueuedHeaders = 32 * 1024 // [eth/62] Maximum number of headers to queue for import (DOS protection)
maxHeadersProcess = 2048 // Number of header download results to import at once into the chain maxHeadersProcess = 2048 // Number of header download results to import at once into the chain
maxResultsProcess = 2048 // Number of content download results to import at once into the chain maxResultsProcess = 2048 // Number of content download results to import at once into the chain
fullMaxForkAncestry uint64 = params.FullImmutabilityThreshold // Maximum chain reorganisation (locally redeclared so tests can reduce it) fullMaxForkAncestry uint64 = params.FullImmutabilityThreshold // Maximum chain reorganisation (locally redeclared so tests can reduce it)
lightMaxForkAncestry uint64 = params.LightImmutabilityThreshold // Maximum chain reorganisation (locally redeclared so tests can reduce it)
reorgProtThreshold = 48 // Threshold number of recent blocks to disable mini reorg protection
reorgProtHeaderDelay = 2 // Number of headers to delay delivering to cover mini reorgs reorgProtHeaderDelay = 2 // Number of headers to delay delivering to cover mini reorgs
fsHeaderSafetyNet = 2048 // Number of headers to discard in case a chain violation is detected fsHeaderSafetyNet = 2048 // Number of headers to discard in case a chain violation is detected
@ -60,23 +57,15 @@ var (
var ( var (
errBusy = errors.New("busy") errBusy = errors.New("busy")
errUnknownPeer = errors.New("peer is unknown or unhealthy")
errBadPeer = errors.New("action from bad peer ignored") errBadPeer = errors.New("action from bad peer ignored")
errStallingPeer = errors.New("peer is stalling")
errUnsyncedPeer = errors.New("unsynced peer")
errNoPeers = errors.New("no peers to keep download active")
errTimeout = errors.New("timeout") errTimeout = errors.New("timeout")
errEmptyHeaderSet = errors.New("empty header set by peer")
errPeersUnavailable = errors.New("no peers available or all tried for download")
errInvalidAncestor = errors.New("retrieved ancestor is invalid")
errInvalidChain = errors.New("retrieved hash chain is invalid") errInvalidChain = errors.New("retrieved hash chain is invalid")
errInvalidBody = errors.New("retrieved block body is invalid") errInvalidBody = errors.New("retrieved block body is invalid")
errInvalidReceipt = errors.New("retrieved receipt is invalid") errInvalidReceipt = errors.New("retrieved receipt is invalid")
errCancelStateFetch = errors.New("state data download canceled (requested)") errCancelStateFetch = errors.New("state data download canceled (requested)")
errCancelContentProcessing = errors.New("content processing canceled (requested)") errCancelContentProcessing = errors.New("content processing canceled (requested)")
errCanceled = errors.New("syncing canceled (requested)") errCanceled = errors.New("syncing canceled (requested)")
errTooOld = errors.New("peer's protocol version too old")
errNoAncestorFound = errors.New("no common ancestor found")
errNoPivotHeader = errors.New("pivot header is not found") errNoPivotHeader = errors.New("pivot header is not found")
ErrMergeTransition = errors.New("legacy sync reached the merge") ErrMergeTransition = errors.New("legacy sync reached the merge")
) )
@ -99,7 +88,6 @@ type Downloader struct {
mode atomic.Uint32 // Synchronisation mode defining the strategy used (per sync cycle), use d.getMode() to get the SyncMode mode atomic.Uint32 // Synchronisation mode defining the strategy used (per sync cycle), use d.getMode() to get the SyncMode
mux *event.TypeMux // Event multiplexer to announce sync operation events mux *event.TypeMux // Event multiplexer to announce sync operation events
genesis uint64 // Genesis block number to limit sync to (e.g. light client CHT)
queue *queue // Scheduler for selecting the hashes to download queue *queue // Scheduler for selecting the hashes to download
peers *peerSet // Set of active peers from which download can proceed peers *peerSet // Set of active peers from which download can proceed
@ -118,7 +106,6 @@ type Downloader struct {
badBlock badBlockFn // Reports a block as rejected by the chain badBlock badBlockFn // Reports a block as rejected by the chain
// Status // Status
synchroniseMock func(id string, hash common.Hash) error // Replacement for synchronise during testing
synchronising atomic.Bool synchronising atomic.Bool
notified atomic.Bool notified atomic.Bool
committed atomic.Bool committed atomic.Bool
@ -138,7 +125,6 @@ type Downloader struct {
stateSyncStart chan *stateSync stateSyncStart chan *stateSync
// Cancellation and termination // Cancellation and termination
cancelPeer string // Identifier of the peer currently being used as the master (cancel on drop)
cancelCh chan struct{} // Channel to cancel mid-flight syncs cancelCh chan struct{} // Channel to cancel mid-flight syncs
cancelLock sync.RWMutex // Lock to protect the cancel channel and peer in delivers cancelLock sync.RWMutex // Lock to protect the cancel channel and peer in delivers
cancelWg sync.WaitGroup // Make sure all fetcher goroutines have exited. cancelWg sync.WaitGroup // Make sure all fetcher goroutines have exited.
@ -147,7 +133,6 @@ type Downloader struct {
quitLock sync.Mutex // Lock to prevent double closes quitLock sync.Mutex // Lock to prevent double closes
// Testing hooks // Testing hooks
syncInitHook func(uint64, uint64) // Method to call upon initiating a new sync run
bodyFetchHook func([]*types.Header) // Method to call upon starting a block body fetch bodyFetchHook func([]*types.Header) // Method to call upon starting a block body fetch
receiptFetchHook func([]*types.Header) // Method to call upon starting a receipt fetch receiptFetchHook func([]*types.Header) // Method to call upon starting a receipt fetch
chainInsertHook func([]*fetchResult) // Method to call upon inserting a chain of blocks (possibly in multiple invocations) chainInsertHook func([]*fetchResult) // Method to call upon inserting a chain of blocks (possibly in multiple invocations)
@ -326,39 +311,10 @@ func (d *Downloader) UnregisterPeer(id string) error {
return nil return nil
} }
// LegacySync tries to sync up our local block chain with a remote peer, both
// adding various sanity checks as well as wrapping it with various log entries.
func (d *Downloader) LegacySync(id string, head common.Hash, td, ttd *big.Int, mode SyncMode) error {
err := d.synchronise(id, head, td, ttd, mode, false, nil)
switch err {
case nil, errBusy, errCanceled:
return err
}
if errors.Is(err, errInvalidChain) || errors.Is(err, errBadPeer) || errors.Is(err, errTimeout) ||
errors.Is(err, errStallingPeer) || errors.Is(err, errUnsyncedPeer) || errors.Is(err, errEmptyHeaderSet) ||
errors.Is(err, errPeersUnavailable) || errors.Is(err, errTooOld) || errors.Is(err, errInvalidAncestor) {
log.Warn("Synchronisation failed, dropping peer", "peer", id, "err", err)
if d.dropPeer == nil {
// The dropPeer method is nil when `--copydb` is used for a local copy.
// Timeouts can occur if e.g. compaction hits at the wrong time, and can be ignored
log.Warn("Downloader wants to drop peer, but peerdrop-function is not set", "peer", id)
} else {
d.dropPeer(id)
}
return err
}
if errors.Is(err, ErrMergeTransition) {
return err // This is an expected fault, don't keep printing it in a spin-loop
}
log.Warn("Synchronisation failed, retrying", "err", err)
return err
}
// synchronise will select the peer and use it for synchronising. If an empty string is given // synchronise will select the peer and use it for synchronising. If an empty string is given
// it will use the best peer possible and synchronize if its TD is higher than our own. If any of the // it will use the best peer possible and synchronize if its TD is higher than our own. If any of the
// checks fail an error will be returned. This method is synchronous // checks fail an error will be returned. This method is synchronous
func (d *Downloader) synchronise(id string, hash common.Hash, td, ttd *big.Int, mode SyncMode, beaconMode bool, beaconPing chan struct{}) error { func (d *Downloader) synchronise(mode SyncMode, beaconPing chan struct{}) error {
// The beacon header syncer is async. It will start this synchronization and // The beacon header syncer is async. It will start this synchronization and
// will continue doing other tasks. However, if synchronization needs to be // will continue doing other tasks. However, if synchronization needs to be
// cancelled, the syncer needs to know if we reached the startup point (and // cancelled, the syncer needs to know if we reached the startup point (and
@ -373,10 +329,6 @@ func (d *Downloader) synchronise(id string, hash common.Hash, td, ttd *big.Int,
} }
}() }()
} }
// Mock out the synchronisation if testing
if d.synchroniseMock != nil {
return d.synchroniseMock(id, hash)
}
// Make sure only one goroutine is ever allowed past this point at once // Make sure only one goroutine is ever allowed past this point at once
if !d.synchronising.CompareAndSwap(false, true) { if !d.synchronising.CompareAndSwap(false, true) {
return errBusy return errBusy
@ -424,7 +376,6 @@ func (d *Downloader) synchronise(id string, hash common.Hash, td, ttd *big.Int,
// Create cancel channel for aborting mid-flight and mark the master peer // Create cancel channel for aborting mid-flight and mark the master peer
d.cancelLock.Lock() d.cancelLock.Lock()
d.cancelCh = make(chan struct{}) d.cancelCh = make(chan struct{})
d.cancelPeer = id
d.cancelLock.Unlock() d.cancelLock.Unlock()
defer d.Cancel() // No matter what, we can't leave the cancel channel open defer d.Cancel() // No matter what, we can't leave the cancel channel open
@ -432,27 +383,19 @@ func (d *Downloader) synchronise(id string, hash common.Hash, td, ttd *big.Int,
// Atomically set the requested sync mode // Atomically set the requested sync mode
d.mode.Store(uint32(mode)) d.mode.Store(uint32(mode))
// Retrieve the origin peer and initiate the downloading process
var p *peerConnection
if !beaconMode { // Beacon mode doesn't need a peer to sync from
p = d.peers.Peer(id)
if p == nil {
return errUnknownPeer
}
}
if beaconPing != nil { if beaconPing != nil {
close(beaconPing) close(beaconPing)
} }
return d.syncWithPeer(p, hash, td, ttd, beaconMode) return d.syncToHead()
} }
func (d *Downloader) getMode() SyncMode { func (d *Downloader) getMode() SyncMode {
return SyncMode(d.mode.Load()) return SyncMode(d.mode.Load())
} }
// syncWithPeer starts a block synchronization based on the hash chain from the // syncToHead starts a block synchronization based on the hash chain from
// specified peer and head hash. // the specified head hash.
func (d *Downloader) syncWithPeer(p *peerConnection, hash common.Hash, td, ttd *big.Int, beaconMode bool) (err error) { func (d *Downloader) syncToHead() (err error) {
d.mux.Post(StartEvent{}) d.mux.Post(StartEvent{})
defer func() { defer func() {
// reset on error // reset on error
@ -465,25 +408,13 @@ func (d *Downloader) syncWithPeer(p *peerConnection, hash common.Hash, td, ttd *
}() }()
mode := d.getMode() mode := d.getMode()
if !beaconMode {
log.Debug("Synchronising with the network", "peer", p.id, "eth", p.version, "head", hash, "td", td, "mode", mode)
} else {
log.Debug("Backfilling with the network", "mode", mode) log.Debug("Backfilling with the network", "mode", mode)
}
defer func(start time.Time) { defer func(start time.Time) {
log.Debug("Synchronisation terminated", "elapsed", common.PrettyDuration(time.Since(start))) log.Debug("Synchronisation terminated", "elapsed", common.PrettyDuration(time.Since(start)))
}(time.Now()) }(time.Now())
// Look up the sync boundaries: the common ancestor and the target block // Look up the sync boundaries: the common ancestor and the target block
var latest, pivot, final *types.Header var latest, pivot, final *types.Header
if !beaconMode {
// In legacy mode, use the master peer to retrieve the headers from
latest, pivot, err = d.fetchHead(p)
if err != nil {
return err
}
} else {
// In beacon mode, use the skeleton chain to retrieve the headers from
latest, _, final, err = d.skeleton.Bounds() latest, _, final, err = d.skeleton.Bounds()
if err != nil { if err != nil {
return err return err
@ -512,7 +443,6 @@ func (d *Downloader) syncWithPeer(p *peerConnection, hash common.Hash, td, ttd *
return errNoPivotHeader return errNoPivotHeader
} }
} }
}
// If no pivot block was returned, the head is below the min full block // If no pivot block was returned, the head is below the min full block
// threshold (i.e. new chain). In that case we won't really snap sync // threshold (i.e. new chain). In that case we won't really snap sync
// anyway, but still need a valid pivot block to avoid some code hitting // anyway, but still need a valid pivot block to avoid some code hitting
@ -522,20 +452,11 @@ func (d *Downloader) syncWithPeer(p *peerConnection, hash common.Hash, td, ttd *
} }
height := latest.Number.Uint64() height := latest.Number.Uint64()
var origin uint64
if !beaconMode {
// In legacy mode, reach out to the network and find the ancestor
origin, err = d.findAncestor(p, latest)
if err != nil {
return err
}
} else {
// In beacon mode, use the skeleton chain for the ancestor lookup // In beacon mode, use the skeleton chain for the ancestor lookup
origin, err = d.findBeaconAncestor() origin, err := d.findBeaconAncestor()
if err != nil { if err != nil {
return err return err
} }
}
d.syncStatsLock.Lock() d.syncStatsLock.Lock()
if d.syncStatsChainHeight <= origin || d.syncStatsChainOrigin > origin { if d.syncStatsChainHeight <= origin || d.syncStatsChainOrigin > origin {
d.syncStatsChainOrigin = origin d.syncStatsChainOrigin = origin
@ -577,7 +498,7 @@ func (d *Downloader) syncWithPeer(p *peerConnection, hash common.Hash, td, ttd *
// the ancientLimit through that. Otherwise calculate the ancient limit through // the ancientLimit through that. Otherwise calculate the ancient limit through
// the advertised height of the remote peer. This most is mostly a fallback for // the advertised height of the remote peer. This most is mostly a fallback for
// legacy networks, but should eventually be dropped. TODO(karalabe). // legacy networks, but should eventually be dropped. TODO(karalabe).
if beaconMode { //
// Beacon sync, use the latest finalized block as the ancient limit // Beacon sync, use the latest finalized block as the ancient limit
// or a reasonable height if no finalized block is yet announced. // or a reasonable height if no finalized block is yet announced.
if final != nil { if final != nil {
@ -587,15 +508,6 @@ func (d *Downloader) syncWithPeer(p *peerConnection, hash common.Hash, td, ttd *
} else { } else {
d.ancientLimit = 0 d.ancientLimit = 0
} }
} else {
// Legacy sync, use the best announcement we have from the remote peer.
// TODO(karalabe): Drop this pathway.
if height > fullMaxForkAncestry+1 {
d.ancientLimit = height - fullMaxForkAncestry - 1
} else {
d.ancientLimit = 0
}
}
frozen, _ := d.stateDB.Ancients() // Ignore the error here since light client can also hit here. frozen, _ := d.stateDB.Ancients() // Ignore the error here since light client can also hit here.
// If a part of blockchain data has already been written into active store, // If a part of blockchain data has already been written into active store,
@ -616,22 +528,13 @@ func (d *Downloader) syncWithPeer(p *peerConnection, hash common.Hash, td, ttd *
} }
// Initiate the sync using a concurrent header and content retrieval algorithm // Initiate the sync using a concurrent header and content retrieval algorithm
d.queue.Prepare(origin+1, mode) d.queue.Prepare(origin+1, mode)
if d.syncInitHook != nil {
d.syncInitHook(origin, height)
}
var headerFetcher func() error
if !beaconMode {
// In legacy mode, headers are retrieved from the network
headerFetcher = func() error { return d.fetchHeaders(p, origin+1, latest.Number.Uint64()) }
} else {
// In beacon mode, headers are served by the skeleton syncer // In beacon mode, headers are served by the skeleton syncer
headerFetcher = func() error { return d.fetchBeaconHeaders(origin + 1) }
}
fetchers := []func() error{ fetchers := []func() error{
headerFetcher, // Headers are always retrieved func() error { return d.fetchHeaders(origin + 1) }, // Headers are always retrieved
func() error { return d.fetchBodies(origin+1, beaconMode) }, // Bodies are retrieved during normal and snap sync func() error { return d.fetchBodies(origin + 1) }, // Bodies are retrieved during normal and snap sync
func() error { return d.fetchReceipts(origin+1, beaconMode) }, // Receipts are retrieved during snap sync func() error { return d.fetchReceipts(origin + 1) }, // Receipts are retrieved during snap sync
func() error { return d.processHeaders(origin+1, td, ttd, beaconMode) }, func() error { return d.processHeaders(origin + 1) },
} }
if mode == SnapSync { if mode == SnapSync {
d.pivotLock.Lock() d.pivotLock.Lock()
@ -640,7 +543,7 @@ func (d *Downloader) syncWithPeer(p *peerConnection, hash common.Hash, td, ttd *
fetchers = append(fetchers, func() error { return d.processSnapSyncContent() }) fetchers = append(fetchers, func() error { return d.processSnapSyncContent() })
} else if mode == FullSync { } else if mode == FullSync {
fetchers = append(fetchers, func() error { return d.processFullSyncContent(ttd, beaconMode) }) fetchers = append(fetchers, func() error { return d.processFullSyncContent() })
} }
return d.spawnSync(fetchers) return d.spawnSync(fetchers)
} }
@ -719,540 +622,12 @@ func (d *Downloader) Terminate() {
d.Cancel() d.Cancel()
} }
// fetchHead retrieves the head header and prior pivot block (if available) from
// a remote peer.
func (d *Downloader) fetchHead(p *peerConnection) (head *types.Header, pivot *types.Header, err error) {
p.log.Debug("Retrieving remote chain head")
mode := d.getMode()
// Request the advertised remote head block and wait for the response
latest, _ := p.peer.Head()
fetch := 1
if mode == SnapSync {
fetch = 2 // head + pivot headers
}
headers, hashes, err := d.fetchHeadersByHash(p, latest, fetch, fsMinFullBlocks-1, true)
if err != nil {
return nil, nil, err
}
// Make sure the peer gave us at least one and at most the requested headers
if len(headers) == 0 || len(headers) > fetch {
return nil, nil, fmt.Errorf("%w: returned headers %d != requested %d", errBadPeer, len(headers), fetch)
}
// The first header needs to be the head, validate against the request. If
// only 1 header was returned, make sure there's no pivot or there was not
// one requested.
head = headers[0]
if len(headers) == 1 {
if mode == SnapSync && head.Number.Uint64() > uint64(fsMinFullBlocks) {
return nil, nil, fmt.Errorf("%w: no pivot included along head header", errBadPeer)
}
p.log.Debug("Remote head identified, no pivot", "number", head.Number, "hash", hashes[0])
return head, nil, nil
}
// At this point we have 2 headers in total and the first is the
// validated head of the chain. Check the pivot number and return,
pivot = headers[1]
if pivot.Number.Uint64() != head.Number.Uint64()-uint64(fsMinFullBlocks) {
return nil, nil, fmt.Errorf("%w: remote pivot %d != requested %d", errInvalidChain, pivot.Number, head.Number.Uint64()-uint64(fsMinFullBlocks))
}
return head, pivot, nil
}
// calculateRequestSpan calculates what headers to request from a peer when trying to determine the
// common ancestor.
// It returns parameters to be used for peer.RequestHeadersByNumber:
//
// from - starting block number
// count - number of headers to request
// skip - number of headers to skip
//
// and also returns 'max', the last block which is expected to be returned by the remote peers,
// given the (from,count,skip)
func calculateRequestSpan(remoteHeight, localHeight uint64) (int64, int, int, uint64) {
var (
from int
count int
MaxCount = MaxHeaderFetch / 16
)
// requestHead is the highest block that we will ask for. If requestHead is not offset,
// the highest block that we will get is 16 blocks back from head, which means we
// will fetch 14 or 15 blocks unnecessarily in the case the height difference
// between us and the peer is 1-2 blocks, which is most common
requestHead := int(remoteHeight) - 1
if requestHead < 0 {
requestHead = 0
}
// requestBottom is the lowest block we want included in the query
// Ideally, we want to include the one just below our own head
requestBottom := int(localHeight - 1)
if requestBottom < 0 {
requestBottom = 0
}
totalSpan := requestHead - requestBottom
span := 1 + totalSpan/MaxCount
if span < 2 {
span = 2
}
if span > 16 {
span = 16
}
count = 1 + totalSpan/span
if count > MaxCount {
count = MaxCount
}
if count < 2 {
count = 2
}
from = requestHead - (count-1)*span
if from < 0 {
from = 0
}
max := from + (count-1)*span
return int64(from), count, span - 1, uint64(max)
}
// findAncestor tries to locate the common ancestor link of the local chain and
// a remote peers blockchain. In the general case when our node was in sync and
// on the correct chain, checking the top N links should already get us a match.
// In the rare scenario when we ended up on a long reorganisation (i.e. none of
// the head links match), we do a binary search to find the common ancestor.
func (d *Downloader) findAncestor(p *peerConnection, remoteHeader *types.Header) (uint64, error) {
// Figure out the valid ancestor range to prevent rewrite attacks
var (
floor = int64(-1)
localHeight uint64
remoteHeight = remoteHeader.Number.Uint64()
)
mode := d.getMode()
switch mode {
case FullSync:
localHeight = d.blockchain.CurrentBlock().Number.Uint64()
case SnapSync:
localHeight = d.blockchain.CurrentSnapBlock().Number.Uint64()
default:
localHeight = d.lightchain.CurrentHeader().Number.Uint64()
}
p.log.Debug("Looking for common ancestor", "local", localHeight, "remote", remoteHeight)
// Recap floor value for binary search
maxForkAncestry := fullMaxForkAncestry
if d.getMode() == LightSync {
maxForkAncestry = lightMaxForkAncestry
}
if localHeight >= maxForkAncestry {
// We're above the max reorg threshold, find the earliest fork point
floor = int64(localHeight - maxForkAncestry)
}
// If we're doing a light sync, ensure the floor doesn't go below the CHT, as
// all headers before that point will be missing.
if mode == LightSync {
// If we don't know the current CHT position, find it
if d.genesis == 0 {
header := d.lightchain.CurrentHeader()
for header != nil {
d.genesis = header.Number.Uint64()
if floor >= int64(d.genesis)-1 {
break
}
header = d.lightchain.GetHeaderByHash(header.ParentHash)
}
}
// We already know the "genesis" block number, cap floor to that
if floor < int64(d.genesis)-1 {
floor = int64(d.genesis) - 1
}
}
ancestor, err := d.findAncestorSpanSearch(p, mode, remoteHeight, localHeight, floor)
if err == nil {
return ancestor, nil
}
// The returned error was not nil.
// If the error returned does not reflect that a common ancestor was not found, return it.
// If the error reflects that a common ancestor was not found, continue to binary search,
// where the error value will be reassigned.
if !errors.Is(err, errNoAncestorFound) {
return 0, err
}
ancestor, err = d.findAncestorBinarySearch(p, mode, remoteHeight, floor)
if err != nil {
return 0, err
}
return ancestor, nil
}
func (d *Downloader) findAncestorSpanSearch(p *peerConnection, mode SyncMode, remoteHeight, localHeight uint64, floor int64) (uint64, error) {
from, count, skip, max := calculateRequestSpan(remoteHeight, localHeight)
p.log.Trace("Span searching for common ancestor", "count", count, "from", from, "skip", skip)
headers, hashes, err := d.fetchHeadersByNumber(p, uint64(from), count, skip, false)
if err != nil {
return 0, err
}
// Wait for the remote response to the head fetch
number, hash := uint64(0), common.Hash{}
// Make sure the peer actually gave something valid
if len(headers) == 0 {
p.log.Warn("Empty head header set")
return 0, errEmptyHeaderSet
}
// Make sure the peer's reply conforms to the request
for i, header := range headers {
expectNumber := from + int64(i)*int64(skip+1)
if number := header.Number.Int64(); number != expectNumber {
p.log.Warn("Head headers broke chain ordering", "index", i, "requested", expectNumber, "received", number)
return 0, fmt.Errorf("%w: %v", errInvalidChain, errors.New("head headers broke chain ordering"))
}
}
// Check if a common ancestor was found
for i := len(headers) - 1; i >= 0; i-- {
// Skip any headers that underflow/overflow our requested set
if headers[i].Number.Int64() < from || headers[i].Number.Uint64() > max {
continue
}
// Otherwise check if we already know the header or not
h := hashes[i]
n := headers[i].Number.Uint64()
var known bool
switch mode {
case FullSync:
known = d.blockchain.HasBlock(h, n)
case SnapSync:
known = d.blockchain.HasFastBlock(h, n)
default:
known = d.lightchain.HasHeader(h, n)
}
if known {
number, hash = n, h
break
}
}
// If the head fetch already found an ancestor, return
if hash != (common.Hash{}) {
if int64(number) <= floor {
p.log.Warn("Ancestor below allowance", "number", number, "hash", hash, "allowance", floor)
return 0, errInvalidAncestor
}
p.log.Debug("Found common ancestor", "number", number, "hash", hash)
return number, nil
}
return 0, errNoAncestorFound
}
func (d *Downloader) findAncestorBinarySearch(p *peerConnection, mode SyncMode, remoteHeight uint64, floor int64) (uint64, error) {
hash := common.Hash{}
// Ancestor not found, we need to binary search over our chain
start, end := uint64(0), remoteHeight
if floor > 0 {
start = uint64(floor)
}
p.log.Trace("Binary searching for common ancestor", "start", start, "end", end)
for start+1 < end {
// Split our chain interval in two, and request the hash to cross check
check := (start + end) / 2
headers, hashes, err := d.fetchHeadersByNumber(p, check, 1, 0, false)
if err != nil {
return 0, err
}
// Make sure the peer actually gave something valid
if len(headers) != 1 {
p.log.Warn("Multiple headers for single request", "headers", len(headers))
return 0, fmt.Errorf("%w: multiple headers (%d) for single request", errBadPeer, len(headers))
}
// Modify the search interval based on the response
h := hashes[0]
n := headers[0].Number.Uint64()
var known bool
switch mode {
case FullSync:
known = d.blockchain.HasBlock(h, n)
case SnapSync:
known = d.blockchain.HasFastBlock(h, n)
default:
known = d.lightchain.HasHeader(h, n)
}
if !known {
end = check
continue
}
header := d.lightchain.GetHeaderByHash(h) // Independent of sync mode, header surely exists
if header.Number.Uint64() != check {
p.log.Warn("Received non requested header", "number", header.Number, "hash", header.Hash(), "request", check)
return 0, fmt.Errorf("%w: non-requested header (%d)", errBadPeer, header.Number)
}
start = check
hash = h
}
// Ensure valid ancestry and return
if int64(start) <= floor {
p.log.Warn("Ancestor below allowance", "number", start, "hash", hash, "allowance", floor)
return 0, errInvalidAncestor
}
p.log.Debug("Found common ancestor", "number", start, "hash", hash)
return start, nil
}
// fetchHeaders keeps retrieving headers concurrently from the number
// requested, until no more are returned, potentially throttling on the way. To
// facilitate concurrency but still protect against malicious nodes sending bad
// headers, we construct a header chain skeleton using the "origin" peer we are
// syncing with, and fill in the missing headers using anyone else. Headers from
// other peers are only accepted if they map cleanly to the skeleton. If no one
// can fill in the skeleton - not even the origin peer - it's assumed invalid and
// the origin is dropped.
func (d *Downloader) fetchHeaders(p *peerConnection, from uint64, head uint64) error {
p.log.Debug("Directing header downloads", "origin", from)
defer p.log.Debug("Header download terminated")
// Start pulling the header chain skeleton until all is done
var (
skeleton = true // Skeleton assembly phase or finishing up
pivoting = false // Whether the next request is pivot verification
ancestor = from
mode = d.getMode()
)
for {
// Pull the next batch of headers, it either:
// - Pivot check to see if the chain moved too far
// - Skeleton retrieval to permit concurrent header fetches
// - Full header retrieval if we're near the chain head
var (
headers []*types.Header
hashes []common.Hash
err error
)
switch {
case pivoting:
d.pivotLock.RLock()
pivot := d.pivotHeader.Number.Uint64()
d.pivotLock.RUnlock()
p.log.Trace("Fetching next pivot header", "number", pivot+uint64(fsMinFullBlocks))
headers, hashes, err = d.fetchHeadersByNumber(p, pivot+uint64(fsMinFullBlocks), 2, fsMinFullBlocks-9, false) // move +64 when it's 2x64-8 deep
case skeleton:
p.log.Trace("Fetching skeleton headers", "count", MaxHeaderFetch, "from", from)
headers, hashes, err = d.fetchHeadersByNumber(p, from+uint64(MaxHeaderFetch)-1, MaxSkeletonSize, MaxHeaderFetch-1, false)
default:
p.log.Trace("Fetching full headers", "count", MaxHeaderFetch, "from", from)
headers, hashes, err = d.fetchHeadersByNumber(p, from, MaxHeaderFetch, 0, false)
}
switch err {
case nil:
// Headers retrieved, continue with processing
case errCanceled:
// Sync cancelled, no issue, propagate up
return err
default:
// Header retrieval either timed out, or the peer failed in some strange way
// (e.g. disconnect). Consider the master peer bad and drop
d.dropPeer(p.id)
// Finish the sync gracefully instead of dumping the gathered data though
for _, ch := range []chan bool{d.queue.blockWakeCh, d.queue.receiptWakeCh} {
select {
case ch <- false:
case <-d.cancelCh:
}
}
select {
case d.headerProcCh <- nil:
case <-d.cancelCh:
}
return fmt.Errorf("%w: header request failed: %v", errBadPeer, err)
}
// If the pivot is being checked, move if it became stale and run the real retrieval
var pivot uint64
d.pivotLock.RLock()
if d.pivotHeader != nil {
pivot = d.pivotHeader.Number.Uint64()
}
d.pivotLock.RUnlock()
if pivoting {
if len(headers) == 2 {
if have, want := headers[0].Number.Uint64(), pivot+uint64(fsMinFullBlocks); have != want {
log.Warn("Peer sent invalid next pivot", "have", have, "want", want)
return fmt.Errorf("%w: next pivot number %d != requested %d", errInvalidChain, have, want)
}
if have, want := headers[1].Number.Uint64(), pivot+2*uint64(fsMinFullBlocks)-8; have != want {
log.Warn("Peer sent invalid pivot confirmer", "have", have, "want", want)
return fmt.Errorf("%w: next pivot confirmer number %d != requested %d", errInvalidChain, have, want)
}
log.Warn("Pivot seemingly stale, moving", "old", pivot, "new", headers[0].Number)
pivot = headers[0].Number.Uint64()
d.pivotLock.Lock()
d.pivotHeader = headers[0]
d.pivotLock.Unlock()
// Write out the pivot into the database so a rollback beyond
// it will reenable snap sync and update the state root that
// the state syncer will be downloading.
rawdb.WriteLastPivotNumber(d.stateDB, pivot)
}
// Disable the pivot check and fetch the next batch of headers
pivoting = false
continue
}
// If the skeleton's finished, pull any remaining head headers directly from the origin
if skeleton && len(headers) == 0 {
// A malicious node might withhold advertised headers indefinitely
if from+uint64(MaxHeaderFetch)-1 <= head {
p.log.Warn("Peer withheld skeleton headers", "advertised", head, "withheld", from+uint64(MaxHeaderFetch)-1)
return fmt.Errorf("%w: withheld skeleton headers: advertised %d, withheld #%d", errStallingPeer, head, from+uint64(MaxHeaderFetch)-1)
}
p.log.Debug("No skeleton, fetching headers directly")
skeleton = false
continue
}
// If no more headers are inbound, notify the content fetchers and return
if len(headers) == 0 {
// Don't abort header fetches while the pivot is downloading
if !d.committed.Load() && pivot <= from {
p.log.Debug("No headers, waiting for pivot commit")
select {
case <-time.After(fsHeaderContCheck):
continue
case <-d.cancelCh:
return errCanceled
}
}
// Pivot done (or not in snap sync) and no more headers, terminate the process
p.log.Debug("No more headers available")
select {
case d.headerProcCh <- nil:
return nil
case <-d.cancelCh:
return errCanceled
}
}
// If we received a skeleton batch, resolve internals concurrently
var progressed bool
if skeleton {
filled, hashset, proced, err := d.fillHeaderSkeleton(from, headers)
if err != nil {
p.log.Debug("Skeleton chain invalid", "err", err)
return fmt.Errorf("%w: %v", errInvalidChain, err)
}
headers = filled[proced:]
hashes = hashset[proced:]
progressed = proced > 0
from += uint64(proced)
} else {
// A malicious node might withhold advertised headers indefinitely
if n := len(headers); n < MaxHeaderFetch && headers[n-1].Number.Uint64() < head {
p.log.Warn("Peer withheld headers", "advertised", head, "delivered", headers[n-1].Number.Uint64())
return fmt.Errorf("%w: withheld headers: advertised %d, delivered %d", errStallingPeer, head, headers[n-1].Number.Uint64())
}
// If we're closing in on the chain head, but haven't yet reached it, delay
// the last few headers so mini reorgs on the head don't cause invalid hash
// chain errors.
if n := len(headers); n > 0 {
// Retrieve the current head we're at
var head uint64
if mode == LightSync {
head = d.lightchain.CurrentHeader().Number.Uint64()
} else {
head = d.blockchain.CurrentSnapBlock().Number.Uint64()
if full := d.blockchain.CurrentBlock().Number.Uint64(); head < full {
head = full
}
}
// If the head is below the common ancestor, we're actually deduplicating
// already existing chain segments, so use the ancestor as the fake head.
// Otherwise, we might end up delaying header deliveries pointlessly.
if head < ancestor {
head = ancestor
}
// If the head is way older than this batch, delay the last few headers
if head+uint64(reorgProtThreshold) < headers[n-1].Number.Uint64() {
delay := reorgProtHeaderDelay
if delay > n {
delay = n
}
headers = headers[:n-delay]
hashes = hashes[:n-delay]
}
}
}
// If no headers have been delivered, or all of them have been delayed,
// sleep a bit and retry. Take care with headers already consumed during
// skeleton filling
if len(headers) == 0 && !progressed {
p.log.Trace("All headers delayed, waiting")
select {
case <-time.After(fsHeaderContCheck):
continue
case <-d.cancelCh:
return errCanceled
}
}
// Insert any remaining new headers and fetch the next batch
if len(headers) > 0 {
p.log.Trace("Scheduling new headers", "count", len(headers), "from", from)
select {
case d.headerProcCh <- &headerTask{
headers: headers,
hashes: hashes,
}:
case <-d.cancelCh:
return errCanceled
}
from += uint64(len(headers))
}
// If we're still skeleton filling snap sync, check pivot staleness
// before continuing to the next skeleton filling
if skeleton && pivot > 0 {
pivoting = true
}
}
}
// fillHeaderSkeleton concurrently retrieves headers from all our available peers
// and maps them to the provided skeleton header chain.
//
// Any partial results from the beginning of the skeleton is (if possible) forwarded
// immediately to the header processor to keep the rest of the pipeline full even
// in the case of header stalls.
//
// The method returns the entire filled skeleton and also the number of headers
// already forwarded for processing.
func (d *Downloader) fillHeaderSkeleton(from uint64, skeleton []*types.Header) ([]*types.Header, []common.Hash, int, error) {
log.Debug("Filling up skeleton", "from", from)
d.queue.ScheduleSkeleton(from, skeleton)
err := d.concurrentFetch((*headerQueue)(d), false)
if err != nil {
log.Debug("Skeleton fill failed", "err", err)
}
filled, hashes, proced := d.queue.RetrieveHeaders()
if err == nil {
log.Debug("Skeleton fill succeeded", "filled", len(filled), "processed", proced)
}
return filled, hashes, proced, err
}
// fetchBodies iteratively downloads the scheduled block bodies, taking any // fetchBodies iteratively downloads the scheduled block bodies, taking any
// available peers, reserving a chunk of blocks for each, waiting for delivery // available peers, reserving a chunk of blocks for each, waiting for delivery
// and also periodically checking for timeouts. // and also periodically checking for timeouts.
func (d *Downloader) fetchBodies(from uint64, beaconMode bool) error { func (d *Downloader) fetchBodies(from uint64) error {
log.Debug("Downloading block bodies", "origin", from) log.Debug("Downloading block bodies", "origin", from)
err := d.concurrentFetch((*bodyQueue)(d), beaconMode) err := d.concurrentFetch((*bodyQueue)(d))
log.Debug("Block body download terminated", "err", err) log.Debug("Block body download terminated", "err", err)
return err return err
@ -1261,9 +636,9 @@ func (d *Downloader) fetchBodies(from uint64, beaconMode bool) error {
// fetchReceipts iteratively downloads the scheduled block receipts, taking any // fetchReceipts iteratively downloads the scheduled block receipts, taking any
// available peers, reserving a chunk of receipts for each, waiting for delivery // available peers, reserving a chunk of receipts for each, waiting for delivery
// and also periodically checking for timeouts. // and also periodically checking for timeouts.
func (d *Downloader) fetchReceipts(from uint64, beaconMode bool) error { func (d *Downloader) fetchReceipts(from uint64) error {
log.Debug("Downloading receipts", "origin", from) log.Debug("Downloading receipts", "origin", from)
err := d.concurrentFetch((*receiptQueue)(d), beaconMode) err := d.concurrentFetch((*receiptQueue)(d))
log.Debug("Receipt download terminated", "err", err) log.Debug("Receipt download terminated", "err", err)
return err return err
@ -1272,10 +647,9 @@ func (d *Downloader) fetchReceipts(from uint64, beaconMode bool) error {
// processHeaders takes batches of retrieved headers from an input channel and // processHeaders takes batches of retrieved headers from an input channel and
// keeps processing and scheduling them into the header chain and downloader's // keeps processing and scheduling them into the header chain and downloader's
// queue until the stream ends or a failure occurs. // queue until the stream ends or a failure occurs.
func (d *Downloader) processHeaders(origin uint64, td, ttd *big.Int, beaconMode bool) error { func (d *Downloader) processHeaders(origin uint64) error {
var ( var (
mode = d.getMode() mode = d.getMode()
gotHeaders = false // Wait for batches of headers to process
timer = time.NewTimer(time.Second) timer = time.NewTimer(time.Second)
) )
defer timer.Stop() defer timer.Stop()
@ -1295,48 +669,11 @@ func (d *Downloader) processHeaders(origin uint64, td, ttd *big.Int, beaconMode
case <-d.cancelCh: case <-d.cancelCh:
} }
} }
// If we're in legacy sync mode, we need to check total difficulty
// violations from malicious peers. That is not needed in beacon
// mode and we can skip to terminating sync.
if !beaconMode {
// If no headers were retrieved at all, the peer violated its TD promise that it had a
// better chain compared to ours. The only exception is if its promised blocks were
// already imported by other means (e.g. fetcher):
//
// R <remote peer>, L <local node>: Both at block 10
// R: Mine block 11, and propagate it to L
// L: Queue block 11 for import
// L: Notice that R's head and TD increased compared to ours, start sync
// L: Import of block 11 finishes
// L: Sync begins, and finds common ancestor at 11
// L: Request new headers up from 11 (R's TD was higher, it must have something)
// R: Nothing to give
if mode != LightSync {
head := d.blockchain.CurrentBlock()
if !gotHeaders && td.Cmp(d.blockchain.GetTd(head.Hash(), head.Number.Uint64())) > 0 {
return errStallingPeer
}
}
// If snap or light syncing, ensure promised headers are indeed delivered. This is
// needed to detect scenarios where an attacker feeds a bad pivot and then bails out
// of delivering the post-pivot blocks that would flag the invalid content.
//
// This check cannot be executed "as is" for full imports, since blocks may still be
// queued for processing when the header download completes. However, as long as the
// peer gave us something useful, we're already happy/progressed (above check).
if mode == SnapSync || mode == LightSync {
head := d.lightchain.CurrentHeader()
if td.Cmp(d.lightchain.GetTd(head.Hash(), head.Number.Uint64())) > 0 {
return errStallingPeer
}
}
}
return nil return nil
} }
// Otherwise split the chunk of headers into batches and process them // Otherwise split the chunk of headers into batches and process them
headers, hashes := task.headers, task.hashes headers, hashes := task.headers, task.hashes
gotHeaders = true
for len(headers) > 0 { for len(headers) > 0 {
// Terminate if something failed in between processing chunks // Terminate if something failed in between processing chunks
select { select {
@ -1357,44 +694,12 @@ func (d *Downloader) processHeaders(origin uint64, td, ttd *big.Int, beaconMode
// Although the received headers might be all valid, a legacy // Although the received headers might be all valid, a legacy
// PoW/PoA sync must not accept post-merge headers. Make sure // PoW/PoA sync must not accept post-merge headers. Make sure
// that any transition is rejected at this point. // that any transition is rejected at this point.
var (
rejected []*types.Header
td *big.Int
)
if !beaconMode && ttd != nil {
td = d.blockchain.GetTd(chunkHeaders[0].ParentHash, chunkHeaders[0].Number.Uint64()-1)
if td == nil {
// This should never really happen, but handle gracefully for now
log.Error("Failed to retrieve parent header TD", "number", chunkHeaders[0].Number.Uint64()-1, "hash", chunkHeaders[0].ParentHash)
return fmt.Errorf("%w: parent TD missing", errInvalidChain)
}
for i, header := range chunkHeaders {
td = new(big.Int).Add(td, header.Difficulty)
if td.Cmp(ttd) >= 0 {
// Terminal total difficulty reached, allow the last header in
if new(big.Int).Sub(td, header.Difficulty).Cmp(ttd) < 0 {
chunkHeaders, rejected = chunkHeaders[:i+1], chunkHeaders[i+1:]
if len(rejected) > 0 {
// Make a nicer user log as to the first TD truly rejected
td = new(big.Int).Add(td, rejected[0].Difficulty)
}
} else {
chunkHeaders, rejected = chunkHeaders[:i], chunkHeaders[i:]
}
break
}
}
}
if len(chunkHeaders) > 0 { if len(chunkHeaders) > 0 {
if n, err := d.lightchain.InsertHeaderChain(chunkHeaders); err != nil { if n, err := d.lightchain.InsertHeaderChain(chunkHeaders); err != nil {
log.Warn("Invalid header encountered", "number", chunkHeaders[n].Number, "hash", chunkHashes[n], "parent", chunkHeaders[n].ParentHash, "err", err) log.Warn("Invalid header encountered", "number", chunkHeaders[n].Number, "hash", chunkHashes[n], "parent", chunkHeaders[n].ParentHash, "err", err)
return fmt.Errorf("%w: %v", errInvalidChain, err) return fmt.Errorf("%w: %v", errInvalidChain, err)
} }
} }
if len(rejected) != 0 {
log.Info("Legacy sync reached merge threshold", "number", rejected[0].Number, "hash", rejected[0].Hash(), "td", td, "ttd", ttd)
return ErrMergeTransition
}
} }
// Unless we're doing light chains, schedule the headers for associated content retrieval // Unless we're doing light chains, schedule the headers for associated content retrieval
if mode == FullSync || mode == SnapSync { if mode == FullSync || mode == SnapSync {
@ -1436,7 +741,7 @@ func (d *Downloader) processHeaders(origin uint64, td, ttd *big.Int, beaconMode
} }
// processFullSyncContent takes fetch results from the queue and imports them into the chain. // processFullSyncContent takes fetch results from the queue and imports them into the chain.
func (d *Downloader) processFullSyncContent(ttd *big.Int, beaconMode bool) error { func (d *Downloader) processFullSyncContent() error {
for { for {
results := d.queue.Results(true) results := d.queue.Results(true)
if len(results) == 0 { if len(results) == 0 {
@ -1445,44 +750,9 @@ func (d *Downloader) processFullSyncContent(ttd *big.Int, beaconMode bool) error
if d.chainInsertHook != nil { if d.chainInsertHook != nil {
d.chainInsertHook(results) d.chainInsertHook(results)
} }
// Although the received blocks might be all valid, a legacy PoW/PoA sync
// must not accept post-merge blocks. Make sure that pre-merge blocks are
// imported, but post-merge ones are rejected.
var (
rejected []*fetchResult
td *big.Int
)
if !beaconMode && ttd != nil {
td = d.blockchain.GetTd(results[0].Header.ParentHash, results[0].Header.Number.Uint64()-1)
if td == nil {
// This should never really happen, but handle gracefully for now
log.Error("Failed to retrieve parent block TD", "number", results[0].Header.Number.Uint64()-1, "hash", results[0].Header.ParentHash)
return fmt.Errorf("%w: parent TD missing", errInvalidChain)
}
for i, result := range results {
td = new(big.Int).Add(td, result.Header.Difficulty)
if td.Cmp(ttd) >= 0 {
// Terminal total difficulty reached, allow the last block in
if new(big.Int).Sub(td, result.Header.Difficulty).Cmp(ttd) < 0 {
results, rejected = results[:i+1], results[i+1:]
if len(rejected) > 0 {
// Make a nicer user log as to the first TD truly rejected
td = new(big.Int).Add(td, rejected[0].Header.Difficulty)
}
} else {
results, rejected = results[:i], results[i:]
}
break
}
}
}
if err := d.importBlockResults(results); err != nil { if err := d.importBlockResults(results); err != nil {
return err return err
} }
if len(rejected) != 0 {
log.Info("Legacy sync reached merge threshold", "number", rejected[0].Header.Number, "hash", rejected[0].Header.Hash(), "td", td, "ttd", ttd)
return ErrMergeTransition
}
} }
} }
@ -1504,7 +774,7 @@ func (d *Downloader) importBlockResults(results []*fetchResult) error {
) )
blocks := make([]*types.Block, len(results)) blocks := make([]*types.Block, len(results))
for i, result := range results { for i, result := range results {
blocks[i] = types.NewBlockWithHeader(result.Header).WithBody(result.Transactions, result.Uncles).WithWithdrawals(result.Withdrawals) blocks[i] = types.NewBlockWithHeader(result.Header).WithBody(result.body())
} }
// Downloaded blocks are always regarded as trusted after the // Downloaded blocks are always regarded as trusted after the
// transition. Because the downloaded chain is guided by the // transition. Because the downloaded chain is guided by the
@ -1726,7 +996,7 @@ func (d *Downloader) commitSnapSyncData(results []*fetchResult, stateSync *state
blocks := make([]*types.Block, len(results)) blocks := make([]*types.Block, len(results))
receipts := make([]types.Receipts, len(results)) receipts := make([]types.Receipts, len(results))
for i, result := range results { for i, result := range results {
blocks[i] = types.NewBlockWithHeader(result.Header).WithBody(result.Transactions, result.Uncles).WithWithdrawals(result.Withdrawals) blocks[i] = types.NewBlockWithHeader(result.Header).WithBody(result.body())
receipts[i] = result.Receipts receipts[i] = result.Receipts
} }
if index, err := d.blockchain.InsertReceiptChain(blocks, receipts, d.ancientLimit); err != nil { if index, err := d.blockchain.InsertReceiptChain(blocks, receipts, d.ancientLimit); err != nil {
@ -1737,7 +1007,7 @@ func (d *Downloader) commitSnapSyncData(results []*fetchResult, stateSync *state
} }
func (d *Downloader) commitPivotBlock(result *fetchResult) error { func (d *Downloader) commitPivotBlock(result *fetchResult) error {
block := types.NewBlockWithHeader(result.Header).WithBody(result.Transactions, result.Uncles).WithWithdrawals(result.Withdrawals) block := types.NewBlockWithHeader(result.Header).WithBody(result.body())
log.Debug("Committing snap sync pivot as new head", "number", block.Number(), "hash", block.Hash()) log.Debug("Committing snap sync pivot as new head", "number", block.Number(), "hash", block.Hash())
// Commit the pivot block as the new head, will require full sync from here on // Commit the pivot block as the new head, will require full sync from here on

View file

@ -20,7 +20,6 @@ import (
"fmt" "fmt"
"math/big" "math/big"
"os" "os"
"strings"
"sync" "sync"
"sync/atomic" "sync/atomic"
"testing" "testing"
@ -94,25 +93,6 @@ func (dl *downloadTester) terminate() {
os.RemoveAll(dl.freezer) os.RemoveAll(dl.freezer)
} }
// sync starts synchronizing with a remote peer, blocking until it completes.
func (dl *downloadTester) sync(id string, td *big.Int, mode SyncMode) error {
head := dl.peers[id].chain.CurrentBlock()
if td == nil {
// If no particular TD was requested, load from the peer's blockchain
td = dl.peers[id].chain.GetTd(head.Hash(), head.Number.Uint64())
}
// Synchronise with the chosen peer and ensure proper cleanup afterwards
err := dl.downloader.synchronise(id, head.Hash(), td, nil, mode, false, nil)
select {
case <-dl.downloader.cancelCh:
// Ok, downloader fully cancelled after sync cycle
default:
// Downloader is still accepting packets, can block a peer up
panic("downloader active post sync cycle") // panic will be caught by tester
}
return err
}
// newPeer registers a new block download source into the downloader. // newPeer registers a new block download source into the downloader.
func (dl *downloadTester) newPeer(id string, version uint, blocks []*types.Block) *downloadTesterPeer { func (dl *downloadTester) newPeer(id string, version uint, blocks []*types.Block) *downloadTesterPeer {
dl.lock.Lock() dl.lock.Lock()
@ -122,7 +102,7 @@ func (dl *downloadTester) newPeer(id string, version uint, blocks []*types.Block
dl: dl, dl: dl,
id: id, id: id,
chain: newTestBlockchain(blocks), chain: newTestBlockchain(blocks),
withholdHeaders: make(map[common.Hash]struct{}), withholdBodies: make(map[common.Hash]struct{}),
} }
dl.peers[id] = peer dl.peers[id] = peer
@ -147,10 +127,9 @@ func (dl *downloadTester) dropPeer(id string) {
type downloadTesterPeer struct { type downloadTesterPeer struct {
dl *downloadTester dl *downloadTester
withholdBodies map[common.Hash]struct{}
id string id string
chain *core.BlockChain chain *core.BlockChain
withholdHeaders map[common.Hash]struct{}
} }
// Head constructs a function to retrieve a peer's current head hash // Head constructs a function to retrieve a peer's current head hash
@ -186,15 +165,6 @@ func (dlp *downloadTesterPeer) RequestHeadersByHash(origin common.Hash, amount i
Reverse: reverse, Reverse: reverse,
}, nil) }, nil)
headers := unmarshalRlpHeaders(rlpHeaders) headers := unmarshalRlpHeaders(rlpHeaders)
// If a malicious peer is simulated withholding headers, delete them
for hash := range dlp.withholdHeaders {
for i, header := range headers {
if header.Hash() == hash {
headers = append(headers[:i], headers[i+1:]...)
break
}
}
}
hashes := make([]common.Hash, len(headers)) hashes := make([]common.Hash, len(headers))
for i, header := range headers { for i, header := range headers {
hashes[i] = header.Hash() hashes[i] = header.Hash()
@ -230,15 +200,6 @@ func (dlp *downloadTesterPeer) RequestHeadersByNumber(origin uint64, amount int,
Reverse: reverse, Reverse: reverse,
}, nil) }, nil)
headers := unmarshalRlpHeaders(rlpHeaders) headers := unmarshalRlpHeaders(rlpHeaders)
// If a malicious peer is simulated withholding headers, delete them
for hash := range dlp.withholdHeaders {
for i, header := range headers {
if header.Hash() == hash {
headers = append(headers[:i], headers[i+1:]...)
break
}
}
}
hashes := make([]common.Hash, len(headers)) hashes := make([]common.Hash, len(headers))
for i, header := range headers { for i, header := range headers {
hashes[i] = header.Hash() hashes[i] = header.Hash()
@ -278,7 +239,13 @@ func (dlp *downloadTesterPeer) RequestBodies(hashes []common.Hash, sink chan *et
) )
hasher := trie.NewStackTrie(nil) hasher := trie.NewStackTrie(nil)
for i, body := range bodies { for i, body := range bodies {
txsHashes[i] = types.DeriveSha(types.Transactions(body.Transactions), hasher) hash := types.DeriveSha(types.Transactions(body.Transactions), hasher)
if _, ok := dlp.withholdBodies[hash]; ok {
txsHashes = append(txsHashes[:i], txsHashes[i+1:]...)
uncleHashes = append(uncleHashes[:i], uncleHashes[i+1:]...)
continue
}
txsHashes[i] = hash
uncleHashes[i] = types.CalcUncleHash(body.Uncles) uncleHashes[i] = types.CalcUncleHash(body.Uncles)
} }
req := &eth.Request{ req := &eth.Request{
@ -442,7 +409,10 @@ func TestCanonicalSynchronisation68Snap(t *testing.T) { testCanonSync(t, eth.ET
func TestCanonicalSynchronisation68Light(t *testing.T) { testCanonSync(t, eth.ETH68, LightSync) } func TestCanonicalSynchronisation68Light(t *testing.T) { testCanonSync(t, eth.ETH68, LightSync) }
func testCanonSync(t *testing.T, protocol uint, mode SyncMode) { func testCanonSync(t *testing.T, protocol uint, mode SyncMode) {
tester := newTester(t) success := make(chan struct{})
tester := newTesterWithNotification(t, func() {
close(success)
})
defer tester.terminate() defer tester.terminate()
// Create a small enough block chain to download // Create a small enough block chain to download
@ -450,10 +420,15 @@ func testCanonSync(t *testing.T, protocol uint, mode SyncMode) {
tester.newPeer("peer", protocol, chain.blocks[1:]) tester.newPeer("peer", protocol, chain.blocks[1:])
// Synchronise with the peer and make sure all relevant data was retrieved // Synchronise with the peer and make sure all relevant data was retrieved
if err := tester.sync("peer", nil, mode); err != nil { if err := tester.downloader.BeaconSync(mode, chain.blocks[len(chain.blocks)-1].Header(), nil); err != nil {
t.Fatalf("failed to synchronise blocks: %v", err) t.Fatalf("failed to beacon-sync chain: %v", err)
} }
select {
case <-success:
assertOwnChain(t, tester, len(chain.blocks)) assertOwnChain(t, tester, len(chain.blocks))
case <-time.NewTimer(time.Second * 3).C:
t.Fatalf("Failed to sync chain in three seconds")
}
} }
// Tests that if a large batch of blocks are being downloaded, it is throttled // Tests that if a large batch of blocks are being downloaded, it is throttled
@ -479,7 +454,7 @@ func testThrottling(t *testing.T, protocol uint, mode SyncMode) {
// Start a synchronisation concurrently // Start a synchronisation concurrently
errc := make(chan error, 1) errc := make(chan error, 1)
go func() { go func() {
errc <- tester.sync("peer", nil, mode) errc <- tester.downloader.BeaconSync(mode, testChainBase.blocks[len(testChainBase.blocks)-1].Header(), nil)
}() }()
// Iteratively take some blocks, always checking the retrieval count // Iteratively take some blocks, always checking the retrieval count
for { for {
@ -535,132 +510,17 @@ func testThrottling(t *testing.T, protocol uint, mode SyncMode) {
} }
} }
// Tests that simple synchronization against a forked chain works correctly. In
// this test common ancestor lookup should *not* be short circuited, and a full
// binary search should be executed.
func TestForkedSync68Full(t *testing.T) { testForkedSync(t, eth.ETH68, FullSync) }
func TestForkedSync68Snap(t *testing.T) { testForkedSync(t, eth.ETH68, SnapSync) }
func TestForkedSync68Light(t *testing.T) { testForkedSync(t, eth.ETH68, LightSync) }
func testForkedSync(t *testing.T, protocol uint, mode SyncMode) {
tester := newTester(t)
defer tester.terminate()
chainA := testChainForkLightA.shorten(len(testChainBase.blocks) + 80)
chainB := testChainForkLightB.shorten(len(testChainBase.blocks) + 81)
tester.newPeer("fork A", protocol, chainA.blocks[1:])
tester.newPeer("fork B", protocol, chainB.blocks[1:])
// Synchronise with the peer and make sure all blocks were retrieved
if err := tester.sync("fork A", nil, mode); err != nil {
t.Fatalf("failed to synchronise blocks: %v", err)
}
assertOwnChain(t, tester, len(chainA.blocks))
// Synchronise with the second peer and make sure that fork is pulled too
if err := tester.sync("fork B", nil, mode); err != nil {
t.Fatalf("failed to synchronise blocks: %v", err)
}
assertOwnChain(t, tester, len(chainB.blocks))
}
// Tests that synchronising against a much shorter but much heavier fork works
// currently and is not dropped.
func TestHeavyForkedSync68Full(t *testing.T) { testHeavyForkedSync(t, eth.ETH68, FullSync) }
func TestHeavyForkedSync68Snap(t *testing.T) { testHeavyForkedSync(t, eth.ETH68, SnapSync) }
func TestHeavyForkedSync68Light(t *testing.T) { testHeavyForkedSync(t, eth.ETH68, LightSync) }
func testHeavyForkedSync(t *testing.T, protocol uint, mode SyncMode) {
tester := newTester(t)
defer tester.terminate()
chainA := testChainForkLightA.shorten(len(testChainBase.blocks) + 80)
chainB := testChainForkHeavy.shorten(len(testChainBase.blocks) + 79)
tester.newPeer("light", protocol, chainA.blocks[1:])
tester.newPeer("heavy", protocol, chainB.blocks[1:])
// Synchronise with the peer and make sure all blocks were retrieved
if err := tester.sync("light", nil, mode); err != nil {
t.Fatalf("failed to synchronise blocks: %v", err)
}
assertOwnChain(t, tester, len(chainA.blocks))
// Synchronise with the second peer and make sure that fork is pulled too
if err := tester.sync("heavy", nil, mode); err != nil {
t.Fatalf("failed to synchronise blocks: %v", err)
}
assertOwnChain(t, tester, len(chainB.blocks))
}
// Tests that chain forks are contained within a certain interval of the current
// chain head, ensuring that malicious peers cannot waste resources by feeding
// long dead chains.
func TestBoundedForkedSync68Full(t *testing.T) { testBoundedForkedSync(t, eth.ETH68, FullSync) }
func TestBoundedForkedSync68Snap(t *testing.T) { testBoundedForkedSync(t, eth.ETH68, SnapSync) }
func TestBoundedForkedSync68Light(t *testing.T) { testBoundedForkedSync(t, eth.ETH68, LightSync) }
func testBoundedForkedSync(t *testing.T, protocol uint, mode SyncMode) {
tester := newTester(t)
defer tester.terminate()
chainA := testChainForkLightA
chainB := testChainForkLightB
tester.newPeer("original", protocol, chainA.blocks[1:])
tester.newPeer("rewriter", protocol, chainB.blocks[1:])
// Synchronise with the peer and make sure all blocks were retrieved
if err := tester.sync("original", nil, mode); err != nil {
t.Fatalf("failed to synchronise blocks: %v", err)
}
assertOwnChain(t, tester, len(chainA.blocks))
// Synchronise with the second peer and ensure that the fork is rejected to being too old
if err := tester.sync("rewriter", nil, mode); err != errInvalidAncestor {
t.Fatalf("sync failure mismatch: have %v, want %v", err, errInvalidAncestor)
}
}
// Tests that chain forks are contained within a certain interval of the current
// chain head for short but heavy forks too. These are a bit special because they
// take different ancestor lookup paths.
func TestBoundedHeavyForkedSync68Full(t *testing.T) {
testBoundedHeavyForkedSync(t, eth.ETH68, FullSync)
}
func TestBoundedHeavyForkedSync68Snap(t *testing.T) {
testBoundedHeavyForkedSync(t, eth.ETH68, SnapSync)
}
func TestBoundedHeavyForkedSync68Light(t *testing.T) {
testBoundedHeavyForkedSync(t, eth.ETH68, LightSync)
}
func testBoundedHeavyForkedSync(t *testing.T, protocol uint, mode SyncMode) {
tester := newTester(t)
defer tester.terminate()
// Create a long enough forked chain
chainA := testChainForkLightA
chainB := testChainForkHeavy
tester.newPeer("original", protocol, chainA.blocks[1:])
// Synchronise with the peer and make sure all blocks were retrieved
if err := tester.sync("original", nil, mode); err != nil {
t.Fatalf("failed to synchronise blocks: %v", err)
}
assertOwnChain(t, tester, len(chainA.blocks))
tester.newPeer("heavy-rewriter", protocol, chainB.blocks[1:])
// Synchronise with the second peer and ensure that the fork is rejected to being too old
if err := tester.sync("heavy-rewriter", nil, mode); err != errInvalidAncestor {
t.Fatalf("sync failure mismatch: have %v, want %v", err, errInvalidAncestor)
}
}
// Tests that a canceled download wipes all previously accumulated state. // Tests that a canceled download wipes all previously accumulated state.
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 testCancel(t *testing.T, protocol uint, mode SyncMode) { func testCancel(t *testing.T, protocol uint, mode SyncMode) {
tester := newTester(t) complete := make(chan struct{})
success := func() {
close(complete)
}
tester := newTesterWithNotification(t, success)
defer tester.terminate() defer tester.terminate()
chain := testChainBase.shorten(MaxHeaderFetch) chain := testChainBase.shorten(MaxHeaderFetch)
@ -672,38 +532,16 @@ func testCancel(t *testing.T, protocol uint, mode SyncMode) {
t.Errorf("download queue not idle") t.Errorf("download queue not idle")
} }
// Synchronise with the peer, but cancel afterwards // Synchronise with the peer, but cancel afterwards
if err := tester.sync("peer", nil, mode); err != nil { if err := tester.downloader.BeaconSync(mode, chain.blocks[len(chain.blocks)-1].Header(), nil); err != nil {
t.Fatalf("failed to synchronise blocks: %v", err) t.Fatalf("failed to synchronise blocks: %v", err)
} }
<-complete
tester.downloader.Cancel() tester.downloader.Cancel()
if !tester.downloader.queue.Idle() { if !tester.downloader.queue.Idle() {
t.Errorf("download queue not idle") t.Errorf("download queue not idle")
} }
} }
// Tests that synchronisation from multiple peers works as intended (multi thread sanity test).
func TestMultiSynchronisation68Full(t *testing.T) { testMultiSynchronisation(t, eth.ETH68, FullSync) }
func TestMultiSynchronisation68Snap(t *testing.T) { testMultiSynchronisation(t, eth.ETH68, SnapSync) }
func TestMultiSynchronisation68Light(t *testing.T) { testMultiSynchronisation(t, eth.ETH68, LightSync) }
func testMultiSynchronisation(t *testing.T, protocol uint, mode SyncMode) {
tester := newTester(t)
defer tester.terminate()
// Create various peers with various parts of the chain
targetPeers := 8
chain := testChainBase.shorten(targetPeers * 100)
for i := 0; i < targetPeers; i++ {
id := fmt.Sprintf("peer #%d", i)
tester.newPeer(id, protocol, chain.shorten(len(chain.blocks) / (i + 1)).blocks[1:])
}
if err := tester.sync("peer #0", nil, mode); err != nil {
t.Fatalf("failed to synchronise blocks: %v", err)
}
assertOwnChain(t, tester, len(chain.blocks))
}
// Tests that synchronisations behave well in multi-version protocol environments // Tests that synchronisations behave well in multi-version protocol environments
// and not wreak havoc on other nodes in the network. // and not wreak havoc on other nodes in the network.
func TestMultiProtoSynchronisation68Full(t *testing.T) { testMultiProtoSync(t, eth.ETH68, FullSync) } func TestMultiProtoSynchronisation68Full(t *testing.T) { testMultiProtoSync(t, eth.ETH68, FullSync) }
@ -711,7 +549,11 @@ func TestMultiProtoSynchronisation68Snap(t *testing.T) { testMultiProtoSync(t,
func TestMultiProtoSynchronisation68Light(t *testing.T) { testMultiProtoSync(t, eth.ETH68, LightSync) } func TestMultiProtoSynchronisation68Light(t *testing.T) { testMultiProtoSync(t, eth.ETH68, LightSync) }
func testMultiProtoSync(t *testing.T, protocol uint, mode SyncMode) { func testMultiProtoSync(t *testing.T, protocol uint, mode SyncMode) {
tester := newTester(t) complete := make(chan struct{})
success := func() {
close(complete)
}
tester := newTesterWithNotification(t, success)
defer tester.terminate() defer tester.terminate()
// Create a small enough block chain to download // Create a small enough block chain to download
@ -720,9 +562,14 @@ 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:])
// Synchronise with the requested peer and make sure all blocks were retrieved if err := tester.downloader.BeaconSync(mode, chain.blocks[len(chain.blocks)-1].Header(), nil); err != nil {
if err := tester.sync(fmt.Sprintf("peer %d", protocol), nil, mode); err != nil { t.Fatalf("failed to start beacon sync: #{err}")
t.Fatalf("failed to synchronise blocks: %v", err) }
select {
case <-complete:
break
case <-time.NewTimer(time.Second * 3).C:
t.Fatalf("Failed to sync chain in three seconds")
} }
assertOwnChain(t, tester, len(chain.blocks)) assertOwnChain(t, tester, len(chain.blocks))
@ -742,7 +589,10 @@ func TestEmptyShortCircuit68Snap(t *testing.T) { testEmptyShortCircuit(t, eth.E
func TestEmptyShortCircuit68Light(t *testing.T) { testEmptyShortCircuit(t, eth.ETH68, LightSync) } func TestEmptyShortCircuit68Light(t *testing.T) { testEmptyShortCircuit(t, eth.ETH68, LightSync) }
func testEmptyShortCircuit(t *testing.T, protocol uint, mode SyncMode) { func testEmptyShortCircuit(t *testing.T, protocol uint, mode SyncMode) {
tester := newTester(t) success := make(chan struct{})
tester := newTesterWithNotification(t, func() {
close(success)
})
defer tester.terminate() defer tester.terminate()
// Create a block chain to download // Create a block chain to download
@ -757,10 +607,19 @@ func testEmptyShortCircuit(t *testing.T, protocol uint, mode SyncMode) {
tester.downloader.receiptFetchHook = func(headers []*types.Header) { tester.downloader.receiptFetchHook = func(headers []*types.Header) {
receiptsHave.Add(int32(len(headers))) receiptsHave.Add(int32(len(headers)))
} }
// Synchronise with the peer and make sure all blocks were retrieved
if err := tester.sync("peer", nil, mode); err != nil { if err := tester.downloader.BeaconSync(mode, chain.blocks[len(chain.blocks)-1].Header(), nil); err != nil {
t.Fatalf("failed to synchronise blocks: %v", err) t.Fatalf("failed to synchronise blocks: %v", err)
} }
select {
case <-success:
checkProgress(t, tester.downloader, "initial", ethereum.SyncProgress{
HighestBlock: uint64(len(chain.blocks) - 1),
CurrentBlock: uint64(len(chain.blocks) - 1),
})
case <-time.NewTimer(time.Second * 3).C:
t.Fatalf("Failed to sync chain in three seconds")
}
assertOwnChain(t, tester, len(chain.blocks)) assertOwnChain(t, tester, len(chain.blocks))
// Validate the number of block bodies that should have been requested // Validate the number of block bodies that should have been requested
@ -783,195 +642,6 @@ func testEmptyShortCircuit(t *testing.T, protocol uint, mode SyncMode) {
} }
} }
// Tests that headers are enqueued continuously, preventing malicious nodes from
// stalling the downloader by feeding gapped header chains.
func TestMissingHeaderAttack68Full(t *testing.T) { testMissingHeaderAttack(t, eth.ETH68, FullSync) }
func TestMissingHeaderAttack68Snap(t *testing.T) { testMissingHeaderAttack(t, eth.ETH68, SnapSync) }
func TestMissingHeaderAttack68Light(t *testing.T) { testMissingHeaderAttack(t, eth.ETH68, LightSync) }
func testMissingHeaderAttack(t *testing.T, protocol uint, mode SyncMode) {
tester := newTester(t)
defer tester.terminate()
chain := testChainBase.shorten(blockCacheMaxItems - 15)
attacker := tester.newPeer("attack", protocol, chain.blocks[1:])
attacker.withholdHeaders[chain.blocks[len(chain.blocks)/2-1].Hash()] = struct{}{}
if err := tester.sync("attack", nil, mode); err == nil {
t.Fatalf("succeeded attacker synchronisation")
}
// Synchronise with the valid peer and make sure sync succeeds
tester.newPeer("valid", protocol, chain.blocks[1:])
if err := tester.sync("valid", nil, mode); err != nil {
t.Fatalf("failed to synchronise blocks: %v", err)
}
assertOwnChain(t, tester, len(chain.blocks))
}
// Tests that if requested headers are shifted (i.e. first is missing), the queue
// detects the invalid numbering.
func TestShiftedHeaderAttack68Full(t *testing.T) { testShiftedHeaderAttack(t, eth.ETH68, FullSync) }
func TestShiftedHeaderAttack68Snap(t *testing.T) { testShiftedHeaderAttack(t, eth.ETH68, SnapSync) }
func TestShiftedHeaderAttack68Light(t *testing.T) { testShiftedHeaderAttack(t, eth.ETH68, LightSync) }
func testShiftedHeaderAttack(t *testing.T, protocol uint, mode SyncMode) {
tester := newTester(t)
defer tester.terminate()
chain := testChainBase.shorten(blockCacheMaxItems - 15)
// Attempt a full sync with an attacker feeding shifted headers
attacker := tester.newPeer("attack", protocol, chain.blocks[1:])
attacker.withholdHeaders[chain.blocks[1].Hash()] = struct{}{}
if err := tester.sync("attack", nil, mode); err == nil {
t.Fatalf("succeeded attacker synchronisation")
}
// Synchronise with the valid peer and make sure sync succeeds
tester.newPeer("valid", protocol, chain.blocks[1:])
if err := tester.sync("valid", nil, mode); err != nil {
t.Fatalf("failed to synchronise blocks: %v", err)
}
assertOwnChain(t, tester, len(chain.blocks))
}
// Tests that a peer advertising a high TD doesn't get to stall the downloader
// afterwards by not sending any useful hashes.
func TestHighTDStarvationAttack68Full(t *testing.T) {
testHighTDStarvationAttack(t, eth.ETH68, FullSync)
}
func TestHighTDStarvationAttack68Snap(t *testing.T) {
testHighTDStarvationAttack(t, eth.ETH68, SnapSync)
}
func TestHighTDStarvationAttack68Light(t *testing.T) {
testHighTDStarvationAttack(t, eth.ETH68, LightSync)
}
func testHighTDStarvationAttack(t *testing.T, protocol uint, mode SyncMode) {
tester := newTester(t)
defer tester.terminate()
chain := testChainBase.shorten(1)
tester.newPeer("attack", protocol, chain.blocks[1:])
if err := tester.sync("attack", big.NewInt(1000000), mode); err != errStallingPeer {
t.Fatalf("synchronisation error mismatch: have %v, want %v", err, errStallingPeer)
}
}
// Tests that misbehaving peers are disconnected, whilst behaving ones are not.
func TestBlockHeaderAttackerDropping68(t *testing.T) { testBlockHeaderAttackerDropping(t, eth.ETH68) }
func testBlockHeaderAttackerDropping(t *testing.T, protocol uint) {
// Define the disconnection requirement for individual hash fetch errors
tests := []struct {
result error
drop bool
}{
{nil, false}, // Sync succeeded, all is well
{errBusy, false}, // Sync is already in progress, no problem
{errUnknownPeer, false}, // Peer is unknown, was already dropped, don't double drop
{errBadPeer, true}, // Peer was deemed bad for some reason, drop it
{errStallingPeer, true}, // Peer was detected to be stalling, drop it
{errUnsyncedPeer, true}, // Peer was detected to be unsynced, drop it
{errNoPeers, false}, // No peers to download from, soft race, no issue
{errTimeout, true}, // No hashes received in due time, drop the peer
{errEmptyHeaderSet, true}, // No headers were returned as a response, drop as it's a dead end
{errPeersUnavailable, true}, // Nobody had the advertised blocks, drop the advertiser
{errInvalidAncestor, true}, // Agreed upon ancestor is not acceptable, drop the chain rewriter
{errInvalidChain, true}, // Hash chain was detected as invalid, definitely drop
{errInvalidBody, false}, // A bad peer was detected, but not the sync origin
{errInvalidReceipt, false}, // A bad peer was detected, but not the sync origin
{errCancelContentProcessing, false}, // Synchronisation was canceled, origin may be innocent, don't drop
}
// Run the tests and check disconnection status
tester := newTester(t)
defer tester.terminate()
chain := testChainBase.shorten(1)
for i, tt := range tests {
// Register a new peer and ensure its presence
id := fmt.Sprintf("test %d", i)
tester.newPeer(id, protocol, chain.blocks[1:])
if _, ok := tester.peers[id]; !ok {
t.Fatalf("test %d: registered peer not found", i)
}
// Simulate a synchronisation and check the required result
tester.downloader.synchroniseMock = func(string, common.Hash) error { return tt.result }
tester.downloader.LegacySync(id, tester.chain.Genesis().Hash(), big.NewInt(1000), nil, FullSync)
if _, ok := tester.peers[id]; !ok != tt.drop {
t.Errorf("test %d: peer drop mismatch for %v: have %v, want %v", i, tt.result, !ok, tt.drop)
}
}
}
// Tests that synchronisation progress (origin block number, current block number
// and highest block number) is tracked and updated correctly.
func TestSyncProgress68Full(t *testing.T) { testSyncProgress(t, eth.ETH68, FullSync) }
func TestSyncProgress68Snap(t *testing.T) { testSyncProgress(t, eth.ETH68, SnapSync) }
func TestSyncProgress68Light(t *testing.T) { testSyncProgress(t, eth.ETH68, LightSync) }
func testSyncProgress(t *testing.T, protocol uint, mode SyncMode) {
tester := newTester(t)
defer tester.terminate()
chain := testChainBase.shorten(blockCacheMaxItems - 15)
// Set a sync init hook to catch progress changes
starting := make(chan struct{})
progress := make(chan struct{})
tester.downloader.syncInitHook = func(origin, latest uint64) {
starting <- struct{}{}
<-progress
}
checkProgress(t, tester.downloader, "pristine", ethereum.SyncProgress{})
// Synchronise half the blocks and check initial progress
tester.newPeer("peer-half", protocol, chain.shorten(len(chain.blocks) / 2).blocks[1:])
pending := new(sync.WaitGroup)
pending.Add(1)
go func() {
defer pending.Done()
if err := tester.sync("peer-half", nil, mode); err != nil {
panic(fmt.Sprintf("failed to synchronise blocks: %v", err))
}
}()
<-starting
checkProgress(t, tester.downloader, "initial", ethereum.SyncProgress{
HighestBlock: uint64(len(chain.blocks)/2 - 1),
})
progress <- struct{}{}
pending.Wait()
// Synchronise all the blocks and check continuation progress
tester.newPeer("peer-full", protocol, chain.blocks[1:])
pending.Add(1)
go func() {
defer pending.Done()
if err := tester.sync("peer-full", nil, mode); err != nil {
panic(fmt.Sprintf("failed to synchronise blocks: %v", err))
}
}()
<-starting
checkProgress(t, tester.downloader, "completing", ethereum.SyncProgress{
StartingBlock: uint64(len(chain.blocks)/2 - 1),
CurrentBlock: uint64(len(chain.blocks)/2 - 1),
HighestBlock: uint64(len(chain.blocks) - 1),
})
// Check final progress after successful sync
progress <- struct{}{}
pending.Wait()
checkProgress(t, tester.downloader, "final", ethereum.SyncProgress{
StartingBlock: uint64(len(chain.blocks)/2 - 1),
CurrentBlock: uint64(len(chain.blocks) - 1),
HighestBlock: uint64(len(chain.blocks) - 1),
})
}
func checkProgress(t *testing.T, d *Downloader, stage string, want ethereum.SyncProgress) { func checkProgress(t *testing.T, d *Downloader, stage string, want ethereum.SyncProgress) {
// Mark this method as a helper to report errors at callsite, not in here // Mark this method as a helper to report errors at callsite, not in here
t.Helper() t.Helper()
@ -982,296 +652,12 @@ func checkProgress(t *testing.T, d *Downloader, stage string, want ethereum.Sync
} }
} }
// Tests that synchronisation progress (origin block number and highest block
// number) is tracked and updated correctly in case of a fork (or manual head
// revertal).
func TestForkedSyncProgress68Full(t *testing.T) { testForkedSyncProgress(t, eth.ETH68, FullSync) }
func TestForkedSyncProgress68Snap(t *testing.T) { testForkedSyncProgress(t, eth.ETH68, SnapSync) }
func TestForkedSyncProgress68Light(t *testing.T) { testForkedSyncProgress(t, eth.ETH68, LightSync) }
func testForkedSyncProgress(t *testing.T, protocol uint, mode SyncMode) {
tester := newTester(t)
defer tester.terminate()
chainA := testChainForkLightA.shorten(len(testChainBase.blocks) + MaxHeaderFetch)
chainB := testChainForkLightB.shorten(len(testChainBase.blocks) + MaxHeaderFetch)
// Set a sync init hook to catch progress changes
starting := make(chan struct{})
progress := make(chan struct{})
tester.downloader.syncInitHook = func(origin, latest uint64) {
starting <- struct{}{}
<-progress
}
checkProgress(t, tester.downloader, "pristine", ethereum.SyncProgress{})
// Synchronise with one of the forks and check progress
tester.newPeer("fork A", protocol, chainA.blocks[1:])
pending := new(sync.WaitGroup)
pending.Add(1)
go func() {
defer pending.Done()
if err := tester.sync("fork A", nil, mode); err != nil {
panic(fmt.Sprintf("failed to synchronise blocks: %v", err))
}
}()
<-starting
checkProgress(t, tester.downloader, "initial", ethereum.SyncProgress{
HighestBlock: uint64(len(chainA.blocks) - 1),
})
progress <- struct{}{}
pending.Wait()
// Simulate a successful sync above the fork
tester.downloader.syncStatsChainOrigin = tester.downloader.syncStatsChainHeight
// Synchronise with the second fork and check progress resets
tester.newPeer("fork B", protocol, chainB.blocks[1:])
pending.Add(1)
go func() {
defer pending.Done()
if err := tester.sync("fork B", nil, mode); err != nil {
panic(fmt.Sprintf("failed to synchronise blocks: %v", err))
}
}()
<-starting
checkProgress(t, tester.downloader, "forking", ethereum.SyncProgress{
StartingBlock: uint64(len(testChainBase.blocks)) - 1,
CurrentBlock: uint64(len(chainA.blocks) - 1),
HighestBlock: uint64(len(chainB.blocks) - 1),
})
// Check final progress after successful sync
progress <- struct{}{}
pending.Wait()
checkProgress(t, tester.downloader, "final", ethereum.SyncProgress{
StartingBlock: uint64(len(testChainBase.blocks)) - 1,
CurrentBlock: uint64(len(chainB.blocks) - 1),
HighestBlock: uint64(len(chainB.blocks) - 1),
})
}
// Tests that if synchronisation is aborted due to some failure, then the progress
// origin is not updated in the next sync cycle, as it should be considered the
// continuation of the previous sync and not a new instance.
func TestFailedSyncProgress68Full(t *testing.T) { testFailedSyncProgress(t, eth.ETH68, FullSync) }
func TestFailedSyncProgress68Snap(t *testing.T) { testFailedSyncProgress(t, eth.ETH68, SnapSync) }
func TestFailedSyncProgress68Light(t *testing.T) { testFailedSyncProgress(t, eth.ETH68, LightSync) }
func testFailedSyncProgress(t *testing.T, protocol uint, mode SyncMode) {
tester := newTester(t)
defer tester.terminate()
chain := testChainBase.shorten(blockCacheMaxItems - 15)
// Set a sync init hook to catch progress changes
starting := make(chan struct{})
progress := make(chan struct{})
tester.downloader.syncInitHook = func(origin, latest uint64) {
starting <- struct{}{}
<-progress
}
checkProgress(t, tester.downloader, "pristine", ethereum.SyncProgress{})
// Attempt a full sync with a faulty peer
missing := len(chain.blocks)/2 - 1
faulter := tester.newPeer("faulty", protocol, chain.blocks[1:])
faulter.withholdHeaders[chain.blocks[missing].Hash()] = struct{}{}
pending := new(sync.WaitGroup)
pending.Add(1)
go func() {
defer pending.Done()
if err := tester.sync("faulty", nil, mode); err == nil {
panic("succeeded faulty synchronisation")
}
}()
<-starting
checkProgress(t, tester.downloader, "initial", ethereum.SyncProgress{
HighestBlock: uint64(len(chain.blocks) - 1),
})
progress <- struct{}{}
pending.Wait()
afterFailedSync := tester.downloader.Progress()
// Synchronise with a good peer and check that the progress origin remind the same
// after a failure
tester.newPeer("valid", protocol, chain.blocks[1:])
pending.Add(1)
go func() {
defer pending.Done()
if err := tester.sync("valid", nil, mode); err != nil {
panic(fmt.Sprintf("failed to synchronise blocks: %v", err))
}
}()
<-starting
checkProgress(t, tester.downloader, "completing", afterFailedSync)
// Check final progress after successful sync
progress <- struct{}{}
pending.Wait()
checkProgress(t, tester.downloader, "final", ethereum.SyncProgress{
CurrentBlock: uint64(len(chain.blocks) - 1),
HighestBlock: uint64(len(chain.blocks) - 1),
})
}
// Tests that if an attacker fakes a chain height, after the attack is detected,
// the progress height is successfully reduced at the next sync invocation.
func TestFakedSyncProgress68Full(t *testing.T) { testFakedSyncProgress(t, eth.ETH68, FullSync) }
func TestFakedSyncProgress68Snap(t *testing.T) { testFakedSyncProgress(t, eth.ETH68, SnapSync) }
func TestFakedSyncProgress68Light(t *testing.T) { testFakedSyncProgress(t, eth.ETH68, LightSync) }
func testFakedSyncProgress(t *testing.T, protocol uint, mode SyncMode) {
tester := newTester(t)
defer tester.terminate()
chain := testChainBase.shorten(blockCacheMaxItems - 15)
// Set a sync init hook to catch progress changes
starting := make(chan struct{})
progress := make(chan struct{})
tester.downloader.syncInitHook = func(origin, latest uint64) {
starting <- struct{}{}
<-progress
}
checkProgress(t, tester.downloader, "pristine", ethereum.SyncProgress{})
// Create and sync with an attacker that promises a higher chain than available.
attacker := tester.newPeer("attack", protocol, chain.blocks[1:])
numMissing := 5
for i := len(chain.blocks) - 2; i > len(chain.blocks)-numMissing; i-- {
attacker.withholdHeaders[chain.blocks[i].Hash()] = struct{}{}
}
pending := new(sync.WaitGroup)
pending.Add(1)
go func() {
defer pending.Done()
if err := tester.sync("attack", nil, mode); err == nil {
panic("succeeded attacker synchronisation")
}
}()
<-starting
checkProgress(t, tester.downloader, "initial", ethereum.SyncProgress{
HighestBlock: uint64(len(chain.blocks) - 1),
})
progress <- struct{}{}
pending.Wait()
afterFailedSync := tester.downloader.Progress()
// Synchronise with a good peer and check that the progress height has been reduced to
// the true value.
validChain := chain.shorten(len(chain.blocks) - numMissing)
tester.newPeer("valid", protocol, validChain.blocks[1:])
pending.Add(1)
go func() {
defer pending.Done()
if err := tester.sync("valid", nil, mode); err != nil {
panic(fmt.Sprintf("failed to synchronise blocks: %v", err))
}
}()
<-starting
checkProgress(t, tester.downloader, "completing", ethereum.SyncProgress{
CurrentBlock: afterFailedSync.CurrentBlock,
HighestBlock: uint64(len(validChain.blocks) - 1),
})
// Check final progress after successful sync.
progress <- struct{}{}
pending.Wait()
checkProgress(t, tester.downloader, "final", ethereum.SyncProgress{
CurrentBlock: uint64(len(validChain.blocks) - 1),
HighestBlock: uint64(len(validChain.blocks) - 1),
})
}
func TestRemoteHeaderRequestSpan(t *testing.T) {
testCases := []struct {
remoteHeight uint64
localHeight uint64
expected []int
}{
// Remote is way higher. We should ask for the remote head and go backwards
{1500, 1000,
[]int{1323, 1339, 1355, 1371, 1387, 1403, 1419, 1435, 1451, 1467, 1483, 1499},
},
{15000, 13006,
[]int{14823, 14839, 14855, 14871, 14887, 14903, 14919, 14935, 14951, 14967, 14983, 14999},
},
// Remote is pretty close to us. We don't have to fetch as many
{1200, 1150,
[]int{1149, 1154, 1159, 1164, 1169, 1174, 1179, 1184, 1189, 1194, 1199},
},
// Remote is equal to us (so on a fork with higher td)
// We should get the closest couple of ancestors
{1500, 1500,
[]int{1497, 1499},
},
// We're higher than the remote! Odd
{1000, 1500,
[]int{997, 999},
},
// Check some weird edgecases that it behaves somewhat rationally
{0, 1500,
[]int{0, 2},
},
{6000000, 0,
[]int{5999823, 5999839, 5999855, 5999871, 5999887, 5999903, 5999919, 5999935, 5999951, 5999967, 5999983, 5999999},
},
{0, 0,
[]int{0, 2},
},
}
reqs := func(from, count, span int) []int {
var r []int
num := from
for len(r) < count {
r = append(r, num)
num += span + 1
}
return r
}
for i, tt := range testCases {
from, count, span, max := calculateRequestSpan(tt.remoteHeight, tt.localHeight)
data := reqs(int(from), count, span)
if max != uint64(data[len(data)-1]) {
t.Errorf("test %d: wrong last value %d != %d", i, data[len(data)-1], max)
}
failed := false
if len(data) != len(tt.expected) {
failed = true
t.Errorf("test %d: length wrong, expected %d got %d", i, len(tt.expected), len(data))
} else {
for j, n := range data {
if n != tt.expected[j] {
failed = true
break
}
}
}
if failed {
res := strings.ReplaceAll(fmt.Sprint(data), " ", ",")
exp := strings.ReplaceAll(fmt.Sprint(tt.expected), " ", ",")
t.Logf("got: %v\n", res)
t.Logf("exp: %v\n", exp)
t.Errorf("test %d: wrong values", i)
}
}
}
// Tests that peers below a pre-configured checkpoint block are prevented from // Tests that peers below a pre-configured checkpoint block are prevented from
// 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 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))))
var cases = []struct { var cases = []struct {
name string // The name of testing scenario name string // The name of testing scenario
local int // The length of local chain(canonical chain assumed), 0 means genesis is the head local int // The length of local chain(canonical chain assumed), 0 means genesis is the head
@ -1312,81 +698,67 @@ func testBeaconSync(t *testing.T, protocol uint, mode SyncMode) {
} }
} }
// Tests that synchronisation progress (origin block number and highest block // Tests that synchronisation progress (origin block number, current block number
// number) is tracked and updated correctly in case of manual head reversion // and highest block number) is tracked and updated correctly.
func TestBeaconForkedSyncProgress68Full(t *testing.T) { func TestSyncProgress68Full(t *testing.T) { testSyncProgress(t, eth.ETH68, FullSync) }
testBeaconForkedSyncProgress(t, eth.ETH68, FullSync) func TestSyncProgress68Snap(t *testing.T) { testSyncProgress(t, eth.ETH68, SnapSync) }
} func TestSyncProgress68Light(t *testing.T) { testSyncProgress(t, eth.ETH68, LightSync) }
func TestBeaconForkedSyncProgress68Snap(t *testing.T) {
testBeaconForkedSyncProgress(t, eth.ETH68, SnapSync)
}
func TestBeaconForkedSyncProgress68Light(t *testing.T) {
testBeaconForkedSyncProgress(t, eth.ETH68, LightSync)
}
func testBeaconForkedSyncProgress(t *testing.T, protocol uint, mode SyncMode) { func testSyncProgress(t *testing.T, protocol uint, mode SyncMode) {
success := make(chan struct{}) success := make(chan struct{})
tester := newTesterWithNotification(t, func() { tester := newTesterWithNotification(t, func() {
success <- struct{}{} success <- struct{}{}
}) })
defer tester.terminate() defer tester.terminate()
chainA := testChainForkLightA.shorten(len(testChainBase.blocks) + MaxHeaderFetch)
chainB := testChainForkLightB.shorten(len(testChainBase.blocks) + MaxHeaderFetch)
// Set a sync init hook to catch progress changes
starting := make(chan struct{})
progress := make(chan struct{})
tester.downloader.syncInitHook = func(origin, latest uint64) {
starting <- struct{}{}
<-progress
}
checkProgress(t, tester.downloader, "pristine", ethereum.SyncProgress{}) checkProgress(t, tester.downloader, "pristine", ethereum.SyncProgress{})
// Synchronise with one of the forks and check progress chain := testChainBase.shorten(blockCacheMaxItems - 15)
tester.newPeer("fork A", protocol, chainA.blocks[1:]) shortChain := chain.shorten(len(chain.blocks) / 2).blocks[1:]
pending := new(sync.WaitGroup)
pending.Add(1)
go func() {
defer pending.Done()
if err := tester.downloader.BeaconSync(mode, chainA.blocks[len(chainA.blocks)-1].Header(), nil); err != nil {
panic(fmt.Sprintf("failed to beacon sync: %v", err))
}
}()
<-starting // Connect to peer that provides all headers and part of the bodies
progress <- struct{}{} faultyPeer := tester.newPeer("peer-half", protocol, shortChain)
for _, header := range shortChain {
faultyPeer.withholdBodies[header.Hash()] = struct{}{}
}
if err := tester.downloader.BeaconSync(mode, chain.blocks[len(chain.blocks)/2-1].Header(), nil); err != nil {
t.Fatalf("failed to beacon-sync chain: %v", err)
}
select { select {
case <-success: case <-success:
checkProgress(t, tester.downloader, "initial", ethereum.SyncProgress{ // Ok, downloader fully cancelled after sync cycle
HighestBlock: uint64(len(chainA.blocks) - 1), checkProgress(t, tester.downloader, "peer-half", ethereum.SyncProgress{
CurrentBlock: uint64(len(chainA.blocks) - 1), CurrentBlock: uint64(len(chain.blocks)/2 - 1),
HighestBlock: uint64(len(chain.blocks)/2 - 1),
}) })
case <-time.NewTimer(time.Second * 3).C: case <-time.NewTimer(time.Second * 3).C:
t.Fatalf("Failed to sync chain in three seconds") t.Fatalf("Failed to sync chain in three seconds")
} }
// Set the head to a second fork // Synchronise all the blocks and check continuation progress
tester.newPeer("fork B", protocol, chainB.blocks[1:]) tester.newPeer("peer-full", protocol, chain.blocks[1:])
pending.Add(1) if err := tester.downloader.BeaconSync(mode, chain.blocks[len(chain.blocks)-1].Header(), nil); err != nil {
go func() { t.Fatalf("failed to beacon-sync chain: %v", err)
defer pending.Done() }
if err := tester.downloader.BeaconSync(mode, chainB.blocks[len(chainB.blocks)-1].Header(), nil); err != nil { var startingBlock uint64
panic(fmt.Sprintf("failed to beacon sync: %v", err)) if mode == LightSync {
// in light-sync mode:
// * the starting block is 0 on the second sync cycle because blocks
// are never downloaded.
// * The current/highest blocks reported in the progress reflect the
// current/highest header.
startingBlock = 0
} else {
startingBlock = uint64(len(chain.blocks)/2 - 1)
} }
}()
<-starting
progress <- struct{}{}
// reorg below available state causes the state sync to rewind to genesis
select { select {
case <-success: case <-success:
checkProgress(t, tester.downloader, "initial", ethereum.SyncProgress{ // Ok, downloader fully cancelled after sync cycle
HighestBlock: uint64(len(chainB.blocks) - 1), checkProgress(t, tester.downloader, "peer-full", ethereum.SyncProgress{
CurrentBlock: uint64(len(chainB.blocks) - 1), StartingBlock: startingBlock,
StartingBlock: 0, CurrentBlock: uint64(len(chain.blocks) - 1),
HighestBlock: uint64(len(chain.blocks) - 1),
}) })
case <-time.NewTimer(time.Second * 3).C: case <-time.NewTimer(time.Second * 3).C:
t.Fatalf("Failed to sync chain in three seconds") t.Fatalf("Failed to sync chain in three seconds")

View file

@ -68,48 +68,3 @@ func (d *Downloader) fetchHeadersByHash(p *peerConnection, hash common.Hash, amo
return *res.Res.(*eth.BlockHeadersRequest), res.Meta.([]common.Hash), nil return *res.Res.(*eth.BlockHeadersRequest), res.Meta.([]common.Hash), nil
} }
} }
// fetchHeadersByNumber is a blocking version of Peer.RequestHeadersByNumber which
// handles all the cancellation, interruption and timeout mechanisms of a data
// retrieval to allow blocking API calls.
func (d *Downloader) fetchHeadersByNumber(p *peerConnection, number uint64, amount int, skip int, reverse bool) ([]*types.Header, []common.Hash, error) {
// Create the response sink and send the network request
start := time.Now()
resCh := make(chan *eth.Response)
req, err := p.peer.RequestHeadersByNumber(number, amount, skip, reverse, resCh)
if err != nil {
return nil, nil, err
}
defer req.Close()
// Wait until the response arrives, the request is cancelled or times out
ttl := d.peers.rates.TargetTimeout()
timeoutTimer := time.NewTimer(ttl)
defer timeoutTimer.Stop()
select {
case <-d.cancelCh:
return nil, nil, errCanceled
case <-timeoutTimer.C:
// Header retrieval timed out, update the metrics
p.log.Debug("Header request timed out", "elapsed", ttl)
headerTimeoutMeter.Mark(1)
return nil, nil, errTimeout
case res := <-resCh:
// Headers successfully retrieved, update the metrics
headerReqTimer.Update(time.Since(start))
headerInMeter.Mark(int64(len(*res.Res.(*eth.BlockHeadersRequest))))
// Don't reject the packet even if it turns out to be bad, downloader will
// disconnect the peer on its own terms. Simply delivery the headers to
// be processed by the caller
res.Done <- nil
return *res.Res.(*eth.BlockHeadersRequest), res.Meta.([]common.Hash), nil
}
}

View file

@ -76,7 +76,7 @@ type typedQueue interface {
// concurrentFetch iteratively downloads scheduled block parts, taking available // concurrentFetch iteratively downloads scheduled block parts, taking available
// peers, reserving a chunk of fetch requests for each and waiting for delivery // peers, reserving a chunk of fetch requests for each and waiting for delivery
// or timeouts. // or timeouts.
func (d *Downloader) concurrentFetch(queue typedQueue, beaconMode bool) error { func (d *Downloader) concurrentFetch(queue typedQueue) error {
// Create a delivery channel to accept responses from all peers // Create a delivery channel to accept responses from all peers
responses := make(chan *eth.Response) responses := make(chan *eth.Response)
@ -126,10 +126,6 @@ func (d *Downloader) concurrentFetch(queue typedQueue, beaconMode bool) error {
// Prepare the queue and fetch block parts until the block header fetcher's done // Prepare the queue and fetch block parts until the block header fetcher's done
finished := false finished := false
for { for {
// Short circuit if we lost all our peers
if d.peers.Len() == 0 && !beaconMode {
return errNoPeers
}
// If there's nothing more to fetch, wait or terminate // If there's nothing more to fetch, wait or terminate
if queue.pending() == 0 { if queue.pending() == 0 {
if len(pending) == 0 && finished { if len(pending) == 0 && finished {
@ -158,27 +154,20 @@ func (d *Downloader) concurrentFetch(queue typedQueue, beaconMode bool) error {
} }
sort.Sort(&peerCapacitySort{idles, caps}) sort.Sort(&peerCapacitySort{idles, caps})
var ( var throttled bool
progressed bool
throttled bool
queued = queue.pending()
)
for _, peer := range idles { for _, peer := range idles {
// Short circuit if throttling activated or there are no more // Short circuit if throttling activated or there are no more
// queued tasks to be retrieved // queued tasks to be retrieved
if throttled { if throttled {
break break
} }
if queued = queue.pending(); queued == 0 { if queued := queue.pending(); queued == 0 {
break break
} }
// Reserve a chunk of fetches for a peer. A nil can mean either that // Reserve a chunk of fetches for a peer. A nil can mean either that
// no more headers are available, or that the peer is known not to // no more headers are available, or that the peer is known not to
// have them. // have them.
request, progress, throttle := queue.reserve(peer, queue.capacity(peer, d.peers.rates.TargetRoundTrip())) request, _, throttle := queue.reserve(peer, queue.capacity(peer, d.peers.rates.TargetRoundTrip()))
if progress {
progressed = true
}
if throttle { if throttle {
throttled = true throttled = true
throttleCounter.Inc(1) throttleCounter.Inc(1)
@ -207,11 +196,6 @@ func (d *Downloader) concurrentFetch(queue typedQueue, beaconMode bool) error {
timeout.Reset(ttl) timeout.Reset(ttl)
} }
} }
// Make sure that we have peers available for fetching. If all peers have been tried
// and all failed throw an error
if !progressed && !throttled && len(pending) == 0 && len(idles) == d.peers.Len() && queued > 0 && !beaconMode {
return errPeersUnavailable
}
} }
// Wait for something to happen // Wait for something to happen
select { select {
@ -315,16 +299,6 @@ func (d *Downloader) concurrentFetch(queue typedQueue, beaconMode bool) error {
queue.updateCapacity(peer, 0, 0) queue.updateCapacity(peer, 0, 0)
} else { } else {
d.dropPeer(peer.id) d.dropPeer(peer.id)
// If this peer was the master peer, abort sync immediately
d.cancelLock.RLock()
master := peer.id == d.cancelPeer
d.cancelLock.RUnlock()
if master {
d.cancel()
return errTimeout
}
} }
case res := <-responses: case res := <-responses:

View file

@ -78,7 +78,6 @@ func (q *bodyQueue) request(peer *peerConnection, req *fetchRequest, resCh chan
if q.bodyFetchHook != nil { if q.bodyFetchHook != nil {
q.bodyFetchHook(req.Headers) q.bodyFetchHook(req.Headers)
} }
hashes := make([]common.Hash, 0, len(req.Headers)) hashes := make([]common.Hash, 0, len(req.Headers))
for _, header := range req.Headers { for _, header := range req.Headers {
hashes = append(hashes, header.Hash()) hashes = append(hashes, header.Hash())

View file

@ -1,97 +0,0 @@
// Copyright 2021 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 downloader
import (
"time"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/eth/protocols/eth"
"github.com/ethereum/go-ethereum/log"
)
// headerQueue implements typedQueue and is a type adapter between the generic
// concurrent fetcher and the downloader.
type headerQueue Downloader
// waker returns a notification channel that gets pinged in case more header
// fetches have been queued up, so the fetcher might assign it to idle peers.
func (q *headerQueue) waker() chan bool {
return q.queue.headerContCh
}
// pending returns the number of headers that are currently queued for fetching
// by the concurrent downloader.
func (q *headerQueue) pending() int {
return q.queue.PendingHeaders()
}
// capacity is responsible for calculating how many headers a particular peer is
// estimated to be able to retrieve within the allotted round trip time.
func (q *headerQueue) capacity(peer *peerConnection, rtt time.Duration) int {
return peer.HeaderCapacity(rtt)
}
// updateCapacity is responsible for updating how many headers a particular peer
// is estimated to be able to retrieve in a unit time.
func (q *headerQueue) updateCapacity(peer *peerConnection, items int, span time.Duration) {
peer.UpdateHeaderRate(items, span)
}
// reserve is responsible for allocating a requested number of pending headers
// from the download queue to the specified peer.
func (q *headerQueue) reserve(peer *peerConnection, items int) (*fetchRequest, bool, bool) {
return q.queue.ReserveHeaders(peer, items), false, false
}
// unreserve is responsible for removing the current header retrieval allocation
// assigned to a specific peer and placing it back into the pool to allow
// reassigning to some other peer.
func (q *headerQueue) unreserve(peer string) int {
fails := q.queue.ExpireHeaders(peer)
if fails > 2 {
log.Trace("Header delivery timed out", "peer", peer)
} else {
log.Debug("Header delivery stalling", "peer", peer)
}
return fails
}
// request is responsible for converting a generic fetch request into a header
// one and sending it to the remote peer for fulfillment.
func (q *headerQueue) request(peer *peerConnection, req *fetchRequest, resCh chan *eth.Response) (*eth.Request, error) {
peer.log.Trace("Requesting new batch of headers", "from", req.From)
return peer.peer.RequestHeadersByNumber(req.From, MaxHeaderFetch, 0, false, resCh)
}
// deliver is responsible for taking a generic response packet from the concurrent
// fetcher, unpacking the header data and delivering it to the downloader's queue.
func (q *headerQueue) deliver(peer *peerConnection, packet *eth.Response) (int, error) {
headers := *packet.Res.(*eth.BlockHeadersRequest)
hashes := packet.Meta.([]common.Hash)
accepted, err := q.queue.DeliverHeaders(peer.id, headers, hashes, q.headerProcCh)
switch {
case err == nil && len(headers) == 0:
peer.log.Trace("Requested headers delivered")
case err == nil:
peer.log.Trace("Delivered new batch of headers", "count", len(headers), "accepted", accepted)
default:
peer.log.Debug("Failed to deliver retrieved headers", "err", err)
}
return accepted, err
}

View file

@ -87,6 +87,15 @@ func newFetchResult(header *types.Header, fastSync bool) *fetchResult {
return item return item
} }
// body returns a representation of the fetch result as a types.Body object.
func (f *fetchResult) body() types.Body {
return types.Body{
Transactions: f.Transactions,
Uncles: f.Uncles,
Withdrawals: f.Withdrawals,
}
}
// SetBodyDone flags the body as finished. // SetBodyDone flags the body as finished.
func (f *fetchResult) SetBodyDone() { func (f *fetchResult) SetBodyDone() {
if v := f.pending.Load(); (v & (1 << bodyType)) != 0 { if v := f.pending.Load(); (v & (1 << bodyType)) != 0 {

View file

@ -58,7 +58,6 @@ var pregenerated bool
func init() { func init() {
// Reduce some of the parameters to make the tester faster // Reduce some of the parameters to make the tester faster
fullMaxForkAncestry = 10000 fullMaxForkAncestry = 10000
lightMaxForkAncestry = 10000
blockCacheMaxItems = 1024 blockCacheMaxItems = 1024
fsHeaderSafetyNet = 256 fsHeaderSafetyNet = 256
fsHeaderContCheck = 500 * time.Millisecond fsHeaderContCheck = 500 * time.Millisecond

View file

@ -42,7 +42,6 @@ import (
"github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/p2p"
"github.com/ethereum/go-ethereum/p2p/enode" "github.com/ethereum/go-ethereum/p2p/enode"
"github.com/ethereum/go-ethereum/triedb/pathdb" "github.com/ethereum/go-ethereum/triedb/pathdb"
"golang.org/x/crypto/sha3"
) )
const ( const (
@ -480,7 +479,7 @@ func (h *handler) BroadcastTransactions(txs types.Transactions) {
var ( var (
signer = types.LatestSignerForChainID(h.chain.Config().ChainID) // Don't care about chain status, we just need *a* sender signer = types.LatestSignerForChainID(h.chain.Config().ChainID) // Don't care about chain status, we just need *a* sender
hasher = sha3.NewLegacyKeccak256().(crypto.KeccakState) hasher = crypto.NewKeccakState()
hash = make([]byte, 32) hash = make([]byte, 32)
) )
for _, tx := range txs { for _, tx := range txs {

View file

@ -164,7 +164,7 @@ func (t *pathTrie) deleteAccountNode(path []byte, inner bool) {
} else { } else {
accountOuterLookupGauge.Inc(1) accountOuterLookupGauge.Inc(1)
} }
if !rawdb.ExistsAccountTrieNode(t.db, path) { if !rawdb.HasAccountTrieNode(t.db, path) {
return return
} }
if inner { if inner {
@ -181,7 +181,7 @@ func (t *pathTrie) deleteStorageNode(path []byte, inner bool) {
} else { } else {
storageOuterLookupGauge.Inc(1) storageOuterLookupGauge.Inc(1)
} }
if !rawdb.ExistsStorageTrieNode(t.db, t.owner, path) { if !rawdb.HasStorageTrieNode(t.db, t.owner, path) {
return return
} }
if inner { if inner {

View file

@ -42,7 +42,6 @@ import (
"github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/trie" "github.com/ethereum/go-ethereum/trie"
"github.com/ethereum/go-ethereum/trie/trienode" "github.com/ethereum/go-ethereum/trie/trienode"
"golang.org/x/crypto/sha3"
) )
const ( const (
@ -2653,7 +2652,7 @@ func (s *Syncer) onByteCodes(peer SyncPeer, id uint64, bytecodes [][]byte) error
// Cross reference the requested bytecodes with the response to find gaps // Cross reference the requested bytecodes with the response to find gaps
// that the serving node is missing // that the serving node is missing
hasher := sha3.NewLegacyKeccak256().(crypto.KeccakState) hasher := crypto.NewKeccakState()
hash := make([]byte, 32) hash := make([]byte, 32)
codes := make([][]byte, len(req.hashes)) codes := make([][]byte, len(req.hashes))
@ -2901,7 +2900,7 @@ func (s *Syncer) OnTrieNodes(peer SyncPeer, id uint64, trienodes [][]byte) error
// Cross reference the requested trienodes with the response to find gaps // Cross reference the requested trienodes with the response to find gaps
// that the serving node is missing // that the serving node is missing
var ( var (
hasher = sha3.NewLegacyKeccak256().(crypto.KeccakState) hasher = crypto.NewKeccakState()
hash = make([]byte, 32) hash = make([]byte, 32)
nodes = make([][]byte, len(req.hashes)) nodes = make([][]byte, len(req.hashes))
fills uint64 fills uint64
@ -3007,7 +3006,7 @@ func (s *Syncer) onHealByteCodes(peer SyncPeer, id uint64, bytecodes [][]byte) e
// Cross reference the requested bytecodes with the response to find gaps // Cross reference the requested bytecodes with the response to find gaps
// that the serving node is missing // that the serving node is missing
hasher := sha3.NewLegacyKeccak256().(crypto.KeccakState) hasher := crypto.NewKeccakState()
hash := make([]byte, 32) hash := make([]byte, 32)
codes := make([][]byte, len(req.hashes)) codes := make([][]byte, len(req.hashes))

View file

@ -64,7 +64,7 @@ func TestHashing(t *testing.T) {
} }
} }
var new = func() { var new = func() {
hasher := sha3.NewLegacyKeccak256().(crypto.KeccakState) hasher := crypto.NewKeccakState()
var hash = make([]byte, 32) var hash = make([]byte, 32)
for i := 0; i < len(bytecodes); i++ { for i := 0; i < len(bytecodes); i++ {
hasher.Reset() hasher.Reset()
@ -96,7 +96,7 @@ func BenchmarkHashing(b *testing.B) {
} }
} }
var new = func() { var new = func() {
hasher := sha3.NewLegacyKeccak256().(crypto.KeccakState) hasher := crypto.NewKeccakState()
var hash = make([]byte, 32) var hash = make([]byte, 32)
for i := 0; i < len(bytecodes); i++ { for i := 0; i < len(bytecodes); i++ {
hasher.Reset() hasher.Reset()

View file

@ -23,6 +23,7 @@ import (
"math/big" "math/big"
"slices" "slices"
"strings" "strings"
"sync/atomic"
"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"
@ -114,7 +115,7 @@ type flatCallTracer struct {
tracer *callTracer tracer *callTracer
config flatCallTracerConfig config flatCallTracerConfig
ctx *tracers.Context // Holds tracer context data ctx *tracers.Context // Holds tracer context data
reason error // Textual reason for the interruption interrupt atomic.Bool // Atomic flag to signal execution interruption
activePrecompiles []common.Address // Updated on tx start based on given rules activePrecompiles []common.Address // Updated on tx start based on given rules
} }
@ -154,6 +155,9 @@ func newFlatCallTracer(ctx *tracers.Context, cfg json.RawMessage) (*tracers.Trac
// OnEnter is called when EVM enters a new scope (via call, create or selfdestruct). // OnEnter is called when EVM enters a new scope (via call, create or selfdestruct).
func (t *flatCallTracer) OnEnter(depth int, typ byte, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) { func (t *flatCallTracer) OnEnter(depth int, typ byte, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) {
if t.interrupt.Load() {
return
}
t.tracer.OnEnter(depth, typ, from, to, input, gas, value) t.tracer.OnEnter(depth, typ, from, to, input, gas, value)
if depth == 0 { if depth == 0 {
@ -169,6 +173,9 @@ func (t *flatCallTracer) OnEnter(depth int, typ byte, from common.Address, to co
// OnExit is called when EVM exits a scope, even if the scope didn't // OnExit is called when EVM exits a scope, even if the scope didn't
// execute any code. // execute any code.
func (t *flatCallTracer) OnExit(depth int, output []byte, gasUsed uint64, err error, reverted bool) { func (t *flatCallTracer) OnExit(depth int, output []byte, gasUsed uint64, err error, reverted bool) {
if t.interrupt.Load() {
return
}
t.tracer.OnExit(depth, output, gasUsed, err, reverted) t.tracer.OnExit(depth, output, gasUsed, err, reverted)
if depth == 0 { if depth == 0 {
@ -194,6 +201,9 @@ func (t *flatCallTracer) OnExit(depth int, output []byte, gasUsed uint64, err er
} }
func (t *flatCallTracer) OnTxStart(env *tracing.VMContext, tx *types.Transaction, from common.Address) { func (t *flatCallTracer) OnTxStart(env *tracing.VMContext, tx *types.Transaction, from common.Address) {
if t.interrupt.Load() {
return
}
t.tracer.OnTxStart(env, tx, from) t.tracer.OnTxStart(env, tx, from)
// Update list of precompiles based on current block // Update list of precompiles based on current block
rules := env.ChainConfig.Rules(env.BlockNumber, env.Random != nil, env.Time) rules := env.ChainConfig.Rules(env.BlockNumber, env.Random != nil, env.Time)
@ -201,6 +211,9 @@ func (t *flatCallTracer) OnTxStart(env *tracing.VMContext, tx *types.Transaction
} }
func (t *flatCallTracer) OnTxEnd(receipt *types.Receipt, err error) { func (t *flatCallTracer) OnTxEnd(receipt *types.Receipt, err error) {
if t.interrupt.Load() {
return
}
t.tracer.OnTxEnd(receipt, err) t.tracer.OnTxEnd(receipt, err)
} }
@ -219,12 +232,13 @@ func (t *flatCallTracer) GetResult() (json.RawMessage, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
return res, t.reason return res, t.tracer.reason
} }
// Stop terminates execution of the tracer at the first opportune moment. // Stop terminates execution of the tracer at the first opportune moment.
func (t *flatCallTracer) Stop(err error) { func (t *flatCallTracer) Stop(err error) {
t.tracer.Stop(err) t.tracer.Stop(err)
t.interrupt.Store(true)
} }
// isPrecompiled returns whether the addr is a precompile. // isPrecompiled returns whether the addr is a precompile.

View file

@ -0,0 +1,64 @@
// 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 native_test
import (
"errors"
"math/big"
"testing"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/tracing"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/eth/tracers"
"github.com/ethereum/go-ethereum/params"
"github.com/stretchr/testify/require"
)
func TestCallFlatStop(t *testing.T) {
tracer, err := tracers.DefaultDirectory.New("flatCallTracer", &tracers.Context{}, nil)
require.NoError(t, err)
// this error should be returned by GetResult
stopError := errors.New("stop error")
// simulate a transaction
tx := types.NewTx(&types.LegacyTx{
Nonce: 0,
To: &common.Address{},
Value: big.NewInt(0),
Gas: 0,
GasPrice: big.NewInt(0),
Data: nil,
})
tracer.OnTxStart(&tracing.VMContext{
ChainConfig: params.MainnetChainConfig,
}, tx, common.Address{})
tracer.OnEnter(0, byte(vm.CALL), common.Address{}, common.Address{}, nil, 0, big.NewInt(0))
// stop before the transaction is finished
tracer.Stop(stopError)
tracer.OnTxEnd(&types.Receipt{GasUsed: 0}, nil)
// check that the error is returned by GetResult
_, tracerError := tracer.GetResult()
require.Equal(t, stopError, tracerError)
}

View file

@ -191,7 +191,12 @@ func (ec *Client) getBlock(ctx context.Context, method string, args ...interface
} }
txs[i] = tx.tx txs[i] = tx.tx
} }
return types.NewBlockWithHeader(head).WithBody(txs, uncles).WithWithdrawals(body.Withdrawals), nil return types.NewBlockWithHeader(head).WithBody(
types.Body{
Transactions: txs,
Uncles: uncles,
Withdrawals: body.Withdrawals,
}), nil
} }
// HeaderByHash returns the block header with the given hash. // HeaderByHash returns the block header with the given hash.

View file

@ -88,8 +88,8 @@ type AncientReaderOp interface {
// Ancients returns the ancient item numbers in the ancient store. // Ancients returns the ancient item numbers in the ancient store.
Ancients() (uint64, error) Ancients() (uint64, error)
// Tail returns the number of first stored item in the freezer. // Tail returns the number of first stored item in the ancient store.
// This number can also be interpreted as the total deleted item numbers. // This number can also be interpreted as the total deleted items.
Tail() (uint64, error) Tail() (uint64, error)
// AncientSize returns the ancient size of the specified category. // AncientSize returns the ancient size of the specified category.
@ -101,7 +101,7 @@ type AncientReader interface {
AncientReaderOp AncientReaderOp
// ReadAncients runs the given read operation while ensuring that no writes take place // ReadAncients runs the given read operation while ensuring that no writes take place
// on the underlying freezer. // on the underlying ancient store.
ReadAncients(fn func(AncientReaderOp) error) (err error) ReadAncients(fn func(AncientReaderOp) error) (err error)
} }
@ -141,11 +141,15 @@ type AncientWriteOp interface {
AppendRaw(kind string, number uint64, item []byte) error AppendRaw(kind string, number uint64, item []byte) error
} }
// AncientStater wraps the Stat method of a backing data store. // AncientStater wraps the Stat method of a backing ancient store.
type AncientStater interface { type AncientStater interface {
// AncientDatadir returns the path of root ancient directory. Empty string // AncientDatadir returns the path of the ancient store directory.
// will be returned if ancient store is not enabled at all. The returned //
// path can be used to construct the path of other freezers. // If the ancient store is not activated, an error is returned.
// If an ephemeral ancient store is used, an empty path is returned.
//
// The path returned by AncientDatadir can be used as the root path
// of the ancient store to construct paths for other sub ancient stores.
AncientDatadir() (string, error) AncientDatadir() (string, error)
} }
@ -171,15 +175,23 @@ type Stater interface {
} }
// AncientStore contains all the methods required to allow handling different // AncientStore contains all the methods required to allow handling different
// ancient data stores backing immutable chain data store. // ancient data stores backing immutable data store.
type AncientStore interface { type AncientStore interface {
AncientReader AncientReader
AncientWriter AncientWriter
io.Closer io.Closer
} }
// ResettableAncientStore extends the AncientStore interface by adding a Reset method.
type ResettableAncientStore interface {
AncientStore
// Reset is designed to reset the entire ancient store to its default state.
Reset() error
}
// Database contains all the methods required by the high level database to not // Database contains all the methods required by the high level database to not
// only access the key-value data store but also the chain freezer. // only access the key-value data store but also the ancient chain store.
type Database interface { type Database interface {
Reader Reader
Writer Writer

View file

@ -151,7 +151,7 @@ func (e *Era) GetBlockByNumber(num uint64) (*types.Block, error) {
if err := rlp.Decode(r, &body); err != nil { if err := rlp.Decode(r, &body); err != nil {
return nil, err return nil, err
} }
return types.NewBlockWithHeader(&header).WithBody(body.Transactions, body.Uncles), nil return types.NewBlockWithHeader(&header).WithBody(body), nil
} }
// Accumulator reads the accumulator entry in the Era1 file. // Accumulator reads the accumulator entry in the Era1 file.

View file

@ -73,7 +73,7 @@ func (it *Iterator) Block() (*types.Block, error) {
if err := rlp.Decode(it.inner.Body, &body); err != nil { if err := rlp.Decode(it.inner.Body, &body); err != nil {
return nil, err return nil, err
} }
return types.NewBlockWithHeader(&header).WithBody(body.Transactions, body.Uncles), nil return types.NewBlockWithHeader(&header).WithBody(body), nil
} }
// Receipts returns the receipts for the iterator's current position. // Receipts returns the receipts for the iterator's current position.

View file

@ -1524,6 +1524,9 @@ func AccessList(ctx context.Context, b Backend, blockNrOrHash rpc.BlockNumberOrH
prevTracer = logger.NewAccessListTracer(*args.AccessList, args.from(), to, precompiles) prevTracer = logger.NewAccessListTracer(*args.AccessList, args.from(), to, precompiles)
} }
for { for {
if err := ctx.Err(); err != nil {
return nil, 0, nil, err
}
// Retrieve the current access list to expand // Retrieve the current access list to expand
accessList := prevTracer.AccessList() accessList := prevTracer.AccessList()
log.Trace("Creating access list", "input", accessList) log.Trace("Creating access list", "input", accessList)

View file

@ -1348,7 +1348,7 @@ func TestRPCMarshalBlock(t *testing.T) {
} }
txs = append(txs, tx) txs = append(txs, tx)
} }
block := types.NewBlock(&types.Header{Number: big.NewInt(100)}, txs, nil, nil, blocktest.NewHasher()) block := types.NewBlock(&types.Header{Number: big.NewInt(100)}, &types.Body{Transactions: txs}, nil, blocktest.NewHasher())
var testSuite = []struct { var testSuite = []struct {
inclTx bool inclTx bool
@ -1559,7 +1559,7 @@ func TestRPCGetBlockOrHeader(t *testing.T) {
Address: common.Address{0x12, 0x34}, Address: common.Address{0x12, 0x34},
Amount: 10, Amount: 10,
} }
pending = types.NewBlockWithWithdrawals(&types.Header{Number: big.NewInt(11), Time: 42}, []*types.Transaction{tx}, nil, nil, []*types.Withdrawal{withdrawal}, blocktest.NewHasher()) pending = types.NewBlock(&types.Header{Number: big.NewInt(11), Time: 42}, &types.Body{Transactions: types.Transactions{tx}, Withdrawals: types.Withdrawals{withdrawal}}, nil, blocktest.NewHasher())
) )
backend := newTestBackend(t, genBlocks, genesis, ethash.NewFaker(), func(i int, b *core.BlockGen) { backend := newTestBackend(t, genBlocks, genesis, ethash.NewFaker(), func(i int, b *core.BlockGen) {
// Transfer from account[0] to account[1] // Transfer from account[0] to account[1]

View file

@ -78,7 +78,7 @@ func (bc *testBlockChain) CurrentBlock() *types.Header {
} }
func (bc *testBlockChain) GetBlock(hash common.Hash, number uint64) *types.Block { func (bc *testBlockChain) GetBlock(hash common.Hash, number uint64) *types.Block {
return types.NewBlock(bc.CurrentBlock(), nil, nil, nil, trie.NewStackTrie(nil)) return types.NewBlock(bc.CurrentBlock(), nil, nil, trie.NewStackTrie(nil))
} }
func (bc *testBlockChain) StateAt(common.Hash) (*state.StateDB, error) { func (bc *testBlockChain) StateAt(common.Hash) (*state.StateDB, error) {

View file

@ -34,6 +34,7 @@ import (
"github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/ethdb/memorydb"
"github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/event"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/p2p"
@ -752,7 +753,7 @@ func (n *Node) OpenDatabaseWithFreezer(name string, cache, handles int, ancient
var db ethdb.Database var db ethdb.Database
var err error var err error
if n.config.DataDir == "" { if n.config.DataDir == "" {
db = rawdb.NewMemoryDatabase() db, err = rawdb.NewDatabaseWithFreezer(memorydb.New(), "", namespace, readonly)
} else { } else {
db, err = rawdb.Open(rawdb.OpenOptions{ db, err = rawdb.Open(rawdb.OpenOptions{
Type: n.config.DBEngine, Type: n.config.DBEngine,

View file

@ -20,6 +20,7 @@ import (
"context" "context"
"errors" "errors"
"fmt" "fmt"
"maps"
"math" "math"
"net" "net"
"sync" "sync"
@ -215,10 +216,7 @@ func (sn *SimNode) ServeRPC(conn *websocket.Conn) error {
// simulation_snapshot RPC method // simulation_snapshot RPC method
func (sn *SimNode) Snapshots() (map[string][]byte, error) { func (sn *SimNode) Snapshots() (map[string][]byte, error) {
sn.lock.RLock() sn.lock.RLock()
services := make(map[string]node.Lifecycle, len(sn.running)) services := maps.Clone(sn.running)
for name, service := range sn.running {
services[name] = service
}
sn.lock.RUnlock() sn.lock.RUnlock()
if len(services) == 0 { if len(services) == 0 {
return nil, errors.New("no running services") return nil, errors.New("no running services")
@ -315,11 +313,7 @@ func (sn *SimNode) Services() []node.Lifecycle {
func (sn *SimNode) ServiceMap() map[string]node.Lifecycle { func (sn *SimNode) ServiceMap() map[string]node.Lifecycle {
sn.lock.RLock() sn.lock.RLock()
defer sn.lock.RUnlock() defer sn.lock.RUnlock()
services := make(map[string]node.Lifecycle, len(sn.running)) return maps.Clone(sn.running)
for name, service := range sn.running {
services[name] = service
}
return services
} }
// Server returns the underlying p2p.Server // Server returns the underlying p2p.Server

View file

@ -566,17 +566,17 @@ func (c *ChainConfig) IsShanghai(num *big.Int, time uint64) bool {
return c.IsLondon(num) && isTimestampForked(c.ShanghaiTime, time) return c.IsLondon(num) && isTimestampForked(c.ShanghaiTime, time)
} }
// IsCancun returns whether num is either equal to the Cancun fork time or greater. // IsCancun returns whether time is either equal to the Cancun fork time or greater.
func (c *ChainConfig) IsCancun(num *big.Int, time uint64) bool { func (c *ChainConfig) IsCancun(num *big.Int, time uint64) bool {
return c.IsLondon(num) && isTimestampForked(c.CancunTime, time) return c.IsLondon(num) && isTimestampForked(c.CancunTime, time)
} }
// IsPrague returns whether num is either equal to the Prague fork time or greater. // IsPrague returns whether time is either equal to the Prague fork time or greater.
func (c *ChainConfig) IsPrague(num *big.Int, time uint64) bool { func (c *ChainConfig) IsPrague(num *big.Int, time uint64) bool {
return c.IsLondon(num) && isTimestampForked(c.PragueTime, time) return c.IsLondon(num) && isTimestampForked(c.PragueTime, time)
} }
// IsVerkle returns whether num is either equal to the Verkle fork time or greater. // IsVerkle returns whether time is either equal to the Verkle fork time or greater.
func (c *ChainConfig) IsVerkle(num *big.Int, time uint64) bool { func (c *ChainConfig) IsVerkle(num *big.Int, time uint64) bool {
return c.IsLondon(num) && isTimestampForked(c.VerkleTime, time) return c.IsLondon(num) && isTimestampForked(c.VerkleTime, time)
} }
@ -880,7 +880,7 @@ func newTimestampCompatError(what string, storedtime, newtime *uint64) *ConfigCo
NewTime: newtime, NewTime: newtime,
RewindToTime: 0, RewindToTime: 0,
} }
if rew != nil { if rew != nil && *rew != 0 {
err.RewindToTime = *rew - 1 err.RewindToTime = *rew - 1
} }
return err return err
@ -890,7 +890,15 @@ func (err *ConfigCompatError) Error() string {
if err.StoredBlock != nil { if err.StoredBlock != nil {
return fmt.Sprintf("mismatching %s in database (have block %d, want block %d, rewindto block %d)", err.What, err.StoredBlock, err.NewBlock, err.RewindToBlock) return fmt.Sprintf("mismatching %s in database (have block %d, want block %d, rewindto block %d)", err.What, err.StoredBlock, err.NewBlock, err.RewindToBlock)
} }
return fmt.Sprintf("mismatching %s in database (have timestamp %d, want timestamp %d, rewindto timestamp %d)", err.What, err.StoredTime, err.NewTime, err.RewindToTime)
if err.StoredTime == nil && err.NewTime == nil {
return ""
} else if err.StoredTime == nil && err.NewTime != nil {
return fmt.Sprintf("mismatching %s in database (have timestamp nil, want timestamp %d, rewindto timestamp %d)", err.What, *err.NewTime, err.RewindToTime)
} else if err.StoredTime != nil && err.NewTime == nil {
return fmt.Sprintf("mismatching %s in database (have timestamp %d, want timestamp nil, rewindto timestamp %d)", err.What, *err.StoredTime, err.RewindToTime)
}
return fmt.Sprintf("mismatching %s in database (have timestamp %d, want timestamp %d, rewindto timestamp %d)", err.What, *err.StoredTime, *err.NewTime, err.RewindToTime)
} }
// Rules wraps ChainConfig and is merely syntactic sugar or can be used for functions // Rules wraps ChainConfig and is merely syntactic sugar or can be used for functions

View file

@ -23,6 +23,7 @@ import (
"time" "time"
"github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/common/math"
"github.com/stretchr/testify/require"
) )
func TestCheckCompatible(t *testing.T) { func TestCheckCompatible(t *testing.T) {
@ -137,3 +138,20 @@ func TestConfigRules(t *testing.T) {
t.Errorf("expected %v to be shanghai", stamp) t.Errorf("expected %v to be shanghai", stamp)
} }
} }
func TestTimestampCompatError(t *testing.T) {
require.Equal(t, new(ConfigCompatError).Error(), "")
errWhat := "Shanghai fork timestamp"
require.Equal(t, newTimestampCompatError(errWhat, nil, newUint64(1681338455)).Error(),
"mismatching Shanghai fork timestamp in database (have timestamp nil, want timestamp 1681338455, rewindto timestamp 1681338454)")
require.Equal(t, newTimestampCompatError(errWhat, newUint64(1681338455), nil).Error(),
"mismatching Shanghai fork timestamp in database (have timestamp 1681338455, want timestamp nil, rewindto timestamp 1681338454)")
require.Equal(t, newTimestampCompatError(errWhat, newUint64(1681338455), newUint64(600624000)).Error(),
"mismatching Shanghai fork timestamp in database (have timestamp 1681338455, want timestamp 600624000, rewindto timestamp 600623999)")
require.Equal(t, newTimestampCompatError(errWhat, newUint64(0), newUint64(1681338455)).Error(),
"mismatching Shanghai fork timestamp in database (have timestamp 0, want timestamp 1681338455, rewindto timestamp 0)")
}

View file

@ -21,7 +21,6 @@ import (
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rlp"
"golang.org/x/crypto/sha3"
) )
// hasher is a type used for the trie Hash operation. A hasher has some // hasher is a type used for the trie Hash operation. A hasher has some
@ -38,7 +37,7 @@ var hasherPool = sync.Pool{
New: func() interface{} { New: func() interface{} {
return &hasher{ return &hasher{
tmp: make([]byte, 0, 550), // cap is as large as a full fullNode. tmp: make([]byte, 0, 550), // cap is as large as a full fullNode.
sha: sha3.NewLegacyKeccak256().(crypto.KeccakState), sha: crypto.NewKeccakState(),
encbuf: rlp.NewEncoderBuffer(nil), encbuf: rlp.NewEncoderBuffer(nil),
} }
}, },

View file

@ -28,7 +28,6 @@ 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/trie/trienode" "github.com/ethereum/go-ethereum/trie/trienode"
"golang.org/x/crypto/sha3"
) )
func FuzzStackTrie(f *testing.F) { func FuzzStackTrie(f *testing.F) {
@ -41,10 +40,10 @@ func fuzz(data []byte, debugging bool) {
// This spongeDb is used to check the sequence of disk-db-writes // This spongeDb is used to check the sequence of disk-db-writes
var ( var (
input = bytes.NewReader(data) input = bytes.NewReader(data)
spongeA = &spongeDb{sponge: sha3.NewLegacyKeccak256()} spongeA = &spongeDb{sponge: crypto.NewKeccakState()}
dbA = newTestDatabase(rawdb.NewDatabase(spongeA), rawdb.HashScheme) dbA = newTestDatabase(rawdb.NewDatabase(spongeA), rawdb.HashScheme)
trieA = NewEmpty(dbA) trieA = NewEmpty(dbA)
spongeB = &spongeDb{sponge: sha3.NewLegacyKeccak256()} spongeB = &spongeDb{sponge: crypto.NewKeccakState()}
dbB = newTestDatabase(rawdb.NewDatabase(spongeB), rawdb.HashScheme) dbB = newTestDatabase(rawdb.NewDatabase(spongeB), rawdb.HashScheme)
trieB = NewStackTrie(func(path []byte, hash common.Hash, blob []byte) { trieB = NewStackTrie(func(path []byte, hash common.Hash, blob []byte) {
rawdb.WriteTrieNode(spongeB, common.Hash{}, path, hash, blob, dbB.Scheme()) rawdb.WriteTrieNode(spongeB, common.Hash{}, path, hash, blob, dbB.Scheme())

View file

@ -25,6 +25,7 @@ import (
"github.com/ethereum/go-ethereum/common/prque" "github.com/ethereum/go-ethereum/common/prque"
"github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/rawdb"
"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/ethdb" "github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/metrics" "github.com/ethereum/go-ethereum/metrics"
@ -546,9 +547,9 @@ func (s *Sync) children(req *nodeRequest, object node) ([]*nodeRequest, error) {
// the performance impact negligible. // the performance impact negligible.
var exists bool var exists bool
if owner == (common.Hash{}) { if owner == (common.Hash{}) {
exists = rawdb.ExistsAccountTrieNode(s.database, append(inner, key[:i]...)) exists = rawdb.HasAccountTrieNode(s.database, append(inner, key[:i]...))
} else { } else {
exists = rawdb.ExistsStorageTrieNode(s.database, owner, append(inner, key[:i]...)) exists = rawdb.HasStorageTrieNode(s.database, owner, append(inner, key[:i]...))
} }
if exists { if exists {
s.membatch.delNode(owner, append(inner, key[:i]...)) s.membatch.delNode(owner, append(inner, key[:i]...))
@ -691,13 +692,14 @@ func (s *Sync) hasNode(owner common.Hash, path []byte, hash common.Hash) (exists
} }
// If node is running with path scheme, check the presence with node path. // If node is running with path scheme, check the presence with node path.
var blob []byte var blob []byte
var dbHash common.Hash
if owner == (common.Hash{}) { if owner == (common.Hash{}) {
blob, dbHash = rawdb.ReadAccountTrieNode(s.database, path) blob = rawdb.ReadAccountTrieNode(s.database, path)
} else { } else {
blob, dbHash = rawdb.ReadStorageTrieNode(s.database, owner, path) blob = rawdb.ReadStorageTrieNode(s.database, owner, path)
} }
exists = hash == dbHash h := newBlobHasher()
defer h.release()
exists = hash == h.hash(blob)
inconsistent = !exists && len(blob) != 0 inconsistent = !exists && len(blob) != 0
return exists, inconsistent return exists, inconsistent
} }
@ -712,3 +714,23 @@ func ResolvePath(path []byte) (common.Hash, []byte) {
} }
return owner, path return owner, path
} }
// blobHasher is used to compute the sha256 hash of the provided data.
type blobHasher struct{ state crypto.KeccakState }
// blobHasherPool is the pool for reusing pre-allocated hash state.
var blobHasherPool = sync.Pool{
New: func() interface{} { return &blobHasher{state: crypto.NewKeccakState()} },
}
func newBlobHasher() *blobHasher {
return blobHasherPool.Get().(*blobHasher)
}
func (h *blobHasher) hash(data []byte) common.Hash {
return crypto.HashData(h.state, data)
}
func (h *blobHasher) release() {
blobHasherPool.Put(h)
}

View file

@ -886,7 +886,7 @@ func TestCommitSequence(t *testing.T) {
} { } {
addresses, accounts := makeAccounts(tc.count) addresses, accounts := makeAccounts(tc.count)
// This spongeDb is used to check the sequence of disk-db-writes // This spongeDb is used to check the sequence of disk-db-writes
s := &spongeDb{sponge: sha3.NewLegacyKeccak256()} s := &spongeDb{sponge: crypto.NewKeccakState()}
db := newTestDatabase(rawdb.NewDatabase(s), rawdb.HashScheme) db := newTestDatabase(rawdb.NewDatabase(s), rawdb.HashScheme)
trie := NewEmpty(db) trie := NewEmpty(db)
// Fill the trie with elements // Fill the trie with elements
@ -917,7 +917,7 @@ func TestCommitSequenceRandomBlobs(t *testing.T) {
} { } {
prng := rand.New(rand.NewSource(int64(i))) prng := rand.New(rand.NewSource(int64(i)))
// This spongeDb is used to check the sequence of disk-db-writes // This spongeDb is used to check the sequence of disk-db-writes
s := &spongeDb{sponge: sha3.NewLegacyKeccak256()} s := &spongeDb{sponge: crypto.NewKeccakState()}
db := newTestDatabase(rawdb.NewDatabase(s), rawdb.HashScheme) db := newTestDatabase(rawdb.NewDatabase(s), rawdb.HashScheme)
trie := NewEmpty(db) trie := NewEmpty(db)
// Fill the trie with elements // Fill the trie with elements

View file

@ -114,7 +114,12 @@ func (set *NodeSet) Merge(owner common.Hash, nodes map[string]*Node) error {
set.updates -= 1 set.updates -= 1
} }
} }
set.AddNode([]byte(path), node) if node.IsDeleted() {
set.deletes += 1
} else {
set.updates += 1
}
set.Nodes[path] = node
} }
return nil return nil
} }
@ -130,16 +135,6 @@ func (set *NodeSet) Size() (int, int) {
return set.updates, set.deletes return set.updates, set.deletes
} }
// Hashes returns the hashes of all updated nodes. TODO(rjl493456442) how can
// we get rid of it?
func (set *NodeSet) Hashes() []common.Hash {
ret := make([]common.Hash, 0, len(set.Nodes))
for _, node := range set.Nodes {
ret = append(ret, node.Hash)
}
return ret
}
// Summary returns a string-representation of the NodeSet. // Summary returns a string-representation of the NodeSet.
func (set *NodeSet) Summary() string { func (set *NodeSet) Summary() string {
var out = new(strings.Builder) var out = new(strings.Builder)

View file

@ -0,0 +1,61 @@
// Copyright 2023 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 trienode
import (
"crypto/rand"
"testing"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
)
func BenchmarkMerge(b *testing.B) {
b.Run("1K", func(b *testing.B) {
benchmarkMerge(b, 1000)
})
b.Run("10K", func(b *testing.B) {
benchmarkMerge(b, 10_000)
})
}
func benchmarkMerge(b *testing.B, count int) {
x := NewNodeSet(common.Hash{})
y := NewNodeSet(common.Hash{})
addNode := func(s *NodeSet) {
path := make([]byte, 4)
rand.Read(path)
blob := make([]byte, 32)
rand.Read(blob)
hash := crypto.Keccak256Hash(blob)
s.AddNode(path, New(hash, blob))
}
for i := 0; i < count; i++ {
// Random path of 4 nibbles
addNode(x)
addNode(y)
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
// Store set x into a backup
z := NewNodeSet(common.Hash{})
z.Merge(common.Hash{}, x.Nodes)
// Merge y into x
x.Merge(common.Hash{}, y.Nodes)
x = z
}
}

View file

@ -26,7 +26,6 @@ import (
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/trie/trienode" "github.com/ethereum/go-ethereum/trie/trienode"
"golang.org/x/crypto/sha3"
) )
// Trie is an Ethereum state trie, can be implemented by Ethereum Merkle Patricia // Trie is an Ethereum state trie, can be implemented by Ethereum Merkle Patricia
@ -257,7 +256,7 @@ func deleteAccount(ctx *context, loader TrieLoader, addr common.Address) error {
type hasher struct{ sha crypto.KeccakState } type hasher struct{ sha crypto.KeccakState }
var hasherPool = sync.Pool{ var hasherPool = sync.Pool{
New: func() interface{} { return &hasher{sha: sha3.NewLegacyKeccak256().(crypto.KeccakState)} }, New: func() interface{} { return &hasher{sha: crypto.NewKeccakState()} },
} }
func newHasher() *hasher { func newHasher() *hasher {

View file

@ -20,6 +20,7 @@ import (
"errors" "errors"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/trie" "github.com/ethereum/go-ethereum/trie"
@ -48,9 +49,6 @@ var HashDefaults = &Config{
// backend defines the methods needed to access/update trie nodes in different // backend defines the methods needed to access/update trie nodes in different
// state scheme. // state scheme.
type backend interface { type backend interface {
// Scheme returns the identifier of used storage scheme.
Scheme() string
// Initialized returns an indicator if the state data is already initialized // Initialized returns an indicator if the state data is already initialized
// according to the state scheme. // according to the state scheme.
Initialized(genesisRoot common.Hash) bool Initialized(genesisRoot common.Hash) bool
@ -181,7 +179,10 @@ func (db *Database) Initialized(genesisRoot common.Hash) bool {
// Scheme returns the node scheme used in the database. // Scheme returns the node scheme used in the database.
func (db *Database) Scheme() string { func (db *Database) Scheme() string {
return db.backend.Scheme() if db.config.PathDB != nil {
return rawdb.PathScheme
}
return rawdb.HashScheme
} }
// Close flushes the dangling preimages to disk and closes the trie database. // Close flushes the dangling preimages to disk and closes the trie database.

View file

@ -25,6 +25,9 @@ type Reader interface {
// Node retrieves the trie node blob with the provided trie identifier, // Node retrieves the trie node blob with the provided trie identifier,
// node path and the corresponding node hash. No error will be returned // node path and the corresponding node hash. No error will be returned
// if the node is not found. // if the node is not found.
//
// Don't modify the returned byte slice since it's not deep-copied and
// still be referenced by database.
Node(owner common.Hash, path []byte, hash common.Hash) ([]byte, error) Node(owner common.Hash, path []byte, hash common.Hash) ([]byte, error)
} }

View file

@ -623,11 +623,6 @@ func (db *Database) Close() error {
return nil return nil
} }
// Scheme returns the node scheme used in the database.
func (db *Database) Scheme() string {
return rawdb.HashScheme
}
// Reader retrieves a node reader belonging to the given state root. // Reader retrieves a node reader belonging to the given state root.
// An error will be returned if the requested state is not available. // An error will be returned if the requested state is not available.
func (db *Database) Reader(root common.Hash) (*reader, error) { func (db *Database) Reader(root common.Hash) (*reader, error) {

View file

@ -26,6 +26,7 @@ import (
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/rawdb"
"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/ethdb" "github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
@ -138,7 +139,7 @@ type Database struct {
config *Config // Configuration for database config *Config // Configuration for database
diskdb ethdb.Database // Persistent storage for matured trie nodes diskdb ethdb.Database // Persistent storage for matured trie nodes
tree *layerTree // The group for all known layers tree *layerTree // The group for all known layers
freezer *rawdb.ResettableFreezer // Freezer for storing trie histories, nil possible in tests freezer ethdb.ResettableAncientStore // Freezer for storing trie histories, nil possible in tests
lock sync.RWMutex // Lock to prevent mutations from happening at the same time lock sync.RWMutex // Lock to prevent mutations from happening at the same time
} }
@ -162,23 +163,44 @@ func New(diskdb ethdb.Database, config *Config, isVerkle bool) *Database {
// and in-memory layer journal. // and in-memory layer journal.
db.tree = newLayerTree(db.loadLayers()) db.tree = newLayerTree(db.loadLayers())
// Open the freezer for state history if the passed database contains an // Repair the state history, which might not be aligned with the state
// ancient store. Otherwise, all the relevant functionalities are disabled. // in the key-value store due to an unclean shutdown.
// if err := db.repairHistory(); err != nil {
// Because the freezer can only be opened once at the same time, this log.Crit("Failed to repair pathdb", "err", err)
// mechanism also ensures that at most one **non-readOnly** database }
// is opened at the same time to prevent accidental mutation. // Disable database in case node is still in the initial state sync stage.
if ancient, err := diskdb.AncientDatadir(); err == nil && ancient != "" && !db.readOnly { if rawdb.ReadSnapSyncStatusFlag(diskdb) == rawdb.StateSyncRunning && !db.readOnly {
if err := db.Disable(); err != nil {
log.Crit("Failed to disable database", "err", err) // impossible to happen
}
}
return db
}
// repairHistory truncates leftover state history objects, which may occur due
// to an unclean shutdown or other unexpected reasons.
func (db *Database) repairHistory() error {
// Open the freezer for state history. This mechanism ensures that
// only one database instance can be opened at a time to prevent
// accidental mutation.
ancient, err := db.diskdb.AncientDatadir()
if err != nil {
// TODO error out if ancient store is disabled. A tons of unit tests
// disable the ancient store thus the error here will immediately fail
// all of them. Fix the tests first.
return nil
}
freezer, err := rawdb.NewStateFreezer(ancient, false) freezer, err := rawdb.NewStateFreezer(ancient, false)
if err != nil { if err != nil {
log.Crit("Failed to open state history freezer", "err", err) log.Crit("Failed to open state history freezer", "err", err)
} }
db.freezer = freezer db.freezer = freezer
diskLayerID := db.tree.bottom().stateID() // Reset the entire state histories if the trie database is not initialized
if diskLayerID == 0 { // yet. This action is necessary because these state histories are not
// Reset the entire state histories in case the trie database is // expected to exist without an initialized trie database.
// not initialized yet, as these state histories are not expected. id := db.tree.bottom().stateID()
if id == 0 {
frozen, err := db.freezer.Ancients() frozen, err := db.freezer.Ancients()
if err != nil { if err != nil {
log.Crit("Failed to retrieve head of state history", "err", err) log.Crit("Failed to retrieve head of state history", "err", err)
@ -190,25 +212,18 @@ func New(diskdb ethdb.Database, config *Config, isVerkle bool) *Database {
} }
log.Info("Truncated extraneous state history") log.Info("Truncated extraneous state history")
} }
} else { return nil
// Truncate the extra state histories above in freezer in case }
// it's not aligned with the disk layer. // Truncate the extra state histories above in freezer in case it's not
pruned, err := truncateFromHead(db.diskdb, freezer, diskLayerID) // aligned with the disk layer. It might happen after a unclean shutdown.
pruned, err := truncateFromHead(db.diskdb, db.freezer, id)
if err != nil { if err != nil {
log.Crit("Failed to truncate extra state histories", "err", err) log.Crit("Failed to truncate extra state histories", "err", err)
} }
if pruned != 0 { if pruned != 0 {
log.Warn("Truncated extra state histories", "number", pruned) log.Warn("Truncated extra state histories", "number", pruned)
} }
} return nil
}
// Disable database in case node is still in the initial state sync stage.
if rawdb.ReadSnapSyncStatusFlag(diskdb) == rawdb.StateSyncRunning && !db.readOnly {
if err := db.Disable(); err != nil {
log.Crit("Failed to disable database", "err", err) // impossible to happen
}
}
return db
} }
// Update adds a new layer into the tree, if that can be linked to an existing // Update adds a new layer into the tree, if that can be linked to an existing
@ -292,8 +307,10 @@ func (db *Database) Enable(root common.Hash) error {
} }
// Ensure the provided state root matches the stored one. // Ensure the provided state root matches the stored one.
root = types.TrieRootHash(root) root = types.TrieRootHash(root)
_, stored := rawdb.ReadAccountTrieNode(db.diskdb, nil) stored := types.EmptyRootHash
stored = types.TrieRootHash(stored) if blob := rawdb.ReadAccountTrieNode(db.diskdb, nil); len(blob) > 0 {
stored = crypto.Keccak256Hash(blob)
}
if stored != root { if stored != root {
return fmt.Errorf("state root mismatch: stored %x, synced %x", stored, root) return fmt.Errorf("state root mismatch: stored %x, synced %x", stored, root)
} }
@ -466,11 +483,6 @@ func (db *Database) SetBufferSize(size int) error {
return db.tree.bottom().setBufferSize(db.bufferSize) return db.tree.bottom().setBufferSize(db.bufferSize)
} }
// Scheme returns the node scheme used in the database.
func (db *Database) Scheme() string {
return rawdb.PathScheme
}
// modifyAllowed returns the indicator if mutation is allowed. This function // modifyAllowed returns the indicator if mutation is allowed. This function
// assumes the db.lock is already held. // assumes the db.lock is already held.
func (db *Database) modifyAllowed() error { func (db *Database) modifyAllowed() error {

View file

@ -474,7 +474,7 @@ func TestDisable(t *testing.T) {
tester := newTester(t, 0) tester := newTester(t, 0)
defer tester.release() defer tester.release()
_, stored := rawdb.ReadAccountTrieNode(tester.db.diskdb, nil) stored := crypto.Keccak256Hash(rawdb.ReadAccountTrieNode(tester.db.diskdb, nil))
if err := tester.db.Disable(); err != nil { if err := tester.db.Disable(); err != nil {
t.Fatalf("Failed to deactivate database: %v", err) t.Fatalf("Failed to deactivate database: %v", err)
} }
@ -580,7 +580,7 @@ func TestCorruptedJournal(t *testing.T) {
t.Errorf("Failed to journal, err: %v", err) t.Errorf("Failed to journal, err: %v", err)
} }
tester.db.Close() tester.db.Close()
_, root := rawdb.ReadAccountTrieNode(tester.db.diskdb, nil) root := crypto.Keccak256Hash(rawdb.ReadAccountTrieNode(tester.db.diskdb, nil))
// Mutate the journal in disk, it should be regarded as invalid // Mutate the journal in disk, it should be regarded as invalid
blob := rawdb.ReadTrieJournal(tester.db.diskdb) blob := rawdb.ReadTrieJournal(tester.db.diskdb)

View file

@ -70,10 +70,10 @@ func benchmarkSearch(b *testing.B, depth int, total int) {
blob = testrand.Bytes(100) blob = testrand.Bytes(100)
node = trienode.New(crypto.Keccak256Hash(blob), blob) node = trienode.New(crypto.Keccak256Hash(blob), blob)
) )
nodes[common.Hash{}][string(path)] = trienode.New(node.Hash, node.Blob) nodes[common.Hash{}][string(path)] = node
if npath == nil && depth == index { if npath == nil && depth == index {
npath = common.CopyBytes(path) npath = common.CopyBytes(path)
nblob = common.CopyBytes(node.Blob) nblob = common.CopyBytes(blob)
} }
} }
return newDiffLayer(parent, common.Hash{}, 0, 0, nodes, nil) return newDiffLayer(parent, common.Hash{}, 0, 0, nodes, nil)
@ -116,7 +116,7 @@ func BenchmarkPersist(b *testing.B) {
blob = testrand.Bytes(100) blob = testrand.Bytes(100)
node = trienode.New(crypto.Keccak256Hash(blob), blob) node = trienode.New(crypto.Keccak256Hash(blob), blob)
) )
nodes[common.Hash{}][string(path)] = trienode.New(node.Hash, node.Blob) nodes[common.Hash{}][string(path)] = node
} }
return newDiffLayer(parent, common.Hash{}, 0, 0, nodes, nil) return newDiffLayer(parent, common.Hash{}, 0, 0, nodes, nil)
} }
@ -154,7 +154,7 @@ func BenchmarkJournal(b *testing.B) {
blob = testrand.Bytes(100) blob = testrand.Bytes(100)
node = trienode.New(crypto.Keccak256Hash(blob), blob) node = trienode.New(crypto.Keccak256Hash(blob), blob)
) )
nodes[common.Hash{}][string(path)] = trienode.New(node.Hash, node.Blob) nodes[common.Hash{}][string(path)] = node
} }
// TODO(rjl493456442) a non-nil state set is expected. // TODO(rjl493456442) a non-nil state set is expected.
return newDiffLayer(parent, common.Hash{}, 0, 0, nodes, nil) return newDiffLayer(parent, common.Hash{}, 0, 0, nodes, nil)

View file

@ -27,7 +27,6 @@ import (
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/trie/trienode" "github.com/ethereum/go-ethereum/trie/trienode"
"github.com/ethereum/go-ethereum/trie/triestate" "github.com/ethereum/go-ethereum/trie/triestate"
"golang.org/x/crypto/sha3"
) )
// diskLayer is a low level persistent layer built on top of a key-value store. // diskLayer is a low level persistent layer built on top of a key-value store.
@ -117,12 +116,12 @@ func (dl *diskLayer) node(owner common.Hash, path []byte, depth int) ([]byte, co
dirtyMissMeter.Mark(1) dirtyMissMeter.Mark(1)
// Try to retrieve the trie node from the clean memory cache // Try to retrieve the trie node from the clean memory cache
key := cacheKey(owner, path)
if dl.cleans != nil {
if blob := dl.cleans.Get(nil, key); len(blob) > 0 {
h := newHasher() h := newHasher()
defer h.release() defer h.release()
key := cacheKey(owner, path)
if dl.cleans != nil {
if blob := dl.cleans.Get(nil, key); len(blob) > 0 {
cleanHitMeter.Mark(1) cleanHitMeter.Mark(1)
cleanReadMeter.Mark(int64(len(blob))) cleanReadMeter.Mark(int64(len(blob)))
return blob, h.hash(blob), &nodeLoc{loc: locCleanCache, depth: depth}, nil return blob, h.hash(blob), &nodeLoc{loc: locCleanCache, depth: depth}, nil
@ -130,20 +129,18 @@ func (dl *diskLayer) node(owner common.Hash, path []byte, depth int) ([]byte, co
cleanMissMeter.Mark(1) cleanMissMeter.Mark(1)
} }
// Try to retrieve the trie node from the disk. // Try to retrieve the trie node from the disk.
var ( var blob []byte
nBlob []byte
nHash common.Hash
)
if owner == (common.Hash{}) { if owner == (common.Hash{}) {
nBlob, nHash = rawdb.ReadAccountTrieNode(dl.db.diskdb, path) blob = rawdb.ReadAccountTrieNode(dl.db.diskdb, path)
} else { } else {
nBlob, nHash = rawdb.ReadStorageTrieNode(dl.db.diskdb, owner, path) blob = rawdb.ReadStorageTrieNode(dl.db.diskdb, owner, path)
} }
if dl.cleans != nil && len(nBlob) > 0 { if dl.cleans != nil && len(blob) > 0 {
dl.cleans.Set(key, nBlob) dl.cleans.Set(key, blob)
cleanWriteMeter.Mark(int64(len(nBlob))) cleanWriteMeter.Mark(int64(len(blob)))
} }
return nBlob, nHash, &nodeLoc{loc: locDiskLayer, depth: depth}, nil
return blob, h.hash(blob), &nodeLoc{loc: locDiskLayer, depth: depth}, nil
} }
// update implements the layer interface, returning a new diff layer on top // update implements the layer interface, returning a new diff layer on top
@ -303,7 +300,7 @@ func (dl *diskLayer) resetCache() {
type hasher struct{ sha crypto.KeccakState } type hasher struct{ sha crypto.KeccakState }
var hasherPool = sync.Pool{ var hasherPool = sync.Pool{
New: func() interface{} { return &hasher{sha: sha3.NewLegacyKeccak256().(crypto.KeccakState)} }, New: func() interface{} { return &hasher{sha: crypto.NewKeccakState()} },
} }
func newHasher() *hasher { func newHasher() *hasher {

View file

@ -472,8 +472,8 @@ func (h *history) decode(accountData, storageData, accountIndexes, storageIndexe
} }
// readHistory reads and decodes the state history object by the given id. // readHistory reads and decodes the state history object by the given id.
func readHistory(freezer *rawdb.ResettableFreezer, id uint64) (*history, error) { func readHistory(reader ethdb.AncientReader, id uint64) (*history, error) {
blob := rawdb.ReadStateHistoryMeta(freezer, id) blob := rawdb.ReadStateHistoryMeta(reader, id)
if len(blob) == 0 { if len(blob) == 0 {
return nil, fmt.Errorf("state history not found %d", id) return nil, fmt.Errorf("state history not found %d", id)
} }
@ -483,10 +483,10 @@ func readHistory(freezer *rawdb.ResettableFreezer, id uint64) (*history, error)
} }
var ( var (
dec = history{meta: &m} dec = history{meta: &m}
accountData = rawdb.ReadStateAccountHistory(freezer, id) accountData = rawdb.ReadStateAccountHistory(reader, id)
storageData = rawdb.ReadStateStorageHistory(freezer, id) storageData = rawdb.ReadStateStorageHistory(reader, id)
accountIndexes = rawdb.ReadStateAccountIndex(freezer, id) accountIndexes = rawdb.ReadStateAccountIndex(reader, id)
storageIndexes = rawdb.ReadStateStorageIndex(freezer, id) storageIndexes = rawdb.ReadStateStorageIndex(reader, id)
) )
if err := dec.decode(accountData, storageData, accountIndexes, storageIndexes); err != nil { if err := dec.decode(accountData, storageData, accountIndexes, storageIndexes); err != nil {
return nil, err return nil, err
@ -495,7 +495,7 @@ func readHistory(freezer *rawdb.ResettableFreezer, id uint64) (*history, error)
} }
// writeHistory persists the state history with the provided state set. // writeHistory persists the state history with the provided state set.
func writeHistory(freezer *rawdb.ResettableFreezer, dl *diffLayer) error { func writeHistory(writer ethdb.AncientWriter, dl *diffLayer) error {
// Short circuit if state set is not available. // Short circuit if state set is not available.
if dl.states == nil { if dl.states == nil {
return errors.New("state change set is not available") return errors.New("state change set is not available")
@ -509,7 +509,7 @@ func writeHistory(freezer *rawdb.ResettableFreezer, dl *diffLayer) error {
indexSize := common.StorageSize(len(accountIndex) + len(storageIndex)) indexSize := common.StorageSize(len(accountIndex) + len(storageIndex))
// Write history data into five freezer table respectively. // Write history data into five freezer table respectively.
rawdb.WriteStateHistory(freezer, dl.stateID(), history.meta.encode(), accountIndex, storageIndex, accountData, storageData) rawdb.WriteStateHistory(writer, dl.stateID(), history.meta.encode(), accountIndex, storageIndex, accountData, storageData)
historyDataBytesMeter.Mark(int64(dataSize)) historyDataBytesMeter.Mark(int64(dataSize))
historyIndexBytesMeter.Mark(int64(indexSize)) historyIndexBytesMeter.Mark(int64(indexSize))
@ -521,13 +521,13 @@ func writeHistory(freezer *rawdb.ResettableFreezer, dl *diffLayer) error {
// checkHistories retrieves a batch of meta objects with the specified range // checkHistories retrieves a batch of meta objects with the specified range
// and performs the callback on each item. // and performs the callback on each item.
func checkHistories(freezer *rawdb.ResettableFreezer, start, count uint64, check func(*meta) error) error { func checkHistories(reader ethdb.AncientReader, start, count uint64, check func(*meta) error) error {
for count > 0 { for count > 0 {
number := count number := count
if number > 10000 { if number > 10000 {
number = 10000 // split the big read into small chunks number = 10000 // split the big read into small chunks
} }
blobs, err := rawdb.ReadStateHistoryMetaList(freezer, start, number) blobs, err := rawdb.ReadStateHistoryMetaList(reader, start, number)
if err != nil { if err != nil {
return err return err
} }
@ -548,12 +548,12 @@ func checkHistories(freezer *rawdb.ResettableFreezer, start, count uint64, check
// truncateFromHead removes the extra state histories from the head with the given // truncateFromHead removes the extra state histories from the head with the given
// parameters. It returns the number of items removed from the head. // parameters. It returns the number of items removed from the head.
func truncateFromHead(db ethdb.Batcher, freezer *rawdb.ResettableFreezer, nhead uint64) (int, error) { func truncateFromHead(db ethdb.Batcher, store ethdb.AncientStore, nhead uint64) (int, error) {
ohead, err := freezer.Ancients() ohead, err := store.Ancients()
if err != nil { if err != nil {
return 0, err return 0, err
} }
otail, err := freezer.Tail() otail, err := store.Tail()
if err != nil { if err != nil {
return 0, err return 0, err
} }
@ -566,7 +566,7 @@ func truncateFromHead(db ethdb.Batcher, freezer *rawdb.ResettableFreezer, nhead
return 0, nil return 0, nil
} }
// Load the meta objects in range [nhead+1, ohead] // Load the meta objects in range [nhead+1, ohead]
blobs, err := rawdb.ReadStateHistoryMetaList(freezer, nhead+1, ohead-nhead) blobs, err := rawdb.ReadStateHistoryMetaList(store, nhead+1, ohead-nhead)
if err != nil { if err != nil {
return 0, err return 0, err
} }
@ -581,7 +581,7 @@ func truncateFromHead(db ethdb.Batcher, freezer *rawdb.ResettableFreezer, nhead
if err := batch.Write(); err != nil { if err := batch.Write(); err != nil {
return 0, err return 0, err
} }
ohead, err = freezer.TruncateHead(nhead) ohead, err = store.TruncateHead(nhead)
if err != nil { if err != nil {
return 0, err return 0, err
} }
@ -590,12 +590,12 @@ func truncateFromHead(db ethdb.Batcher, freezer *rawdb.ResettableFreezer, nhead
// truncateFromTail removes the extra state histories from the tail with the given // truncateFromTail removes the extra state histories from the tail with the given
// parameters. It returns the number of items removed from the tail. // parameters. It returns the number of items removed from the tail.
func truncateFromTail(db ethdb.Batcher, freezer *rawdb.ResettableFreezer, ntail uint64) (int, error) { func truncateFromTail(db ethdb.Batcher, store ethdb.AncientStore, ntail uint64) (int, error) {
ohead, err := freezer.Ancients() ohead, err := store.Ancients()
if err != nil { if err != nil {
return 0, err return 0, err
} }
otail, err := freezer.Tail() otail, err := store.Tail()
if err != nil { if err != nil {
return 0, err return 0, err
} }
@ -608,7 +608,7 @@ func truncateFromTail(db ethdb.Batcher, freezer *rawdb.ResettableFreezer, ntail
return 0, nil return 0, nil
} }
// Load the meta objects in range [otail+1, ntail] // Load the meta objects in range [otail+1, ntail]
blobs, err := rawdb.ReadStateHistoryMetaList(freezer, otail+1, ntail-otail) blobs, err := rawdb.ReadStateHistoryMetaList(store, otail+1, ntail-otail)
if err != nil { if err != nil {
return 0, err return 0, err
} }
@ -623,7 +623,7 @@ func truncateFromTail(db ethdb.Batcher, freezer *rawdb.ResettableFreezer, ntail
if err := batch.Write(); err != nil { if err := batch.Write(); err != nil {
return 0, err return 0, err
} }
otail, err = freezer.TruncateTail(ntail) otail, err = store.TruncateTail(ntail)
if err != nil { if err != nil {
return 0, err return 0, err
} }

View file

@ -21,7 +21,7 @@ import (
"time" "time"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
) )
@ -34,7 +34,7 @@ type HistoryStats struct {
} }
// sanitizeRange limits the given range to fit within the local history store. // sanitizeRange limits the given range to fit within the local history store.
func sanitizeRange(start, end uint64, freezer *rawdb.ResettableFreezer) (uint64, uint64, error) { func sanitizeRange(start, end uint64, freezer ethdb.AncientReader) (uint64, uint64, error) {
// Load the id of the first history object in local store. // Load the id of the first history object in local store.
tail, err := freezer.Tail() tail, err := freezer.Tail()
if err != nil { if err != nil {
@ -60,7 +60,7 @@ func sanitizeRange(start, end uint64, freezer *rawdb.ResettableFreezer) (uint64,
return first, last, nil return first, last, nil
} }
func inspectHistory(freezer *rawdb.ResettableFreezer, start, end uint64, onHistory func(*history, *HistoryStats)) (*HistoryStats, error) { func inspectHistory(freezer ethdb.AncientReader, start, end uint64, onHistory func(*history, *HistoryStats)) (*HistoryStats, error) {
var ( var (
stats = &HistoryStats{} stats = &HistoryStats{}
init = time.Now() init = time.Now()
@ -96,7 +96,7 @@ func inspectHistory(freezer *rawdb.ResettableFreezer, start, end uint64, onHisto
} }
// accountHistory inspects the account history within the range. // accountHistory inspects the account history within the range.
func accountHistory(freezer *rawdb.ResettableFreezer, address common.Address, start, end uint64) (*HistoryStats, error) { func accountHistory(freezer ethdb.AncientReader, address common.Address, start, end uint64) (*HistoryStats, error) {
return inspectHistory(freezer, start, end, func(h *history, stats *HistoryStats) { return inspectHistory(freezer, start, end, func(h *history, stats *HistoryStats) {
blob, exists := h.accounts[address] blob, exists := h.accounts[address]
if !exists { if !exists {
@ -108,7 +108,7 @@ func accountHistory(freezer *rawdb.ResettableFreezer, address common.Address, st
} }
// storageHistory inspects the storage history within the range. // storageHistory inspects the storage history within the range.
func storageHistory(freezer *rawdb.ResettableFreezer, address common.Address, slot common.Hash, start uint64, end uint64) (*HistoryStats, error) { func storageHistory(freezer ethdb.AncientReader, address common.Address, slot common.Hash, start uint64, end uint64) (*HistoryStats, error) {
return inspectHistory(freezer, start, end, func(h *history, stats *HistoryStats) { return inspectHistory(freezer, start, end, func(h *history, stats *HistoryStats) {
slots, exists := h.storages[address] slots, exists := h.storages[address]
if !exists { if !exists {
@ -124,7 +124,7 @@ func storageHistory(freezer *rawdb.ResettableFreezer, address common.Address, sl
} }
// historyRange returns the block number range of local state histories. // historyRange returns the block number range of local state histories.
func historyRange(freezer *rawdb.ResettableFreezer) (uint64, uint64, error) { func historyRange(freezer ethdb.AncientReader) (uint64, uint64, error) {
// Load the id of the first history object in local store. // Load the id of the first history object in local store.
tail, err := freezer.Tail() tail, err := freezer.Tail()
if err != nil { if err != nil {

View file

@ -102,7 +102,7 @@ func TestEncodeDecodeHistory(t *testing.T) {
} }
} }
func checkHistory(t *testing.T, db ethdb.KeyValueReader, freezer *rawdb.ResettableFreezer, id uint64, root common.Hash, exist bool) { func checkHistory(t *testing.T, db ethdb.KeyValueReader, freezer ethdb.AncientReader, id uint64, root common.Hash, exist bool) {
blob := rawdb.ReadStateHistoryMeta(freezer, id) blob := rawdb.ReadStateHistoryMeta(freezer, id)
if exist && len(blob) == 0 { if exist && len(blob) == 0 {
t.Fatalf("Failed to load trie history, %d", id) t.Fatalf("Failed to load trie history, %d", id)
@ -118,7 +118,7 @@ func checkHistory(t *testing.T, db ethdb.KeyValueReader, freezer *rawdb.Resettab
} }
} }
func checkHistoriesInRange(t *testing.T, db ethdb.KeyValueReader, freezer *rawdb.ResettableFreezer, from, to uint64, roots []common.Hash, exist bool) { func checkHistoriesInRange(t *testing.T, db ethdb.KeyValueReader, freezer ethdb.AncientReader, from, to uint64, roots []common.Hash, exist bool) {
for i, j := from, 0; i <= to; i, j = i+1, j+1 { for i, j := from, 0; i <= to; i, j = i+1, j+1 {
checkHistory(t, db, freezer, i, roots[j], exist) checkHistory(t, db, freezer, i, roots[j], exist)
} }
@ -129,7 +129,7 @@ func TestTruncateHeadHistory(t *testing.T) {
roots []common.Hash roots []common.Hash
hs = makeHistories(10) hs = makeHistories(10)
db = rawdb.NewMemoryDatabase() db = rawdb.NewMemoryDatabase()
freezer, _ = openFreezer(t.TempDir(), false) freezer, _ = rawdb.NewStateFreezer(t.TempDir(), false)
) )
defer freezer.Close() defer freezer.Close()
@ -157,7 +157,7 @@ func TestTruncateTailHistory(t *testing.T) {
roots []common.Hash roots []common.Hash
hs = makeHistories(10) hs = makeHistories(10)
db = rawdb.NewMemoryDatabase() db = rawdb.NewMemoryDatabase()
freezer, _ = openFreezer(t.TempDir(), false) freezer, _ = rawdb.NewStateFreezer(t.TempDir(), false)
) )
defer freezer.Close() defer freezer.Close()
@ -200,7 +200,7 @@ func TestTruncateTailHistories(t *testing.T) {
roots []common.Hash roots []common.Hash
hs = makeHistories(10) hs = makeHistories(10)
db = rawdb.NewMemoryDatabase() db = rawdb.NewMemoryDatabase()
freezer, _ = openFreezer(t.TempDir()+fmt.Sprintf("%d", i), false) freezer, _ = rawdb.NewStateFreezer(t.TempDir()+fmt.Sprintf("%d", i), false)
) )
defer freezer.Close() defer freezer.Close()
@ -228,7 +228,7 @@ func TestTruncateOutOfRange(t *testing.T) {
var ( var (
hs = makeHistories(10) hs = makeHistories(10)
db = rawdb.NewMemoryDatabase() db = rawdb.NewMemoryDatabase()
freezer, _ = openFreezer(t.TempDir(), false) freezer, _ = rawdb.NewStateFreezer(t.TempDir(), false)
) )
defer freezer.Close() defer freezer.Close()
@ -268,11 +268,6 @@ func TestTruncateOutOfRange(t *testing.T) {
} }
} }
// openFreezer initializes the freezer instance for storing state histories.
func openFreezer(datadir string, readOnly bool) (*rawdb.ResettableFreezer, error) {
return rawdb.NewStateFreezer(datadir, readOnly)
}
func compareSet[k comparable](a, b map[k][]byte) bool { func compareSet[k comparable](a, b map[k][]byte) bool {
if len(a) != len(b) { if len(a) != len(b) {
return false return false

View file

@ -120,9 +120,10 @@ func (db *Database) loadJournal(diskRoot common.Hash) (layer, error) {
// loadLayers loads a pre-existing state layer backed by a key-value store. // loadLayers loads a pre-existing state layer backed by a key-value store.
func (db *Database) loadLayers() layer { func (db *Database) loadLayers() layer {
// Retrieve the root node of persistent state. // Retrieve the root node of persistent state.
_, root := rawdb.ReadAccountTrieNode(db.diskdb, nil) var root = types.EmptyRootHash
root = types.TrieRootHash(root) if blob := rawdb.ReadAccountTrieNode(db.diskdb, nil); len(blob) > 0 {
root = crypto.Keccak256Hash(blob)
}
// Load the layers by resolving the journal // Load the layers by resolving the journal
head, err := db.loadJournal(root) head, err := db.loadJournal(root)
if err == nil { if err == nil {
@ -361,14 +362,13 @@ func (db *Database) Journal(root common.Hash) error {
if err := rlp.Encode(journal, journalVersion); err != nil { if err := rlp.Encode(journal, journalVersion); err != nil {
return err return err
} }
// The stored state in disk might be empty, convert the
// root to emptyRoot in this case.
_, diskroot := rawdb.ReadAccountTrieNode(db.diskdb, nil)
diskroot = types.TrieRootHash(diskroot)
// Secondly write out the state root in disk, ensure all layers // Secondly write out the state root in disk, ensure all layers
// on top are continuous with disk. // on top are continuous with disk.
if err := rlp.Encode(journal, diskroot); err != nil { diskRoot := types.EmptyRootHash
if blob := rawdb.ReadAccountTrieNode(db.diskdb, nil); len(blob) > 0 {
diskRoot = crypto.Keccak256Hash(blob)
}
if err := rlp.Encode(journal, diskRoot); err != nil {
return err return err
} }
// Finally write out the journal of each layer in reverse order. // Finally write out the journal of each layer in reverse order.

View file

@ -17,6 +17,7 @@
package pathdb package pathdb
import ( import (
"bytes"
"fmt" "fmt"
"time" "time"
@ -89,7 +90,7 @@ func (b *nodebuffer) commit(nodes map[common.Hash]map[string]*trienode.Node) *no
// The nodes belong to original diff layer are still accessible even // The nodes belong to original diff layer are still accessible even
// after merging, thus the ownership of nodes map should still belong // after merging, thus the ownership of nodes map should still belong
// to original layer and any mutation on it should be prevented. // to original layer and any mutation on it should be prevented.
current = make(map[string]*trienode.Node) current = make(map[string]*trienode.Node, len(subset))
for path, n := range subset { for path, n := range subset {
current[path] = n current[path] = n
delta += int64(len(n.Blob) + len(path)) delta += int64(len(n.Blob) + len(path))
@ -148,14 +149,14 @@ func (b *nodebuffer) revert(db ethdb.KeyValueReader, nodes map[common.Hash]map[s
// //
// In case of database rollback, don't panic if this "clean" // In case of database rollback, don't panic if this "clean"
// node occurs which is not present in buffer. // node occurs which is not present in buffer.
var nhash common.Hash var blob []byte
if owner == (common.Hash{}) { if owner == (common.Hash{}) {
_, nhash = rawdb.ReadAccountTrieNode(db, []byte(path)) blob = rawdb.ReadAccountTrieNode(db, []byte(path))
} else { } else {
_, nhash = rawdb.ReadStorageTrieNode(db, owner, []byte(path)) blob = rawdb.ReadStorageTrieNode(db, owner, []byte(path))
} }
// Ignore the clean node in the case described above. // Ignore the clean node in the case described above.
if nhash == n.Hash { if bytes.Equal(blob, n.Blob) {
continue continue
} }
panic(fmt.Sprintf("non-existent node (%x %v) blob: %v", owner, path, crypto.Keccak256Hash(n.Blob).Hex())) panic(fmt.Sprintf("non-existent node (%x %v) blob: %v", owner, path, crypto.Keccak256Hash(n.Blob).Hex()))