mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-27 23:26:44 +00:00
Merge branch 'ethereum:master' into portal
This commit is contained in:
commit
e660629a78
87 changed files with 1733 additions and 2198 deletions
|
|
@ -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.
|
||||
func (ks *KeyStore) Lock(addr common.Address) error {
|
||||
ks.mu.Lock()
|
||||
if unl, found := ks.unlocked[addr]; found {
|
||||
ks.mu.Unlock()
|
||||
unl, found := ks.unlocked[addr]
|
||||
ks.mu.Unlock()
|
||||
if found {
|
||||
ks.expire(addr, unl, time.Duration(0)*time.Nanosecond)
|
||||
} else {
|
||||
ks.mu.Unlock()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -95,6 +95,7 @@ func (hub *Hub) readPairings() error {
|
|||
}
|
||||
return err
|
||||
}
|
||||
defer pairingFile.Close()
|
||||
|
||||
pairingData, err := io.ReadAll(pairingFile)
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -250,7 +250,7 @@ func ExecutableDataToBlock(params ExecutableData, versionedHashes []common.Hash,
|
|||
BlobGasUsed: params.BlobGasUsed,
|
||||
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 {
|
||||
return nil, fmt.Errorf("blockhash mismatch, want %x, got %x", params.BlockHash, block.Hash())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -63,9 +63,7 @@ func convertPayload[T payloadType](payload T, parentRoot *zrntcommon.Root) (*typ
|
|||
panic("unsupported block type")
|
||||
}
|
||||
|
||||
block := types.NewBlockWithHeader(&header)
|
||||
block = block.WithBody(transactions, nil)
|
||||
block = block.WithWithdrawals(withdrawals)
|
||||
block := types.NewBlockWithHeader(&header).WithBody(types.Body{Transactions: transactions, Withdrawals: withdrawals})
|
||||
if hash := block.Hash(); hash != expectedHash {
|
||||
return nil, fmt.Errorf("Sanity check failed, payload hash does not match (expected %x, got %x)", expectedHash, hash)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,7 +32,6 @@ import (
|
|||
"github.com/ethereum/go-ethereum/internal/utesting"
|
||||
"github.com/ethereum/go-ethereum/trie"
|
||||
"github.com/ethereum/go-ethereum/trie/trienode"
|
||||
"golang.org/x/crypto/sha3"
|
||||
)
|
||||
|
||||
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
|
||||
var (
|
||||
bytecodes = res.Codes
|
||||
hasher = sha3.NewLegacyKeccak256().(crypto.KeccakState)
|
||||
hasher = crypto.NewKeccakState()
|
||||
hash = make([]byte, 32)
|
||||
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
|
||||
// that the serving node is missing
|
||||
hasher := sha3.NewLegacyKeccak256().(crypto.KeccakState)
|
||||
hasher := crypto.NewKeccakState()
|
||||
hash := make([]byte, 32)
|
||||
trienodes := res.Nodes
|
||||
if got, want := len(trienodes), len(tc.expHashes); got != want {
|
||||
|
|
|
|||
|
|
@ -160,7 +160,7 @@ func (i *bbInput) ToBlock() *types.Block {
|
|||
if i.Header.Difficulty != nil {
|
||||
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.
|
||||
|
|
|
|||
|
|
@ -296,7 +296,7 @@ func (g Alloc) OnAccount(addr *common.Address, dumpAccount state.DumpAccount) {
|
|||
balance, _ := new(big.Int).SetString(dumpAccount.Balance, 0)
|
||||
var storage map[common.Hash]common.Hash
|
||||
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 {
|
||||
storage[k] = common.HexToHash(v)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -246,11 +246,17 @@ func removeDB(ctx *cli.Context) error {
|
|||
ancientDir = config.Node.ResolvePath(ancientDir)
|
||||
}
|
||||
// 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)
|
||||
|
||||
// Delete ancient chain
|
||||
chainPaths := []string{filepath.Join(ancientDir, rawdb.ChainFreezerName)}
|
||||
chainPaths := []string{filepath.Join(
|
||||
ancientDir,
|
||||
rawdb.ChainFreezerName,
|
||||
)}
|
||||
confirmAndRemoveDB(chainPaths, "ancient chain", ctx, removeChainDataFlag.Name)
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -73,6 +73,7 @@ func testConsoleLogging(t *testing.T, format string, tStart, tEnd int) {
|
|||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer readFile.Close()
|
||||
wantLines := split(readFile)
|
||||
haveLines := split(bytes.NewBuffer(haveB))
|
||||
for i, want := range wantLines {
|
||||
|
|
@ -109,6 +110,7 @@ func TestJsonLogging(t *testing.T) {
|
|||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer readFile.Close()
|
||||
wantLines := split(readFile)
|
||||
haveLines := split(bytes.NewBuffer(haveB))
|
||||
for i, wantLine := range wantLines {
|
||||
|
|
|
|||
|
|
@ -1940,13 +1940,15 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) {
|
|||
Fatalf("Could not read genesis from database: %v", err)
|
||||
}
|
||||
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 {
|
||||
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 {
|
||||
Fatalf("Bad developer-mode genesis configuration: genesis block difficulty must be > terminalTotalDifficulty")
|
||||
if genesis.Difficulty.Cmp(big.NewInt(0)) != 0 {
|
||||
Fatalf("Bad developer-mode genesis configuration: difficulty must be 0")
|
||||
}
|
||||
}
|
||||
chaindb.Close()
|
||||
|
|
|
|||
|
|
@ -388,7 +388,7 @@ func (beacon *Beacon) FinalizeAndAssemble(chain consensus.ChainHeaderReader, hea
|
|||
header.Root = state.IntermediateRoot(true)
|
||||
|
||||
// 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
|
||||
|
|
|
|||
|
|
@ -597,7 +597,7 @@ func (c *Clique) FinalizeAndAssemble(chain consensus.ChainHeaderReader, header *
|
|||
header.Root = state.IntermediateRoot(chain.Config().IsEIP158(header.Number))
|
||||
|
||||
// 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
|
||||
|
|
|
|||
|
|
@ -520,7 +520,7 @@ func (ethash *Ethash) FinalizeAndAssemble(chain consensus.ChainHeaderReader, hea
|
|||
header.Root = state.IntermediateRoot(chain.Config().IsEIP158(header.Number))
|
||||
|
||||
// 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.
|
||||
|
|
|
|||
|
|
@ -1309,7 +1309,7 @@ func (bc *BlockChain) InsertReceiptChain(blockChain types.Blocks, receiptChain [
|
|||
// Delete block data from the main database.
|
||||
var (
|
||||
batch = bc.db.NewBatch()
|
||||
canonHashes = make(map[common.Hash]struct{})
|
||||
canonHashes = make(map[common.Hash]struct{}, len(blockChain))
|
||||
)
|
||||
for _, block := range blockChain {
|
||||
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
|
||||
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)
|
||||
|
||||
return &blockProcessingResult{usedGas: usedGas, procTime: proctime, status: status}, nil
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -322,7 +322,7 @@ func TestVerkleGenesisCommit(t *testing.T) {
|
|||
t.Fatalf("expected trie to be verkle")
|
||||
}
|
||||
|
||||
if !rawdb.ExistsAccountTrieNode(db, nil) {
|
||||
if !rawdb.HasAccountTrieNode(db, nil) {
|
||||
t.Fatal("could not find node")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -101,6 +101,7 @@ func main() {
|
|||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer file.Close()
|
||||
if err := json.NewDecoder(file).Decode(g); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -753,7 +753,7 @@ func ReadBlock(db ethdb.Reader, hash common.Hash, number uint64) *types.Block {
|
|||
if body == 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.
|
||||
|
|
@ -843,7 +843,11 @@ func ReadBadBlock(db ethdb.Reader, hash common.Hash) *types.Block {
|
|||
}
|
||||
for _, bad := range badBlocks {
|
||||
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
|
||||
|
|
@ -862,7 +866,11 @@ func ReadAllBadBlocks(db ethdb.Reader) []*types.Block {
|
|||
}
|
||||
var blocks []*types.Block
|
||||
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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -640,7 +640,7 @@ func makeTestBlocks(nblock int, txsPerBlock int) []*types.Block {
|
|||
Number: big.NewInt(int64(i)),
|
||||
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
|
||||
}
|
||||
return blocks
|
||||
|
|
|
|||
|
|
@ -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})
|
||||
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
|
||||
for i, tx := range txs {
|
||||
|
|
|
|||
|
|
@ -24,7 +24,6 @@ import (
|
|||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/ethdb"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"golang.org/x/crypto/sha3"
|
||||
)
|
||||
|
||||
// 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 }
|
||||
|
||||
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 {
|
||||
|
|
@ -65,33 +64,15 @@ func (h *hasher) release() {
|
|||
hasherPool.Put(h)
|
||||
}
|
||||
|
||||
// ReadAccountTrieNode retrieves the account trie node and the associated node
|
||||
// hash with the specified node path.
|
||||
func ReadAccountTrieNode(db ethdb.KeyValueReader, path []byte) ([]byte, common.Hash) {
|
||||
data, err := db.Get(accountTrieNodeKey(path))
|
||||
if err != nil {
|
||||
return nil, common.Hash{}
|
||||
}
|
||||
h := newHasher()
|
||||
defer h.release()
|
||||
return data, h.hash(data)
|
||||
// ReadAccountTrieNode retrieves the account trie node with the specified node path.
|
||||
func ReadAccountTrieNode(db ethdb.KeyValueReader, path []byte) []byte {
|
||||
data, _ := db.Get(accountTrieNodeKey(path))
|
||||
return data
|
||||
}
|
||||
|
||||
// HasAccountTrieNode checks the account trie node presence with the specified
|
||||
// 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
|
||||
// HasAccountTrieNode checks the presence of the account trie node with the
|
||||
// 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))
|
||||
if err != nil {
|
||||
return false
|
||||
|
|
@ -113,33 +94,15 @@ func DeleteAccountTrieNode(db ethdb.KeyValueWriter, path []byte) {
|
|||
}
|
||||
}
|
||||
|
||||
// ReadStorageTrieNode retrieves the storage trie node and the associated node
|
||||
// hash with the specified node path.
|
||||
func ReadStorageTrieNode(db ethdb.KeyValueReader, accountHash common.Hash, path []byte) ([]byte, common.Hash) {
|
||||
data, err := db.Get(storageTrieNodeKey(accountHash, path))
|
||||
if err != nil {
|
||||
return nil, common.Hash{}
|
||||
}
|
||||
h := newHasher()
|
||||
defer h.release()
|
||||
return data, h.hash(data)
|
||||
// ReadStorageTrieNode retrieves the storage trie node with the specified node path.
|
||||
func ReadStorageTrieNode(db ethdb.KeyValueReader, accountHash common.Hash, path []byte) []byte {
|
||||
data, _ := db.Get(storageTrieNodeKey(accountHash, path))
|
||||
return data
|
||||
}
|
||||
|
||||
// HasStorageTrieNode checks the storage trie node presence with the provided
|
||||
// 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
|
||||
// HasStorageTrieNode checks the presence of the storage trie node with the
|
||||
// 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))
|
||||
if err != nil {
|
||||
return false
|
||||
|
|
@ -198,10 +161,18 @@ func HasTrieNode(db ethdb.KeyValueReader, owner common.Hash, path []byte, hash c
|
|||
case HashScheme:
|
||||
return HasLegacyTrieNode(db, hash)
|
||||
case PathScheme:
|
||||
var blob []byte
|
||||
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:
|
||||
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
|
||||
// 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 {
|
||||
switch scheme {
|
||||
case HashScheme:
|
||||
return ReadLegacyTrieNode(db, hash)
|
||||
case PathScheme:
|
||||
var (
|
||||
blob []byte
|
||||
nHash common.Hash
|
||||
)
|
||||
var blob []byte
|
||||
if owner == (common.Hash{}) {
|
||||
blob, nHash = ReadAccountTrieNode(db, path)
|
||||
blob = ReadAccountTrieNode(db, path)
|
||||
} else {
|
||||
blob, nHash = ReadStorageTrieNode(db, owner, path)
|
||||
blob = ReadStorageTrieNode(db, owner, path)
|
||||
}
|
||||
if nHash != hash {
|
||||
if len(blob) == 0 {
|
||||
return nil
|
||||
}
|
||||
h := newHasher()
|
||||
defer h.release()
|
||||
if h.hash(blob) != hash {
|
||||
return nil // exists but not match
|
||||
}
|
||||
return blob
|
||||
default:
|
||||
panic(fmt.Sprintf("Unknown scheme %v", scheme))
|
||||
}
|
||||
}
|
||||
|
||||
// WriteTrieNode writes the trie node into database with the provided node info
|
||||
// and associated node hash.
|
||||
// hashScheme-based lookup requires the following:
|
||||
// - hash
|
||||
// WriteTrieNode writes the trie node into database with the provided node info.
|
||||
//
|
||||
// pathScheme-based lookup requires the following:
|
||||
// - owner
|
||||
// - path
|
||||
// hash-scheme requires the node hash as the identifier.
|
||||
// path-scheme requires the node owner and path as the identifier.
|
||||
func WriteTrieNode(db ethdb.KeyValueWriter, owner common.Hash, path []byte, hash common.Hash, node []byte, scheme string) {
|
||||
switch scheme {
|
||||
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
|
||||
// and associated node hash.
|
||||
// hashScheme-based lookup requires the following:
|
||||
// - hash
|
||||
// DeleteTrieNode deletes the trie node from database with the provided node info.
|
||||
//
|
||||
// pathScheme-based lookup requires the following:
|
||||
// - owner
|
||||
// - path
|
||||
// hash-scheme requires the node hash as the identifier.
|
||||
// path-scheme requires the node owner and path as the identifier.
|
||||
func DeleteTrieNode(db ethdb.KeyValueWriter, owner common.Hash, path []byte, hash common.Hash, scheme string) {
|
||||
switch scheme {
|
||||
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
|
||||
// if the state is not present in database.
|
||||
func ReadStateScheme(db ethdb.Reader) string {
|
||||
// Check if state in path-based scheme is present
|
||||
blob, _ := ReadAccountTrieNode(db, nil)
|
||||
if len(blob) != 0 {
|
||||
// Check if state in path-based scheme is present.
|
||||
if HasAccountTrieNode(db, nil) {
|
||||
return PathScheme
|
||||
}
|
||||
// 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 {
|
||||
return "" // empty datadir
|
||||
}
|
||||
blob = ReadLegacyTrieNode(db, header.Root)
|
||||
if len(blob) == 0 {
|
||||
if !HasLegacyTrieNode(db, header.Root) {
|
||||
return "" // no state in disk
|
||||
}
|
||||
return HashScheme
|
||||
|
|
|
|||
|
|
@ -16,7 +16,11 @@
|
|||
|
||||
package rawdb
|
||||
|
||||
import "path/filepath"
|
||||
import (
|
||||
"path/filepath"
|
||||
|
||||
"github.com/ethereum/go-ethereum/ethdb"
|
||||
)
|
||||
|
||||
// The list of table names of chain freezer.
|
||||
const (
|
||||
|
|
@ -75,7 +79,15 @@ var (
|
|||
// freezers the collections of all builtin freezers.
|
||||
var freezers = []string{ChainFreezerName, StateFreezerName}
|
||||
|
||||
// NewStateFreezer initializes the freezer for state history.
|
||||
func NewStateFreezer(ancientDir string, readOnly bool) (*ResettableFreezer, error) {
|
||||
return NewResettableFreezer(filepath.Join(ancientDir, StateFreezerName), "eth/db/state", readOnly, stateHistoryTableSize, stateFreezerNoSnappy)
|
||||
// NewStateFreezer initializes the ancient store for state history.
|
||||
//
|
||||
// - 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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -89,20 +89,17 @@ func inspectFreezers(db ethdb.Database) ([]freezerInfo, error) {
|
|||
infos = append(infos, info)
|
||||
|
||||
case StateFreezerName:
|
||||
if ReadStateScheme(db) != PathScheme {
|
||||
continue
|
||||
}
|
||||
datadir, err := db.AncientDatadir()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
f, err := NewStateFreezer(datadir, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
continue // might be possible the state freezer is not existent
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
info, err := inspect(StateFreezerName, stateFreezerNoSnappy, f)
|
||||
info, err := inspect(freezer, stateFreezerNoSnappy, f)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
|||
325
core/rawdb/ancienttest/testsuite.go
Normal file
325
core/rawdb/ancienttest/testsuite.go
Normal 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
|
||||
}
|
||||
|
|
@ -39,26 +39,40 @@ const (
|
|||
freezerBatchLimit = 30000
|
||||
)
|
||||
|
||||
// chainFreezer is a wrapper of freezer with additional chain freezing feature.
|
||||
// The background thread will keep moving ancient chain segments from key-value
|
||||
// database to flat files for saving space on live database.
|
||||
// chainFreezer is a wrapper of chain ancient store with additional chain freezing
|
||||
// feature. The background thread will keep moving ancient chain segments from
|
||||
// key-value database to flat files for saving space on live database.
|
||||
type chainFreezer struct {
|
||||
*Freezer
|
||||
ethdb.AncientStore // Ancient store for storing cold chain segment
|
||||
|
||||
quit chan struct{}
|
||||
wg sync.WaitGroup
|
||||
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) {
|
||||
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 {
|
||||
return nil, err
|
||||
}
|
||||
return &chainFreezer{
|
||||
Freezer: freezer,
|
||||
quit: make(chan struct{}),
|
||||
trigger: make(chan chan struct{}),
|
||||
AncientStore: freezer,
|
||||
quit: make(chan struct{}),
|
||||
trigger: make(chan chan struct{}),
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
|
@ -70,7 +84,7 @@ func (f *chainFreezer) Close() error {
|
|||
close(f.quit)
|
||||
}
|
||||
f.wg.Wait()
|
||||
return f.Freezer.Close()
|
||||
return f.AncientStore.Close()
|
||||
}
|
||||
|
||||
// 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)
|
||||
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.
|
||||
if frozen != 0 && frozen-1 >= threshold {
|
||||
|
|
@ -190,7 +204,7 @@ func (f *chainFreezer) freeze(db ethdb.KeyValueStore) {
|
|||
backoff = true
|
||||
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 {
|
||||
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
|
||||
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++ {
|
||||
// Always keep the genesis block in active database
|
||||
if number != 0 {
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ func TestChainIterator(t *testing.T) {
|
|||
var block *types.Block
|
||||
var txs []*types.Transaction
|
||||
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)
|
||||
WriteCanonicalHash(chainDb, block.Hash(), block.NumberU64())
|
||||
for i := uint64(1); i <= 10; i++ {
|
||||
|
|
@ -60,7 +60,7 @@ func TestChainIterator(t *testing.T) {
|
|||
})
|
||||
}
|
||||
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)
|
||||
WriteCanonicalHash(chainDb, block.Hash(), block.NumberU64())
|
||||
}
|
||||
|
|
@ -111,7 +111,7 @@ func TestIndexTransactions(t *testing.T) {
|
|||
to := common.BytesToAddress([]byte{0x11})
|
||||
|
||||
// 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)
|
||||
WriteCanonicalHash(chainDb, block.Hash(), block.NumberU64())
|
||||
|
||||
|
|
@ -138,7 +138,7 @@ func TestIndexTransactions(t *testing.T) {
|
|||
})
|
||||
}
|
||||
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)
|
||||
WriteCanonicalHash(chainDb, block.Hash(), block.NumberU64())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -34,11 +34,13 @@ import (
|
|||
"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 {
|
||||
ancientRoot string
|
||||
ethdb.KeyValueStore
|
||||
ethdb.AncientStore
|
||||
*chainFreezer
|
||||
|
||||
readOnly bool
|
||||
ancientRoot string
|
||||
}
|
||||
|
||||
// AncientDatadir returns the path of root ancient directory.
|
||||
|
|
@ -50,7 +52,7 @@ func (frdb *freezerdb) AncientDatadir() (string, error) {
|
|||
// the slow ancient tables.
|
||||
func (frdb *freezerdb) Close() error {
|
||||
var errs []error
|
||||
if err := frdb.AncientStore.Close(); err != nil {
|
||||
if err := frdb.chainFreezer.Close(); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
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
|
||||
// automatic background run.
|
||||
func (frdb *freezerdb) Freeze() error {
|
||||
if frdb.AncientStore.(*chainFreezer).readonly {
|
||||
if frdb.readOnly {
|
||||
return errReadOnly
|
||||
}
|
||||
// Trigger a freeze cycle and block until it's done
|
||||
trigger := make(chan struct{}, 1)
|
||||
frdb.AncientStore.(*chainFreezer).trigger <- trigger
|
||||
frdb.chainFreezer.trigger <- trigger
|
||||
<-trigger
|
||||
return nil
|
||||
}
|
||||
|
|
@ -192,8 +194,14 @@ func resolveChainFreezerDir(ancient string) string {
|
|||
// storage. The passed ancient indicates the path of root ancient directory
|
||||
// where the chain freezer can be opened.
|
||||
func NewDatabaseWithFreezer(db ethdb.KeyValueStore, ancient string, namespace string, readonly bool) (ethdb.Database, error) {
|
||||
// Create the idle freezer instance
|
||||
frdb, err := newChainFreezer(resolveChainFreezerDir(ancient), namespace, readonly)
|
||||
// Create the idle freezer instance. If the given ancient directory is empty,
|
||||
// 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 {
|
||||
printChainMetadata(db)
|
||||
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
|
||||
if !frdb.readonly {
|
||||
if !readonly {
|
||||
frdb.wg.Add(1)
|
||||
go func() {
|
||||
frdb.freeze(db)
|
||||
|
|
@ -287,7 +295,7 @@ func NewDatabaseWithFreezer(db ethdb.KeyValueStore, ancient string, namespace st
|
|||
return &freezerdb{
|
||||
ancientRoot: ancient,
|
||||
KeyValueStore: db,
|
||||
AncientStore: frdb,
|
||||
chainFreezer: frdb,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -62,7 +62,7 @@ const freezerTableSize = 2 * 1000 * 1000 * 1000
|
|||
// reserving it for go-ethereum. This would also reduce the memory requirements
|
||||
// of Geth, and thus also GC overhead.
|
||||
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
|
||||
|
||||
// This lock synchronizes writers and the truncate operation, as well as
|
||||
|
|
@ -76,12 +76,6 @@ type Freezer struct {
|
|||
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
|
||||
// data according to the given parameters.
|
||||
//
|
||||
|
|
|
|||
428
core/rawdb/freezer_memory.go
Normal file
428
core/rawdb/freezer_memory.go
Normal 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
|
||||
}
|
||||
41
core/rawdb/freezer_memory_test.go
Normal file
41
core/rawdb/freezer_memory_test.go
Normal 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)
|
||||
})
|
||||
}
|
||||
|
|
@ -30,16 +30,16 @@ const tmpSuffix = ".tmp"
|
|||
// freezerOpenFunc is the function used to open/create a freezer.
|
||||
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.
|
||||
type ResettableFreezer struct {
|
||||
type resettableFreezer struct {
|
||||
freezer *Freezer
|
||||
opener freezerOpenFunc
|
||||
datadir string
|
||||
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
|
||||
// by the freezer. And also the user-configurable ancient root directory
|
||||
// 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
|
||||
// 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 {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -59,7 +59,7 @@ func NewResettableFreezer(datadir string, namespace string, readonly bool, maxTa
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &ResettableFreezer{
|
||||
return &resettableFreezer{
|
||||
freezer: freezer,
|
||||
opener: opener,
|
||||
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
|
||||
// is guaranteed by the rename operation, the leftover directory will be
|
||||
// cleaned up in next startup in case crash happens after rename.
|
||||
func (f *ResettableFreezer) Reset() error {
|
||||
func (f *resettableFreezer) Reset() error {
|
||||
f.lock.Lock()
|
||||
defer f.lock.Unlock()
|
||||
|
||||
|
|
@ -93,7 +93,7 @@ func (f *ResettableFreezer) Reset() error {
|
|||
}
|
||||
|
||||
// Close terminates the chain freezer, unmapping all the data files.
|
||||
func (f *ResettableFreezer) Close() error {
|
||||
func (f *resettableFreezer) Close() error {
|
||||
f.lock.RLock()
|
||||
defer f.lock.RUnlock()
|
||||
|
||||
|
|
@ -102,7 +102,7 @@ func (f *ResettableFreezer) Close() error {
|
|||
|
||||
// HasAncient returns an indicator whether the specified ancient data exists
|
||||
// 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()
|
||||
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.
|
||||
func (f *ResettableFreezer) Ancient(kind string, number uint64) ([]byte, error) {
|
||||
func (f *resettableFreezer) Ancient(kind string, number uint64) ([]byte, error) {
|
||||
f.lock.RLock()
|
||||
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),
|
||||
// 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 *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()
|
||||
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.
|
||||
func (f *ResettableFreezer) Ancients() (uint64, error) {
|
||||
func (f *resettableFreezer) Ancients() (uint64, error) {
|
||||
f.lock.RLock()
|
||||
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.
|
||||
func (f *ResettableFreezer) Tail() (uint64, error) {
|
||||
func (f *resettableFreezer) Tail() (uint64, error) {
|
||||
f.lock.RLock()
|
||||
defer f.lock.RUnlock()
|
||||
|
||||
|
|
@ -147,7 +147,7 @@ func (f *ResettableFreezer) Tail() (uint64, error) {
|
|||
}
|
||||
|
||||
// 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()
|
||||
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
|
||||
// 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()
|
||||
defer f.lock.RUnlock()
|
||||
|
||||
|
|
@ -164,7 +164,7 @@ func (f *ResettableFreezer) ReadAncients(fn func(ethdb.AncientReaderOp) error) (
|
|||
}
|
||||
|
||||
// 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()
|
||||
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.
|
||||
// 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()
|
||||
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.
|
||||
// It returns the previous value
|
||||
func (f *ResettableFreezer) TruncateTail(tail uint64) (uint64, error) {
|
||||
func (f *resettableFreezer) TruncateTail(tail uint64) (uint64, error) {
|
||||
f.lock.RLock()
|
||||
defer f.lock.RUnlock()
|
||||
|
||||
|
|
@ -190,7 +190,7 @@ func (f *ResettableFreezer) TruncateTail(tail uint64) (uint64, error) {
|
|||
}
|
||||
|
||||
// Sync flushes all data tables to disk.
|
||||
func (f *ResettableFreezer) Sync() error {
|
||||
func (f *resettableFreezer) Sync() error {
|
||||
f.lock.RLock()
|
||||
defer f.lock.RUnlock()
|
||||
|
||||
|
|
@ -199,7 +199,7 @@ func (f *ResettableFreezer) Sync() error {
|
|||
|
||||
// MigrateTable processes the entries in a given table in sequence
|
||||
// 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()
|
||||
defer f.lock.RUnlock()
|
||||
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ func TestResetFreezer(t *testing.T) {
|
|||
{1, bytes.Repeat([]byte{1}, 2048)},
|
||||
{2, bytes.Repeat([]byte{2}, 2048)},
|
||||
}
|
||||
f, _ := NewResettableFreezer(t.TempDir(), "", false, 2048, freezerTestTableDef)
|
||||
f, _ := newResettableFreezer(t.TempDir(), "", false, 2048, freezerTestTableDef)
|
||||
defer f.Close()
|
||||
|
||||
f.ModifyAncients(func(op ethdb.AncientWriteOp) error {
|
||||
|
|
@ -87,7 +87,7 @@ func TestFreezerCleanup(t *testing.T) {
|
|||
{2, bytes.Repeat([]byte{2}, 2048)},
|
||||
}
|
||||
datadir := t.TempDir()
|
||||
f, _ := NewResettableFreezer(datadir, "", false, 2048, freezerTestTableDef)
|
||||
f, _ := newResettableFreezer(datadir, "", false, 2048, freezerTestTableDef)
|
||||
f.ModifyAncients(func(op ethdb.AncientWriteOp) error {
|
||||
for _, item := range items {
|
||||
op.AppendRaw("test", item.id, item.blob)
|
||||
|
|
@ -98,7 +98,7 @@ func TestFreezerCleanup(t *testing.T) {
|
|||
os.Rename(datadir, tmpName(datadir))
|
||||
|
||||
// Open the freezer again, trigger cleanup operation
|
||||
f, _ = NewResettableFreezer(datadir, "", false, 2048, freezerTestTableDef)
|
||||
f, _ = newResettableFreezer(datadir, "", false, 2048, freezerTestTableDef)
|
||||
f.Close()
|
||||
|
||||
if _, err := os.Lstat(tmpName(datadir)); !os.IsNotExist(err) {
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ import (
|
|||
"sync"
|
||||
"testing"
|
||||
|
||||
"github.com/ethereum/go-ethereum/core/rawdb/ancienttest"
|
||||
"github.com/ethereum/go-ethereum/ethdb"
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
|
@ -480,3 +481,22 @@ func TestFreezerCloseSync(t *testing.T) {
|
|||
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
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -403,6 +403,9 @@ func (s *stateObject) updateRoot() {
|
|||
// 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
|
||||
// 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) {
|
||||
// Short circuit if trie is not even loaded, don't bother with committing anything
|
||||
if s.trie == nil {
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ import (
|
|||
"math/big"
|
||||
"slices"
|
||||
"sort"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
|
|
@ -37,6 +38,7 @@ import (
|
|||
"github.com/ethereum/go-ethereum/trie/trienode"
|
||||
"github.com/ethereum/go-ethereum/trie/triestate"
|
||||
"github.com/holiman/uint256"
|
||||
"golang.org/x/sync/errgroup"
|
||||
)
|
||||
|
||||
type revision struct {
|
||||
|
|
@ -1146,66 +1148,108 @@ func (s *StateDB) Commit(block uint64, deleteEmptyObjects bool) (common.Hash, er
|
|||
storageTrieNodesUpdated int
|
||||
storageTrieNodesDeleted int
|
||||
nodes = trienode.NewMergedNodeSet()
|
||||
codeWriter = s.db.DiskDB().NewBatch()
|
||||
)
|
||||
// Handle all state deletions first
|
||||
if err := s.handleDestruction(nodes); err != nil {
|
||||
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()
|
||||
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 {
|
||||
if op.isDelete() {
|
||||
continue
|
||||
}
|
||||
obj := s.stateObjects[addr]
|
||||
|
||||
// Write any contract code associated with the state object
|
||||
obj := s.stateObjects[addr]
|
||||
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
|
||||
}
|
||||
// Write any storage changes in the state object to its storage trie
|
||||
set, err := obj.commit()
|
||||
if err != nil {
|
||||
return common.Hash{}, err
|
||||
}
|
||||
// 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.
|
||||
// In this case, the node set is shared by both accounts.
|
||||
if set != nil {
|
||||
if err := nodes.Merge(set); err != nil {
|
||||
return common.Hash{}, err
|
||||
// Run the storage updates concurrently to one another
|
||||
workers.Go(func() error {
|
||||
// Write any storage changes in the state object to its storage trie
|
||||
set, err := obj.commit()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
updates, deleted := set.Size()
|
||||
storageTrieNodesUpdated += updates
|
||||
storageTrieNodesDeleted += deleted
|
||||
}
|
||||
}
|
||||
s.StorageCommits += time.Since(start)
|
||||
// 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.
|
||||
// In this case, the node set is shared by both accounts.
|
||||
lock.Lock()
|
||||
defer lock.Unlock()
|
||||
|
||||
if codeWriter.ValueSize() > 0 {
|
||||
if err := codeWriter.Write(); err != nil {
|
||||
log.Crit("Failed to commit dirty codes", "error", err)
|
||||
}
|
||||
if set != nil {
|
||||
if err = nodes.Merge(set); err != nil {
|
||||
return err
|
||||
}
|
||||
updates, deleted := set.Size()
|
||||
storageTrieNodesUpdated += updates
|
||||
storageTrieNodesDeleted += deleted
|
||||
}
|
||||
s.StorageCommits = time.Since(start) // overwrite with the longest storage commit runtime
|
||||
return nil
|
||||
})
|
||||
}
|
||||
// Write the account trie changes, measuring the amount of wasted time
|
||||
start = time.Now()
|
||||
|
||||
root, set, err := s.trie.Commit(true)
|
||||
if err != nil {
|
||||
// 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,
|
||||
// it's always yolo.
|
||||
workers.Go(func() error {
|
||||
if code.ValueSize() > 0 {
|
||||
if err := code.Write(); err != nil {
|
||||
log.Crit("Failed to commit dirty codes", "error", err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
// Wait for everything to finish and update the metrics
|
||||
if err := workers.Wait(); err != nil {
|
||||
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))
|
||||
storageUpdatedMeter.Mark(int64(s.StorageUpdated))
|
||||
accountDeletedMeter.Mark(int64(s.AccountDeleted))
|
||||
|
|
|
|||
|
|
@ -417,10 +417,11 @@ func GenerateBadBlock(parent *types.Block, engine consensus.Engine, txs types.Tr
|
|||
header.ParentBeaconRoot = &beaconRoot
|
||||
}
|
||||
// Assemble and return the final block for sealing
|
||||
body := &types.Body{Transactions: txs}
|
||||
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 (
|
||||
|
|
|
|||
|
|
@ -87,7 +87,7 @@ func (bc *testBlockChain) CurrentBlock() *types.Header {
|
|||
}
|
||||
|
||||
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) {
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ import (
|
|||
"io"
|
||||
"math/big"
|
||||
"reflect"
|
||||
"slices"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
|
|
@ -217,13 +218,19 @@ type extblock struct {
|
|||
// NewBlock creates a new block. 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 NewBlock(header *Header, txs []*Transaction, uncles []*Header, receipts []*Receipt, hasher TrieHasher) *Block {
|
||||
b := &Block{header: CopyHeader(header)}
|
||||
// The body elements and the receipts are used to recompute and overwrite the
|
||||
// relevant portions of the header.
|
||||
func NewBlock(header *Header, body *Body, receipts []*Receipt, hasher TrieHasher) *Block {
|
||||
if body == nil {
|
||||
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 {
|
||||
b.header.TxHash = EmptyTxsHash
|
||||
} 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 {
|
||||
b.header.WithdrawalsHash = nil
|
||||
} else if len(withdrawals) == 0 {
|
||||
b.header.WithdrawalsHash = &EmptyWithdrawalsHash
|
||||
b.withdrawals = Withdrawals{}
|
||||
} else {
|
||||
h := DeriveSha(Withdrawals(withdrawals), hasher)
|
||||
b.header.WithdrawalsHash = &h
|
||||
hash := DeriveSha(Withdrawals(withdrawals), hasher)
|
||||
b.header.WithdrawalsHash = &hash
|
||||
b.withdrawals = slices.Clone(withdrawals)
|
||||
}
|
||||
|
||||
return b.WithWithdrawals(withdrawals)
|
||||
return b
|
||||
}
|
||||
|
||||
// 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.
|
||||
func (b *Block) WithBody(transactions []*Transaction, uncles []*Header) *Block {
|
||||
// WithBody returns a new block with the original header and a deep copy of the
|
||||
// provided body.
|
||||
func (b *Block) WithBody(body Body) *Block {
|
||||
block := &Block{
|
||||
header: b.header,
|
||||
transactions: make([]*Transaction, len(transactions)),
|
||||
uncles: make([]*Header, len(uncles)),
|
||||
withdrawals: b.withdrawals,
|
||||
transactions: slices.Clone(body.Transactions),
|
||||
uncles: make([]*Header, len(body.Uncles)),
|
||||
withdrawals: slices.Clone(body.Withdrawals),
|
||||
}
|
||||
copy(block.transactions, transactions)
|
||||
for i := range uncles {
|
||||
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)
|
||||
for i := range body.Uncles {
|
||||
block.uncles[i] = CopyHeader(body.Uncles[i])
|
||||
}
|
||||
return block
|
||||
}
|
||||
|
|
|
|||
|
|
@ -254,7 +254,7 @@ func makeBenchBlock() *Block {
|
|||
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) {
|
||||
|
|
|
|||
|
|
@ -705,6 +705,8 @@ func (c *bls12381G1Add) Run(input []byte) ([]byte, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
// No need to check the subgroup here, as specified by EIP-2537
|
||||
|
||||
// Compute r = p_0 + p_1
|
||||
p0.Add(p0, p1)
|
||||
|
||||
|
|
@ -734,6 +736,11 @@ func (c *bls12381G1Mul) Run(input []byte) ([]byte, error) {
|
|||
if p0, err = decodePointG1(input[:128]); err != nil {
|
||||
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
|
||||
e := new(big.Int).SetBytes(input[128:])
|
||||
|
||||
|
|
@ -787,6 +794,11 @@ func (c *bls12381G1MultiExp) Run(input []byte) ([]byte, error) {
|
|||
if err != nil {
|
||||
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
|
||||
// Decode scalar value
|
||||
scalars[i] = *new(fr.Element).SetBytes(input[t1:t2])
|
||||
|
|
@ -827,6 +839,8 @@ func (c *bls12381G2Add) Run(input []byte) ([]byte, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
// No need to check the subgroup here, as specified by EIP-2537
|
||||
|
||||
// Compute r = p_0 + p_1
|
||||
r := new(bls12381.G2Affine)
|
||||
r.Add(p0, p1)
|
||||
|
|
@ -857,6 +871,11 @@ func (c *bls12381G2Mul) Run(input []byte) ([]byte, error) {
|
|||
if p0, err = decodePointG2(input[:256]); err != nil {
|
||||
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
|
||||
e := new(big.Int).SetBytes(input[256:])
|
||||
|
||||
|
|
@ -910,6 +929,11 @@ func (c *bls12381G2MultiExp) Run(input []byte) ([]byte, error) {
|
|||
if err != nil {
|
||||
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
|
||||
// Decode scalar value
|
||||
scalars[i] = *new(fr.Element).SetBytes(input[t1:t2])
|
||||
|
|
@ -1099,9 +1123,6 @@ func (c *bls12381MapG1) Run(input []byte) ([]byte, error) {
|
|||
|
||||
// Compute mapping
|
||||
r := bls12381.MapToG1(fe)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Encode the G1 point to 128 bytes
|
||||
return encodePointG1(&r), nil
|
||||
|
|
@ -1135,9 +1156,6 @@ func (c *bls12381MapG2) Run(input []byte) ([]byte, error) {
|
|||
|
||||
// Compute mapping
|
||||
r := bls12381.MapToG2(bls12381.E2{A0: c0, A1: c1})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Encode the G2 point to 256 bytes
|
||||
return encodePointG2(&r), nil
|
||||
|
|
|
|||
|
|
@ -173,11 +173,7 @@ func opByte(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byt
|
|||
|
||||
func opAddmod(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
||||
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
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -779,7 +779,7 @@ func setBlockhash(data *engine.ExecutableData) *engine.ExecutableData {
|
|||
Extra: data.ExtraData,
|
||||
MixDigest: data.Random,
|
||||
}
|
||||
block := types.NewBlockWithHeader(header).WithBody(txs, nil /* uncles */)
|
||||
block := types.NewBlockWithHeader(header).WithBody(types.Body{Transactions: txs})
|
||||
data.BlockHash = block.Hash()
|
||||
return data
|
||||
}
|
||||
|
|
@ -935,7 +935,7 @@ func TestNewPayloadOnInvalidTerminalBlock(t *testing.T) {
|
|||
Extra: data.ExtraData,
|
||||
MixDigest: data.Random,
|
||||
}
|
||||
block := types.NewBlockWithHeader(header).WithBody(txs, nil /* uncles */)
|
||||
block := types.NewBlockWithHeader(header).WithBody(types.Body{Transactions: txs})
|
||||
data.BlockHash = block.Hash()
|
||||
// Send the new payload
|
||||
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)
|
||||
var want int
|
||||
for _, tx := range txs {
|
||||
|
|
|
|||
|
|
@ -106,7 +106,7 @@ func (b *beaconBackfiller) resume() {
|
|||
}()
|
||||
// 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.
|
||||
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)
|
||||
return
|
||||
}
|
||||
|
|
@ -268,9 +268,9 @@ func (d *Downloader) findBeaconAncestor() (uint64, error) {
|
|||
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.
|
||||
func (d *Downloader) fetchBeaconHeaders(from uint64) error {
|
||||
func (d *Downloader) fetchHeaders(from uint64) error {
|
||||
var head *types.Header
|
||||
_, tail, _, err := d.skeleton.Bounds()
|
||||
if err != nil {
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -20,7 +20,6 @@ import (
|
|||
"fmt"
|
||||
"math/big"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
|
|
@ -94,35 +93,16 @@ func (dl *downloadTester) terminate() {
|
|||
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.
|
||||
func (dl *downloadTester) newPeer(id string, version uint, blocks []*types.Block) *downloadTesterPeer {
|
||||
dl.lock.Lock()
|
||||
defer dl.lock.Unlock()
|
||||
|
||||
peer := &downloadTesterPeer{
|
||||
dl: dl,
|
||||
id: id,
|
||||
chain: newTestBlockchain(blocks),
|
||||
withholdHeaders: make(map[common.Hash]struct{}),
|
||||
dl: dl,
|
||||
id: id,
|
||||
chain: newTestBlockchain(blocks),
|
||||
withholdBodies: make(map[common.Hash]struct{}),
|
||||
}
|
||||
dl.peers[id] = peer
|
||||
|
||||
|
|
@ -146,11 +126,10 @@ func (dl *downloadTester) dropPeer(id string) {
|
|||
}
|
||||
|
||||
type downloadTesterPeer struct {
|
||||
dl *downloadTester
|
||||
id string
|
||||
chain *core.BlockChain
|
||||
|
||||
withholdHeaders map[common.Hash]struct{}
|
||||
dl *downloadTester
|
||||
withholdBodies map[common.Hash]struct{}
|
||||
id string
|
||||
chain *core.BlockChain
|
||||
}
|
||||
|
||||
// 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,
|
||||
}, nil)
|
||||
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))
|
||||
for i, header := range headers {
|
||||
hashes[i] = header.Hash()
|
||||
|
|
@ -230,15 +200,6 @@ func (dlp *downloadTesterPeer) RequestHeadersByNumber(origin uint64, amount int,
|
|||
Reverse: reverse,
|
||||
}, nil)
|
||||
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))
|
||||
for i, header := range headers {
|
||||
hashes[i] = header.Hash()
|
||||
|
|
@ -278,7 +239,13 @@ func (dlp *downloadTesterPeer) RequestBodies(hashes []common.Hash, sink chan *et
|
|||
)
|
||||
hasher := trie.NewStackTrie(nil)
|
||||
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)
|
||||
}
|
||||
req := ð.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 testCanonSync(t *testing.T, protocol uint, mode SyncMode) {
|
||||
tester := newTester(t)
|
||||
success := make(chan struct{})
|
||||
tester := newTesterWithNotification(t, func() {
|
||||
close(success)
|
||||
})
|
||||
defer tester.terminate()
|
||||
|
||||
// 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:])
|
||||
|
||||
// Synchronise with the peer and make sure all relevant data was retrieved
|
||||
if err := tester.sync("peer", nil, mode); err != nil {
|
||||
t.Fatalf("failed to synchronise blocks: %v", err)
|
||||
if err := tester.downloader.BeaconSync(mode, chain.blocks[len(chain.blocks)-1].Header(), nil); err != nil {
|
||||
t.Fatalf("failed to beacon-sync chain: %v", err)
|
||||
}
|
||||
select {
|
||||
case <-success:
|
||||
assertOwnChain(t, tester, len(chain.blocks))
|
||||
case <-time.NewTimer(time.Second * 3).C:
|
||||
t.Fatalf("Failed to sync chain in three seconds")
|
||||
}
|
||||
assertOwnChain(t, tester, len(chain.blocks))
|
||||
}
|
||||
|
||||
// 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
|
||||
errc := make(chan error, 1)
|
||||
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
|
||||
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.
|
||||
func TestCancel68Full(t *testing.T) { testCancel(t, eth.ETH68, FullSync) }
|
||||
func TestCancel68Snap(t *testing.T) { testCancel(t, eth.ETH68, SnapSync) }
|
||||
func TestCancel68Light(t *testing.T) { testCancel(t, eth.ETH68, LightSync) }
|
||||
|
||||
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()
|
||||
|
||||
chain := testChainBase.shorten(MaxHeaderFetch)
|
||||
|
|
@ -672,38 +532,16 @@ func testCancel(t *testing.T, protocol uint, mode SyncMode) {
|
|||
t.Errorf("download queue not idle")
|
||||
}
|
||||
// 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)
|
||||
}
|
||||
<-complete
|
||||
tester.downloader.Cancel()
|
||||
if !tester.downloader.queue.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
|
||||
// and not wreak havoc on other nodes in the network.
|
||||
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 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()
|
||||
|
||||
// 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
|
||||
tester.newPeer("peer 68", eth.ETH68, chain.blocks[1:])
|
||||
|
||||
// Synchronise with the requested peer and make sure all blocks were retrieved
|
||||
if err := tester.sync(fmt.Sprintf("peer %d", protocol), nil, mode); err != nil {
|
||||
t.Fatalf("failed to synchronise blocks: %v", err)
|
||||
if err := tester.downloader.BeaconSync(mode, chain.blocks[len(chain.blocks)-1].Header(), nil); err != nil {
|
||||
t.Fatalf("failed to start beacon sync: #{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))
|
||||
|
||||
|
|
@ -742,7 +589,10 @@ func TestEmptyShortCircuit68Snap(t *testing.T) { testEmptyShortCircuit(t, eth.E
|
|||
func TestEmptyShortCircuit68Light(t *testing.T) { testEmptyShortCircuit(t, eth.ETH68, LightSync) }
|
||||
|
||||
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()
|
||||
|
||||
// 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) {
|
||||
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)
|
||||
}
|
||||
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))
|
||||
|
||||
// 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) {
|
||||
// Mark this method as a helper to report errors at callsite, not in here
|
||||
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
|
||||
// being fast-synced from, avoiding potential cheap eclipse attacks.
|
||||
func TestBeaconSync68Full(t *testing.T) { testBeaconSync(t, eth.ETH68, FullSync) }
|
||||
func TestBeaconSync68Snap(t *testing.T) { testBeaconSync(t, eth.ETH68, SnapSync) }
|
||||
|
||||
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 {
|
||||
name string // The name of testing scenario
|
||||
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
|
||||
// number) is tracked and updated correctly in case of manual head reversion
|
||||
func TestBeaconForkedSyncProgress68Full(t *testing.T) {
|
||||
testBeaconForkedSyncProgress(t, eth.ETH68, FullSync)
|
||||
}
|
||||
func TestBeaconForkedSyncProgress68Snap(t *testing.T) {
|
||||
testBeaconForkedSyncProgress(t, eth.ETH68, SnapSync)
|
||||
}
|
||||
func TestBeaconForkedSyncProgress68Light(t *testing.T) {
|
||||
testBeaconForkedSyncProgress(t, eth.ETH68, LightSync)
|
||||
}
|
||||
// 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 testBeaconForkedSyncProgress(t *testing.T, protocol uint, mode SyncMode) {
|
||||
func testSyncProgress(t *testing.T, protocol uint, mode SyncMode) {
|
||||
success := make(chan struct{})
|
||||
tester := newTesterWithNotification(t, func() {
|
||||
success <- struct{}{}
|
||||
})
|
||||
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.downloader.BeaconSync(mode, chainA.blocks[len(chainA.blocks)-1].Header(), nil); err != nil {
|
||||
panic(fmt.Sprintf("failed to beacon sync: %v", err))
|
||||
}
|
||||
}()
|
||||
chain := testChainBase.shorten(blockCacheMaxItems - 15)
|
||||
shortChain := chain.shorten(len(chain.blocks) / 2).blocks[1:]
|
||||
|
||||
<-starting
|
||||
progress <- struct{}{}
|
||||
// Connect to peer that provides all headers and part of the bodies
|
||||
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 {
|
||||
case <-success:
|
||||
checkProgress(t, tester.downloader, "initial", ethereum.SyncProgress{
|
||||
HighestBlock: uint64(len(chainA.blocks) - 1),
|
||||
CurrentBlock: uint64(len(chainA.blocks) - 1),
|
||||
// Ok, downloader fully cancelled after sync cycle
|
||||
checkProgress(t, tester.downloader, "peer-half", ethereum.SyncProgress{
|
||||
CurrentBlock: uint64(len(chain.blocks)/2 - 1),
|
||||
HighestBlock: uint64(len(chain.blocks)/2 - 1),
|
||||
})
|
||||
case <-time.NewTimer(time.Second * 3).C:
|
||||
t.Fatalf("Failed to sync chain in three seconds")
|
||||
}
|
||||
|
||||
// Set the head to a second fork
|
||||
tester.newPeer("fork B", protocol, chainB.blocks[1:])
|
||||
pending.Add(1)
|
||||
go func() {
|
||||
defer pending.Done()
|
||||
if err := tester.downloader.BeaconSync(mode, chainB.blocks[len(chainB.blocks)-1].Header(), nil); err != nil {
|
||||
panic(fmt.Sprintf("failed to beacon sync: %v", err))
|
||||
}
|
||||
}()
|
||||
// Synchronise all the blocks and check continuation progress
|
||||
tester.newPeer("peer-full", protocol, chain.blocks[1:])
|
||||
if err := tester.downloader.BeaconSync(mode, chain.blocks[len(chain.blocks)-1].Header(), nil); err != nil {
|
||||
t.Fatalf("failed to beacon-sync chain: %v", err)
|
||||
}
|
||||
var startingBlock uint64
|
||||
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 {
|
||||
case <-success:
|
||||
checkProgress(t, tester.downloader, "initial", ethereum.SyncProgress{
|
||||
HighestBlock: uint64(len(chainB.blocks) - 1),
|
||||
CurrentBlock: uint64(len(chainB.blocks) - 1),
|
||||
StartingBlock: 0,
|
||||
// Ok, downloader fully cancelled after sync cycle
|
||||
checkProgress(t, tester.downloader, "peer-full", ethereum.SyncProgress{
|
||||
StartingBlock: startingBlock,
|
||||
CurrentBlock: uint64(len(chain.blocks) - 1),
|
||||
HighestBlock: uint64(len(chain.blocks) - 1),
|
||||
})
|
||||
case <-time.NewTimer(time.Second * 3).C:
|
||||
t.Fatalf("Failed to sync chain in three seconds")
|
||||
|
|
|
|||
|
|
@ -68,48 +68,3 @@ func (d *Downloader) fetchHeadersByHash(p *peerConnection, hash common.Hash, amo
|
|||
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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -76,7 +76,7 @@ type typedQueue interface {
|
|||
// concurrentFetch iteratively downloads scheduled block parts, taking available
|
||||
// peers, reserving a chunk of fetch requests for each and waiting for delivery
|
||||
// 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
|
||||
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
|
||||
finished := false
|
||||
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 queue.pending() == 0 {
|
||||
if len(pending) == 0 && finished {
|
||||
|
|
@ -158,27 +154,20 @@ func (d *Downloader) concurrentFetch(queue typedQueue, beaconMode bool) error {
|
|||
}
|
||||
sort.Sort(&peerCapacitySort{idles, caps})
|
||||
|
||||
var (
|
||||
progressed bool
|
||||
throttled bool
|
||||
queued = queue.pending()
|
||||
)
|
||||
var throttled bool
|
||||
for _, peer := range idles {
|
||||
// Short circuit if throttling activated or there are no more
|
||||
// queued tasks to be retrieved
|
||||
if throttled {
|
||||
break
|
||||
}
|
||||
if queued = queue.pending(); queued == 0 {
|
||||
if queued := queue.pending(); queued == 0 {
|
||||
break
|
||||
}
|
||||
// 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
|
||||
// have them.
|
||||
request, progress, throttle := queue.reserve(peer, queue.capacity(peer, d.peers.rates.TargetRoundTrip()))
|
||||
if progress {
|
||||
progressed = true
|
||||
}
|
||||
request, _, throttle := queue.reserve(peer, queue.capacity(peer, d.peers.rates.TargetRoundTrip()))
|
||||
if throttle {
|
||||
throttled = true
|
||||
throttleCounter.Inc(1)
|
||||
|
|
@ -207,11 +196,6 @@ func (d *Downloader) concurrentFetch(queue typedQueue, beaconMode bool) error {
|
|||
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
|
||||
select {
|
||||
|
|
@ -315,16 +299,6 @@ func (d *Downloader) concurrentFetch(queue typedQueue, beaconMode bool) error {
|
|||
queue.updateCapacity(peer, 0, 0)
|
||||
} else {
|
||||
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:
|
||||
|
|
|
|||
|
|
@ -78,7 +78,6 @@ func (q *bodyQueue) request(peer *peerConnection, req *fetchRequest, resCh chan
|
|||
if q.bodyFetchHook != nil {
|
||||
q.bodyFetchHook(req.Headers)
|
||||
}
|
||||
|
||||
hashes := make([]common.Hash, 0, len(req.Headers))
|
||||
for _, header := range req.Headers {
|
||||
hashes = append(hashes, header.Hash())
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -87,6 +87,15 @@ func newFetchResult(header *types.Header, fastSync bool) *fetchResult {
|
|||
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.
|
||||
func (f *fetchResult) SetBodyDone() {
|
||||
if v := f.pending.Load(); (v & (1 << bodyType)) != 0 {
|
||||
|
|
|
|||
|
|
@ -58,7 +58,6 @@ var pregenerated bool
|
|||
func init() {
|
||||
// Reduce some of the parameters to make the tester faster
|
||||
fullMaxForkAncestry = 10000
|
||||
lightMaxForkAncestry = 10000
|
||||
blockCacheMaxItems = 1024
|
||||
fsHeaderSafetyNet = 256
|
||||
fsHeaderContCheck = 500 * time.Millisecond
|
||||
|
|
|
|||
|
|
@ -42,7 +42,6 @@ import (
|
|||
"github.com/ethereum/go-ethereum/p2p"
|
||||
"github.com/ethereum/go-ethereum/p2p/enode"
|
||||
"github.com/ethereum/go-ethereum/triedb/pathdb"
|
||||
"golang.org/x/crypto/sha3"
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
@ -480,7 +479,7 @@ func (h *handler) BroadcastTransactions(txs types.Transactions) {
|
|||
|
||||
var (
|
||||
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)
|
||||
)
|
||||
for _, tx := range txs {
|
||||
|
|
|
|||
|
|
@ -164,7 +164,7 @@ func (t *pathTrie) deleteAccountNode(path []byte, inner bool) {
|
|||
} else {
|
||||
accountOuterLookupGauge.Inc(1)
|
||||
}
|
||||
if !rawdb.ExistsAccountTrieNode(t.db, path) {
|
||||
if !rawdb.HasAccountTrieNode(t.db, path) {
|
||||
return
|
||||
}
|
||||
if inner {
|
||||
|
|
@ -181,7 +181,7 @@ func (t *pathTrie) deleteStorageNode(path []byte, inner bool) {
|
|||
} else {
|
||||
storageOuterLookupGauge.Inc(1)
|
||||
}
|
||||
if !rawdb.ExistsStorageTrieNode(t.db, t.owner, path) {
|
||||
if !rawdb.HasStorageTrieNode(t.db, t.owner, path) {
|
||||
return
|
||||
}
|
||||
if inner {
|
||||
|
|
|
|||
|
|
@ -42,7 +42,6 @@ import (
|
|||
"github.com/ethereum/go-ethereum/rlp"
|
||||
"github.com/ethereum/go-ethereum/trie"
|
||||
"github.com/ethereum/go-ethereum/trie/trienode"
|
||||
"golang.org/x/crypto/sha3"
|
||||
)
|
||||
|
||||
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
|
||||
// that the serving node is missing
|
||||
hasher := sha3.NewLegacyKeccak256().(crypto.KeccakState)
|
||||
hasher := crypto.NewKeccakState()
|
||||
hash := make([]byte, 32)
|
||||
|
||||
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
|
||||
// that the serving node is missing
|
||||
var (
|
||||
hasher = sha3.NewLegacyKeccak256().(crypto.KeccakState)
|
||||
hasher = crypto.NewKeccakState()
|
||||
hash = make([]byte, 32)
|
||||
nodes = make([][]byte, len(req.hashes))
|
||||
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
|
||||
// that the serving node is missing
|
||||
hasher := sha3.NewLegacyKeccak256().(crypto.KeccakState)
|
||||
hasher := crypto.NewKeccakState()
|
||||
hash := make([]byte, 32)
|
||||
|
||||
codes := make([][]byte, len(req.hashes))
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@ func TestHashing(t *testing.T) {
|
|||
}
|
||||
}
|
||||
var new = func() {
|
||||
hasher := sha3.NewLegacyKeccak256().(crypto.KeccakState)
|
||||
hasher := crypto.NewKeccakState()
|
||||
var hash = make([]byte, 32)
|
||||
for i := 0; i < len(bytecodes); i++ {
|
||||
hasher.Reset()
|
||||
|
|
@ -96,7 +96,7 @@ func BenchmarkHashing(b *testing.B) {
|
|||
}
|
||||
}
|
||||
var new = func() {
|
||||
hasher := sha3.NewLegacyKeccak256().(crypto.KeccakState)
|
||||
hasher := crypto.NewKeccakState()
|
||||
var hash = make([]byte, 32)
|
||||
for i := 0; i < len(bytecodes); i++ {
|
||||
hasher.Reset()
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ import (
|
|||
"math/big"
|
||||
"slices"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
|
|
@ -114,7 +115,7 @@ type flatCallTracer struct {
|
|||
tracer *callTracer
|
||||
config flatCallTracerConfig
|
||||
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
|
||||
}
|
||||
|
||||
|
|
@ -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).
|
||||
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)
|
||||
|
||||
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
|
||||
// execute any code.
|
||||
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)
|
||||
|
||||
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) {
|
||||
if t.interrupt.Load() {
|
||||
return
|
||||
}
|
||||
t.tracer.OnTxStart(env, tx, from)
|
||||
// Update list of precompiles based on current block
|
||||
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) {
|
||||
if t.interrupt.Load() {
|
||||
return
|
||||
}
|
||||
t.tracer.OnTxEnd(receipt, err)
|
||||
}
|
||||
|
||||
|
|
@ -219,12 +232,13 @@ func (t *flatCallTracer) GetResult() (json.RawMessage, error) {
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return res, t.reason
|
||||
return res, t.tracer.reason
|
||||
}
|
||||
|
||||
// Stop terminates execution of the tracer at the first opportune moment.
|
||||
func (t *flatCallTracer) Stop(err error) {
|
||||
t.tracer.Stop(err)
|
||||
t.interrupt.Store(true)
|
||||
}
|
||||
|
||||
// isPrecompiled returns whether the addr is a precompile.
|
||||
|
|
|
|||
64
eth/tracers/native/call_flat_test.go
Normal file
64
eth/tracers/native/call_flat_test.go
Normal 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)
|
||||
}
|
||||
|
|
@ -191,7 +191,12 @@ func (ec *Client) getBlock(ctx context.Context, method string, args ...interface
|
|||
}
|
||||
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.
|
||||
|
|
|
|||
|
|
@ -88,8 +88,8 @@ type AncientReaderOp interface {
|
|||
// Ancients returns the ancient item numbers in the ancient store.
|
||||
Ancients() (uint64, error)
|
||||
|
||||
// Tail returns the number of first stored item in the freezer.
|
||||
// This number can also be interpreted as the total deleted item numbers.
|
||||
// Tail returns the number of first stored item in the ancient store.
|
||||
// This number can also be interpreted as the total deleted items.
|
||||
Tail() (uint64, error)
|
||||
|
||||
// AncientSize returns the ancient size of the specified category.
|
||||
|
|
@ -101,7 +101,7 @@ type AncientReader interface {
|
|||
AncientReaderOp
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
|
|
@ -141,11 +141,15 @@ type AncientWriteOp interface {
|
|||
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 {
|
||||
// AncientDatadir returns the path of root ancient directory. Empty string
|
||||
// will be returned if ancient store is not enabled at all. The returned
|
||||
// path can be used to construct the path of other freezers.
|
||||
// AncientDatadir returns the path of the ancient store directory.
|
||||
//
|
||||
// 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)
|
||||
}
|
||||
|
||||
|
|
@ -171,15 +175,23 @@ type Stater interface {
|
|||
}
|
||||
|
||||
// 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 {
|
||||
AncientReader
|
||||
AncientWriter
|
||||
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
|
||||
// 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 {
|
||||
Reader
|
||||
Writer
|
||||
|
|
|
|||
|
|
@ -151,7 +151,7 @@ func (e *Era) GetBlockByNumber(num uint64) (*types.Block, error) {
|
|||
if err := rlp.Decode(r, &body); err != nil {
|
||||
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.
|
||||
|
|
|
|||
|
|
@ -73,7 +73,7 @@ func (it *Iterator) Block() (*types.Block, error) {
|
|||
if err := rlp.Decode(it.inner.Body, &body); err != nil {
|
||||
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.
|
||||
|
|
|
|||
|
|
@ -1524,6 +1524,9 @@ func AccessList(ctx context.Context, b Backend, blockNrOrHash rpc.BlockNumberOrH
|
|||
prevTracer = logger.NewAccessListTracer(*args.AccessList, args.from(), to, precompiles)
|
||||
}
|
||||
for {
|
||||
if err := ctx.Err(); err != nil {
|
||||
return nil, 0, nil, err
|
||||
}
|
||||
// Retrieve the current access list to expand
|
||||
accessList := prevTracer.AccessList()
|
||||
log.Trace("Creating access list", "input", accessList)
|
||||
|
|
|
|||
|
|
@ -1348,7 +1348,7 @@ func TestRPCMarshalBlock(t *testing.T) {
|
|||
}
|
||||
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 {
|
||||
inclTx bool
|
||||
|
|
@ -1559,7 +1559,7 @@ func TestRPCGetBlockOrHeader(t *testing.T) {
|
|||
Address: common.Address{0x12, 0x34},
|
||||
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) {
|
||||
// Transfer from account[0] to account[1]
|
||||
|
|
|
|||
|
|
@ -78,7 +78,7 @@ func (bc *testBlockChain) CurrentBlock() *types.Header {
|
|||
}
|
||||
|
||||
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) {
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ import (
|
|||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||
"github.com/ethereum/go-ethereum/ethdb"
|
||||
"github.com/ethereum/go-ethereum/ethdb/memorydb"
|
||||
"github.com/ethereum/go-ethereum/event"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"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 err error
|
||||
if n.config.DataDir == "" {
|
||||
db = rawdb.NewMemoryDatabase()
|
||||
db, err = rawdb.NewDatabaseWithFreezer(memorydb.New(), "", namespace, readonly)
|
||||
} else {
|
||||
db, err = rawdb.Open(rawdb.OpenOptions{
|
||||
Type: n.config.DBEngine,
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ import (
|
|||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"maps"
|
||||
"math"
|
||||
"net"
|
||||
"sync"
|
||||
|
|
@ -215,10 +216,7 @@ func (sn *SimNode) ServeRPC(conn *websocket.Conn) error {
|
|||
// simulation_snapshot RPC method
|
||||
func (sn *SimNode) Snapshots() (map[string][]byte, error) {
|
||||
sn.lock.RLock()
|
||||
services := make(map[string]node.Lifecycle, len(sn.running))
|
||||
for name, service := range sn.running {
|
||||
services[name] = service
|
||||
}
|
||||
services := maps.Clone(sn.running)
|
||||
sn.lock.RUnlock()
|
||||
if len(services) == 0 {
|
||||
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 {
|
||||
sn.lock.RLock()
|
||||
defer sn.lock.RUnlock()
|
||||
services := make(map[string]node.Lifecycle, len(sn.running))
|
||||
for name, service := range sn.running {
|
||||
services[name] = service
|
||||
}
|
||||
return services
|
||||
return maps.Clone(sn.running)
|
||||
}
|
||||
|
||||
// Server returns the underlying p2p.Server
|
||||
|
|
|
|||
|
|
@ -566,17 +566,17 @@ func (c *ChainConfig) IsShanghai(num *big.Int, time uint64) bool {
|
|||
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 {
|
||||
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 {
|
||||
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 {
|
||||
return c.IsLondon(num) && isTimestampForked(c.VerkleTime, time)
|
||||
}
|
||||
|
|
@ -880,7 +880,7 @@ func newTimestampCompatError(what string, storedtime, newtime *uint64) *ConfigCo
|
|||
NewTime: newtime,
|
||||
RewindToTime: 0,
|
||||
}
|
||||
if rew != nil {
|
||||
if rew != nil && *rew != 0 {
|
||||
err.RewindToTime = *rew - 1
|
||||
}
|
||||
return err
|
||||
|
|
@ -890,7 +890,15 @@ func (err *ConfigCompatError) Error() string {
|
|||
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 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
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common/math"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestCheckCompatible(t *testing.T) {
|
||||
|
|
@ -137,3 +138,20 @@ func TestConfigRules(t *testing.T) {
|
|||
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)")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,7 +21,6 @@ import (
|
|||
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"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
|
||||
|
|
@ -38,7 +37,7 @@ var hasherPool = sync.Pool{
|
|||
New: func() interface{} {
|
||||
return &hasher{
|
||||
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),
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -28,7 +28,6 @@ import (
|
|||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/trie/trienode"
|
||||
"golang.org/x/crypto/sha3"
|
||||
)
|
||||
|
||||
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
|
||||
var (
|
||||
input = bytes.NewReader(data)
|
||||
spongeA = &spongeDb{sponge: sha3.NewLegacyKeccak256()}
|
||||
spongeA = &spongeDb{sponge: crypto.NewKeccakState()}
|
||||
dbA = newTestDatabase(rawdb.NewDatabase(spongeA), rawdb.HashScheme)
|
||||
trieA = NewEmpty(dbA)
|
||||
spongeB = &spongeDb{sponge: sha3.NewLegacyKeccak256()}
|
||||
spongeB = &spongeDb{sponge: crypto.NewKeccakState()}
|
||||
dbB = newTestDatabase(rawdb.NewDatabase(spongeB), rawdb.HashScheme)
|
||||
trieB = NewStackTrie(func(path []byte, hash common.Hash, blob []byte) {
|
||||
rawdb.WriteTrieNode(spongeB, common.Hash{}, path, hash, blob, dbB.Scheme())
|
||||
|
|
|
|||
34
trie/sync.go
34
trie/sync.go
|
|
@ -25,6 +25,7 @@ import (
|
|||
"github.com/ethereum/go-ethereum/common/prque"
|
||||
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/ethdb"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/metrics"
|
||||
|
|
@ -546,9 +547,9 @@ func (s *Sync) children(req *nodeRequest, object node) ([]*nodeRequest, error) {
|
|||
// the performance impact negligible.
|
||||
var exists bool
|
||||
if owner == (common.Hash{}) {
|
||||
exists = rawdb.ExistsAccountTrieNode(s.database, append(inner, key[:i]...))
|
||||
exists = rawdb.HasAccountTrieNode(s.database, append(inner, key[:i]...))
|
||||
} else {
|
||||
exists = rawdb.ExistsStorageTrieNode(s.database, owner, append(inner, key[:i]...))
|
||||
exists = rawdb.HasStorageTrieNode(s.database, owner, append(inner, key[:i]...))
|
||||
}
|
||||
if exists {
|
||||
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.
|
||||
var blob []byte
|
||||
var dbHash common.Hash
|
||||
if owner == (common.Hash{}) {
|
||||
blob, dbHash = rawdb.ReadAccountTrieNode(s.database, path)
|
||||
blob = rawdb.ReadAccountTrieNode(s.database, path)
|
||||
} 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
|
||||
return exists, inconsistent
|
||||
}
|
||||
|
|
@ -712,3 +714,23 @@ func ResolvePath(path []byte) (common.Hash, []byte) {
|
|||
}
|
||||
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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -886,7 +886,7 @@ func TestCommitSequence(t *testing.T) {
|
|||
} {
|
||||
addresses, accounts := makeAccounts(tc.count)
|
||||
// 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)
|
||||
trie := NewEmpty(db)
|
||||
// Fill the trie with elements
|
||||
|
|
@ -917,7 +917,7 @@ func TestCommitSequenceRandomBlobs(t *testing.T) {
|
|||
} {
|
||||
prng := rand.New(rand.NewSource(int64(i)))
|
||||
// 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)
|
||||
trie := NewEmpty(db)
|
||||
// Fill the trie with elements
|
||||
|
|
|
|||
|
|
@ -114,7 +114,12 @@ func (set *NodeSet) Merge(owner common.Hash, nodes map[string]*Node) error {
|
|||
set.updates -= 1
|
||||
}
|
||||
}
|
||||
set.AddNode([]byte(path), node)
|
||||
if node.IsDeleted() {
|
||||
set.deletes += 1
|
||||
} else {
|
||||
set.updates += 1
|
||||
}
|
||||
set.Nodes[path] = node
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
@ -130,16 +135,6 @@ func (set *NodeSet) Size() (int, int) {
|
|||
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.
|
||||
func (set *NodeSet) Summary() string {
|
||||
var out = new(strings.Builder)
|
||||
|
|
|
|||
61
trie/trienode/node_test.go
Normal file
61
trie/trienode/node_test.go
Normal 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
|
||||
}
|
||||
}
|
||||
|
|
@ -26,7 +26,6 @@ import (
|
|||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
"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
|
||||
|
|
@ -257,7 +256,7 @@ func deleteAccount(ctx *context, loader TrieLoader, addr common.Address) error {
|
|||
type hasher struct{ sha crypto.KeccakState }
|
||||
|
||||
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 {
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ import (
|
|||
"errors"
|
||||
|
||||
"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/trie"
|
||||
|
|
@ -48,9 +49,6 @@ var HashDefaults = &Config{
|
|||
// backend defines the methods needed to access/update trie nodes in different
|
||||
// state scheme.
|
||||
type backend interface {
|
||||
// Scheme returns the identifier of used storage scheme.
|
||||
Scheme() string
|
||||
|
||||
// Initialized returns an indicator if the state data is already initialized
|
||||
// according to the state scheme.
|
||||
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.
|
||||
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.
|
||||
|
|
|
|||
|
|
@ -25,6 +25,9 @@ type Reader interface {
|
|||
// Node retrieves the trie node blob with the provided trie identifier,
|
||||
// node path and the corresponding node hash. No error will be returned
|
||||
// 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)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -623,11 +623,6 @@ func (db *Database) Close() error {
|
|||
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.
|
||||
// An error will be returned if the requested state is not available.
|
||||
func (db *Database) Reader(root common.Hash) (*reader, error) {
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ import (
|
|||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/ethdb"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
|
|
@ -131,15 +132,15 @@ type Database struct {
|
|||
// readOnly is the flag whether the mutation is allowed to be applied.
|
||||
// It will be set automatically when the database is journaled during
|
||||
// the shutdown to reject all following unexpected mutations.
|
||||
readOnly bool // Flag if database is opened in read only mode
|
||||
waitSync bool // Flag if database is deactivated due to initial state sync
|
||||
isVerkle bool // Flag if database is used for verkle tree
|
||||
bufferSize int // Memory allowance (in bytes) for caching dirty nodes
|
||||
config *Config // Configuration for database
|
||||
diskdb ethdb.Database // Persistent storage for matured trie nodes
|
||||
tree *layerTree // The group for all known layers
|
||||
freezer *rawdb.ResettableFreezer // Freezer for storing trie histories, nil possible in tests
|
||||
lock sync.RWMutex // Lock to prevent mutations from happening at the same time
|
||||
readOnly bool // Flag if database is opened in read only mode
|
||||
waitSync bool // Flag if database is deactivated due to initial state sync
|
||||
isVerkle bool // Flag if database is used for verkle tree
|
||||
bufferSize int // Memory allowance (in bytes) for caching dirty nodes
|
||||
config *Config // Configuration for database
|
||||
diskdb ethdb.Database // Persistent storage for matured trie nodes
|
||||
tree *layerTree // The group for all known layers
|
||||
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
|
||||
}
|
||||
|
||||
// New attempts to load an already existing layer from a persistent key-value
|
||||
|
|
@ -162,45 +163,10 @@ func New(diskdb ethdb.Database, config *Config, isVerkle bool) *Database {
|
|||
// and in-memory layer journal.
|
||||
db.tree = newLayerTree(db.loadLayers())
|
||||
|
||||
// Open the freezer for state history if the passed database contains an
|
||||
// ancient store. Otherwise, all the relevant functionalities are disabled.
|
||||
//
|
||||
// Because the freezer can only be opened once at the same time, this
|
||||
// mechanism also ensures that at most one **non-readOnly** database
|
||||
// is opened at the same time to prevent accidental mutation.
|
||||
if ancient, err := diskdb.AncientDatadir(); err == nil && ancient != "" && !db.readOnly {
|
||||
freezer, err := rawdb.NewStateFreezer(ancient, false)
|
||||
if err != nil {
|
||||
log.Crit("Failed to open state history freezer", "err", err)
|
||||
}
|
||||
db.freezer = freezer
|
||||
|
||||
diskLayerID := db.tree.bottom().stateID()
|
||||
if diskLayerID == 0 {
|
||||
// Reset the entire state histories in case the trie database is
|
||||
// not initialized yet, as these state histories are not expected.
|
||||
frozen, err := db.freezer.Ancients()
|
||||
if err != nil {
|
||||
log.Crit("Failed to retrieve head of state history", "err", err)
|
||||
}
|
||||
if frozen != 0 {
|
||||
err := db.freezer.Reset()
|
||||
if err != nil {
|
||||
log.Crit("Failed to reset state histories", "err", err)
|
||||
}
|
||||
log.Info("Truncated extraneous state history")
|
||||
}
|
||||
} else {
|
||||
// Truncate the extra state histories above in freezer in case
|
||||
// it's not aligned with the disk layer.
|
||||
pruned, err := truncateFromHead(db.diskdb, freezer, diskLayerID)
|
||||
if err != nil {
|
||||
log.Crit("Failed to truncate extra state histories", "err", err)
|
||||
}
|
||||
if pruned != 0 {
|
||||
log.Warn("Truncated extra state histories", "number", pruned)
|
||||
}
|
||||
}
|
||||
// Repair the state history, which might not be aligned with the state
|
||||
// in the key-value store due to an unclean shutdown.
|
||||
if err := db.repairHistory(); err != nil {
|
||||
log.Crit("Failed to repair pathdb", "err", err)
|
||||
}
|
||||
// Disable database in case node is still in the initial state sync stage.
|
||||
if rawdb.ReadSnapSyncStatusFlag(diskdb) == rawdb.StateSyncRunning && !db.readOnly {
|
||||
|
|
@ -211,6 +177,55 @@ func New(diskdb ethdb.Database, config *Config, isVerkle bool) *Database {
|
|||
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)
|
||||
if err != nil {
|
||||
log.Crit("Failed to open state history freezer", "err", err)
|
||||
}
|
||||
db.freezer = freezer
|
||||
|
||||
// Reset the entire state histories if the trie database is not initialized
|
||||
// yet. This action is necessary because these state histories are not
|
||||
// expected to exist without an initialized trie database.
|
||||
id := db.tree.bottom().stateID()
|
||||
if id == 0 {
|
||||
frozen, err := db.freezer.Ancients()
|
||||
if err != nil {
|
||||
log.Crit("Failed to retrieve head of state history", "err", err)
|
||||
}
|
||||
if frozen != 0 {
|
||||
err := db.freezer.Reset()
|
||||
if err != nil {
|
||||
log.Crit("Failed to reset state histories", "err", err)
|
||||
}
|
||||
log.Info("Truncated extraneous state history")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
// Truncate the extra state histories above in freezer in case it's not
|
||||
// aligned with the disk layer. It might happen after a unclean shutdown.
|
||||
pruned, err := truncateFromHead(db.diskdb, db.freezer, id)
|
||||
if err != nil {
|
||||
log.Crit("Failed to truncate extra state histories", "err", err)
|
||||
}
|
||||
if pruned != 0 {
|
||||
log.Warn("Truncated extra state histories", "number", pruned)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Update adds a new layer into the tree, if that can be linked to an existing
|
||||
// old parent. It is disallowed to insert a disk layer (the origin of all). Apart
|
||||
// from that this function will flatten the extra diff layers at bottom into disk
|
||||
|
|
@ -292,8 +307,10 @@ func (db *Database) Enable(root common.Hash) error {
|
|||
}
|
||||
// Ensure the provided state root matches the stored one.
|
||||
root = types.TrieRootHash(root)
|
||||
_, stored := rawdb.ReadAccountTrieNode(db.diskdb, nil)
|
||||
stored = types.TrieRootHash(stored)
|
||||
stored := types.EmptyRootHash
|
||||
if blob := rawdb.ReadAccountTrieNode(db.diskdb, nil); len(blob) > 0 {
|
||||
stored = crypto.Keccak256Hash(blob)
|
||||
}
|
||||
if 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)
|
||||
}
|
||||
|
||||
// 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
|
||||
// assumes the db.lock is already held.
|
||||
func (db *Database) modifyAllowed() error {
|
||||
|
|
|
|||
|
|
@ -474,7 +474,7 @@ func TestDisable(t *testing.T) {
|
|||
tester := newTester(t, 0)
|
||||
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 {
|
||||
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)
|
||||
}
|
||||
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
|
||||
blob := rawdb.ReadTrieJournal(tester.db.diskdb)
|
||||
|
|
|
|||
|
|
@ -70,10 +70,10 @@ func benchmarkSearch(b *testing.B, depth int, total int) {
|
|||
blob = testrand.Bytes(100)
|
||||
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 {
|
||||
npath = common.CopyBytes(path)
|
||||
nblob = common.CopyBytes(node.Blob)
|
||||
nblob = common.CopyBytes(blob)
|
||||
}
|
||||
}
|
||||
return newDiffLayer(parent, common.Hash{}, 0, 0, nodes, nil)
|
||||
|
|
@ -116,7 +116,7 @@ func BenchmarkPersist(b *testing.B) {
|
|||
blob = testrand.Bytes(100)
|
||||
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)
|
||||
}
|
||||
|
|
@ -154,7 +154,7 @@ func BenchmarkJournal(b *testing.B) {
|
|||
blob = testrand.Bytes(100)
|
||||
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.
|
||||
return newDiffLayer(parent, common.Hash{}, 0, 0, nodes, nil)
|
||||
|
|
|
|||
|
|
@ -27,7 +27,6 @@ import (
|
|||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/trie/trienode"
|
||||
"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.
|
||||
|
|
@ -117,12 +116,12 @@ func (dl *diskLayer) node(owner common.Hash, path []byte, depth int) ([]byte, co
|
|||
dirtyMissMeter.Mark(1)
|
||||
|
||||
// Try to retrieve the trie node from the clean memory cache
|
||||
h := newHasher()
|
||||
defer h.release()
|
||||
|
||||
key := cacheKey(owner, path)
|
||||
if dl.cleans != nil {
|
||||
if blob := dl.cleans.Get(nil, key); len(blob) > 0 {
|
||||
h := newHasher()
|
||||
defer h.release()
|
||||
|
||||
cleanHitMeter.Mark(1)
|
||||
cleanReadMeter.Mark(int64(len(blob)))
|
||||
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)
|
||||
}
|
||||
// Try to retrieve the trie node from the disk.
|
||||
var (
|
||||
nBlob []byte
|
||||
nHash common.Hash
|
||||
)
|
||||
var blob []byte
|
||||
if owner == (common.Hash{}) {
|
||||
nBlob, nHash = rawdb.ReadAccountTrieNode(dl.db.diskdb, path)
|
||||
blob = rawdb.ReadAccountTrieNode(dl.db.diskdb, path)
|
||||
} 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 {
|
||||
dl.cleans.Set(key, nBlob)
|
||||
cleanWriteMeter.Mark(int64(len(nBlob)))
|
||||
if dl.cleans != nil && len(blob) > 0 {
|
||||
dl.cleans.Set(key, blob)
|
||||
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
|
||||
|
|
@ -303,7 +300,7 @@ func (dl *diskLayer) resetCache() {
|
|||
type hasher struct{ sha crypto.KeccakState }
|
||||
|
||||
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 {
|
||||
|
|
|
|||
|
|
@ -472,8 +472,8 @@ func (h *history) decode(accountData, storageData, accountIndexes, storageIndexe
|
|||
}
|
||||
|
||||
// readHistory reads and decodes the state history object by the given id.
|
||||
func readHistory(freezer *rawdb.ResettableFreezer, id uint64) (*history, error) {
|
||||
blob := rawdb.ReadStateHistoryMeta(freezer, id)
|
||||
func readHistory(reader ethdb.AncientReader, id uint64) (*history, error) {
|
||||
blob := rawdb.ReadStateHistoryMeta(reader, id)
|
||||
if len(blob) == 0 {
|
||||
return nil, fmt.Errorf("state history not found %d", id)
|
||||
}
|
||||
|
|
@ -483,10 +483,10 @@ func readHistory(freezer *rawdb.ResettableFreezer, id uint64) (*history, error)
|
|||
}
|
||||
var (
|
||||
dec = history{meta: &m}
|
||||
accountData = rawdb.ReadStateAccountHistory(freezer, id)
|
||||
storageData = rawdb.ReadStateStorageHistory(freezer, id)
|
||||
accountIndexes = rawdb.ReadStateAccountIndex(freezer, id)
|
||||
storageIndexes = rawdb.ReadStateStorageIndex(freezer, id)
|
||||
accountData = rawdb.ReadStateAccountHistory(reader, id)
|
||||
storageData = rawdb.ReadStateStorageHistory(reader, id)
|
||||
accountIndexes = rawdb.ReadStateAccountIndex(reader, id)
|
||||
storageIndexes = rawdb.ReadStateStorageIndex(reader, id)
|
||||
)
|
||||
if err := dec.decode(accountData, storageData, accountIndexes, storageIndexes); err != nil {
|
||||
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.
|
||||
func writeHistory(freezer *rawdb.ResettableFreezer, dl *diffLayer) error {
|
||||
func writeHistory(writer ethdb.AncientWriter, dl *diffLayer) error {
|
||||
// Short circuit if state set is not available.
|
||||
if dl.states == nil {
|
||||
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))
|
||||
|
||||
// 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))
|
||||
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
|
||||
// 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 {
|
||||
number := count
|
||||
if number > 10000 {
|
||||
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 {
|
||||
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
|
||||
// parameters. It returns the number of items removed from the head.
|
||||
func truncateFromHead(db ethdb.Batcher, freezer *rawdb.ResettableFreezer, nhead uint64) (int, error) {
|
||||
ohead, err := freezer.Ancients()
|
||||
func truncateFromHead(db ethdb.Batcher, store ethdb.AncientStore, nhead uint64) (int, error) {
|
||||
ohead, err := store.Ancients()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
otail, err := freezer.Tail()
|
||||
otail, err := store.Tail()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
|
@ -566,7 +566,7 @@ func truncateFromHead(db ethdb.Batcher, freezer *rawdb.ResettableFreezer, nhead
|
|||
return 0, nil
|
||||
}
|
||||
// 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 {
|
||||
return 0, err
|
||||
}
|
||||
|
|
@ -581,7 +581,7 @@ func truncateFromHead(db ethdb.Batcher, freezer *rawdb.ResettableFreezer, nhead
|
|||
if err := batch.Write(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
ohead, err = freezer.TruncateHead(nhead)
|
||||
ohead, err = store.TruncateHead(nhead)
|
||||
if err != nil {
|
||||
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
|
||||
// parameters. It returns the number of items removed from the tail.
|
||||
func truncateFromTail(db ethdb.Batcher, freezer *rawdb.ResettableFreezer, ntail uint64) (int, error) {
|
||||
ohead, err := freezer.Ancients()
|
||||
func truncateFromTail(db ethdb.Batcher, store ethdb.AncientStore, ntail uint64) (int, error) {
|
||||
ohead, err := store.Ancients()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
otail, err := freezer.Tail()
|
||||
otail, err := store.Tail()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
|
@ -608,7 +608,7 @@ func truncateFromTail(db ethdb.Batcher, freezer *rawdb.ResettableFreezer, ntail
|
|||
return 0, nil
|
||||
}
|
||||
// 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 {
|
||||
return 0, err
|
||||
}
|
||||
|
|
@ -623,7 +623,7 @@ func truncateFromTail(db ethdb.Batcher, freezer *rawdb.ResettableFreezer, ntail
|
|||
if err := batch.Write(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
otail, err = freezer.TruncateTail(ntail)
|
||||
otail, err = store.TruncateTail(ntail)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ import (
|
|||
"time"
|
||||
|
||||
"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"
|
||||
)
|
||||
|
||||
|
|
@ -34,7 +34,7 @@ type HistoryStats struct {
|
|||
}
|
||||
|
||||
// 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.
|
||||
tail, err := freezer.Tail()
|
||||
if err != nil {
|
||||
|
|
@ -60,7 +60,7 @@ func sanitizeRange(start, end uint64, freezer *rawdb.ResettableFreezer) (uint64,
|
|||
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 (
|
||||
stats = &HistoryStats{}
|
||||
init = time.Now()
|
||||
|
|
@ -96,7 +96,7 @@ func inspectHistory(freezer *rawdb.ResettableFreezer, start, end uint64, onHisto
|
|||
}
|
||||
|
||||
// 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) {
|
||||
blob, exists := h.accounts[address]
|
||||
if !exists {
|
||||
|
|
@ -108,7 +108,7 @@ func accountHistory(freezer *rawdb.ResettableFreezer, address common.Address, st
|
|||
}
|
||||
|
||||
// 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) {
|
||||
slots, exists := h.storages[address]
|
||||
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.
|
||||
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.
|
||||
tail, err := freezer.Tail()
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
if exist && len(blob) == 0 {
|
||||
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 {
|
||||
checkHistory(t, db, freezer, i, roots[j], exist)
|
||||
}
|
||||
|
|
@ -129,7 +129,7 @@ func TestTruncateHeadHistory(t *testing.T) {
|
|||
roots []common.Hash
|
||||
hs = makeHistories(10)
|
||||
db = rawdb.NewMemoryDatabase()
|
||||
freezer, _ = openFreezer(t.TempDir(), false)
|
||||
freezer, _ = rawdb.NewStateFreezer(t.TempDir(), false)
|
||||
)
|
||||
defer freezer.Close()
|
||||
|
||||
|
|
@ -157,7 +157,7 @@ func TestTruncateTailHistory(t *testing.T) {
|
|||
roots []common.Hash
|
||||
hs = makeHistories(10)
|
||||
db = rawdb.NewMemoryDatabase()
|
||||
freezer, _ = openFreezer(t.TempDir(), false)
|
||||
freezer, _ = rawdb.NewStateFreezer(t.TempDir(), false)
|
||||
)
|
||||
defer freezer.Close()
|
||||
|
||||
|
|
@ -200,7 +200,7 @@ func TestTruncateTailHistories(t *testing.T) {
|
|||
roots []common.Hash
|
||||
hs = makeHistories(10)
|
||||
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()
|
||||
|
||||
|
|
@ -228,7 +228,7 @@ func TestTruncateOutOfRange(t *testing.T) {
|
|||
var (
|
||||
hs = makeHistories(10)
|
||||
db = rawdb.NewMemoryDatabase()
|
||||
freezer, _ = openFreezer(t.TempDir(), false)
|
||||
freezer, _ = rawdb.NewStateFreezer(t.TempDir(), false)
|
||||
)
|
||||
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 {
|
||||
if len(a) != len(b) {
|
||||
return false
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
func (db *Database) loadLayers() layer {
|
||||
// Retrieve the root node of persistent state.
|
||||
_, root := rawdb.ReadAccountTrieNode(db.diskdb, nil)
|
||||
root = types.TrieRootHash(root)
|
||||
|
||||
var root = types.EmptyRootHash
|
||||
if blob := rawdb.ReadAccountTrieNode(db.diskdb, nil); len(blob) > 0 {
|
||||
root = crypto.Keccak256Hash(blob)
|
||||
}
|
||||
// Load the layers by resolving the journal
|
||||
head, err := db.loadJournal(root)
|
||||
if err == nil {
|
||||
|
|
@ -361,14 +362,13 @@ func (db *Database) Journal(root common.Hash) error {
|
|||
if err := rlp.Encode(journal, journalVersion); err != nil {
|
||||
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
|
||||
// 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
|
||||
}
|
||||
// Finally write out the journal of each layer in reverse order.
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
package pathdb
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"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
|
||||
// after merging, thus the ownership of nodes map should still belong
|
||||
// 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 {
|
||||
current[path] = n
|
||||
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"
|
||||
// node occurs which is not present in buffer.
|
||||
var nhash common.Hash
|
||||
var blob []byte
|
||||
if owner == (common.Hash{}) {
|
||||
_, nhash = rawdb.ReadAccountTrieNode(db, []byte(path))
|
||||
blob = rawdb.ReadAccountTrieNode(db, []byte(path))
|
||||
} else {
|
||||
_, nhash = rawdb.ReadStorageTrieNode(db, owner, []byte(path))
|
||||
blob = rawdb.ReadStorageTrieNode(db, owner, []byte(path))
|
||||
}
|
||||
// Ignore the clean node in the case described above.
|
||||
if nhash == n.Hash {
|
||||
if bytes.Equal(blob, n.Blob) {
|
||||
continue
|
||||
}
|
||||
panic(fmt.Sprintf("non-existent node (%x %v) blob: %v", owner, path, crypto.Keccak256Hash(n.Blob).Hex()))
|
||||
|
|
|
|||
Loading…
Reference in a new issue