erigon-pulse/trie/resolver.go
Alex Sharov ce96cf75b2
Intermediate hash phase 3 (#377)
* #remove debug prints

* remove storage-mode="i"

* minnet re-execute hack with checkpoints

* minnet re-execute hack with checkpoints

* rollback to master setup

* mainnet re-exec hack

* rollback some changes

* v0 of "push down" functionality

* move all logic to own functions

* handle case when re-created account already has some storage

* clear path for storage

* try to rely on tree structure (but maybe need to rely on DB because can be intra-block re-creations of account)

* fix some bugs with indexes, moving to tests

* tests added

* make linter happy

* make linter happy

* simplify logic

* adjust comparison of keys with and without incarnation

* test for keyIsBefore

* test for keyIsBefore

* better nibbles alignment

* better nibbles alignment

* cleanup

* continue work on tests

* simplify test

* check tombstone existence before pushing it down.

* put tombstone only when account deleted, not created

* put tombstone only when account has storage

* make linter happy

* test for storage resolver

* make fixedbytes work without incarnation

* fix panic on short keys

* use special comparison only when working with keys from cache

* add blockNr for better tracing

* fix: incorrect tombstone check

* fix: incorrect tombstone check

* trigger ci

* hack for problem block

* more test-cases

* add test case for too long keys

* speedup cached resolver by removing bucket creation transaction

* remove parent type check in pruning, remove unused copy from mutation.put

* dump resolving info on fail

* dump resolving info on fail

* set tombstone everytime for now to check if it will help

* on unload: check parent type, not type of node

* fix wrong order of checking node type

* fix wrong order of checking node type

* rebase to new master

* make linter happy

* rebase to new master

* place tombstone only if acc has storage

* rebase master

* rebase master

* rebase master

* rebase master

Co-authored-by: alex.sharov <alex.sharov@lazada.com>
2020-03-11 10:31:49 +00:00

231 lines
6.7 KiB
Go

package trie
import (
"bytes"
"fmt"
"sort"
"github.com/ledgerwatch/turbo-geth/common"
"github.com/ledgerwatch/turbo-geth/common/debug"
"github.com/ledgerwatch/turbo-geth/ethdb"
"github.com/ledgerwatch/turbo-geth/log"
)
var emptyHash [32]byte
func (t *Trie) Rebuild(db ethdb.Database, blockNr uint64) error {
if t.root == nil {
return nil
}
n, ok := t.root.(hashNode)
if !ok {
return fmt.Errorf("Rebuild: Expected hashNode, got %T", t.root)
}
if err := t.rebuildHashes(db, nil, 0, blockNr, true, n); err != nil {
return err
}
log.Info("Rebuilt top of account trie and verified", "root hash", n)
return nil
}
// Resolver looks up (resolves) some keys and corresponding values from a database.
// One resolver per trie (prefix).
// See also ResolveRequest in trie.go
type Resolver struct {
accounts bool // Is this a resolver for accounts or for storage
historical bool
collectWitnesses bool // if true, stores witnesses for all the subtries that are being resolved
blockNr uint64
topLevels int // How many top levels of the trie to keep (not roll into hashes)
requests []*ResolveRequest
witnesses []*Witness // list of witnesses for resolved subtries, nil if `collectWitnesses` is false
}
func NewResolver(topLevels int, forAccounts bool, blockNr uint64) *Resolver {
tr := Resolver{
accounts: forAccounts,
requests: []*ResolveRequest{},
blockNr: blockNr,
topLevels: topLevels,
}
return &tr
}
func (tr *Resolver) Reset(topLevels int, forAccounts bool, blockNr uint64) {
tr.topLevels = topLevels
tr.accounts = forAccounts
tr.blockNr = blockNr
tr.requests = tr.requests[:0]
tr.witnesses = nil
tr.collectWitnesses = false
tr.historical = false
}
func (tr *Resolver) CollectWitnesses(c bool) {
tr.collectWitnesses = c
}
// PopCollectedWitnesses returns all the collected witnesses and clears the storage in this resolver
func (tr *Resolver) PopCollectedWitnesses() []*Witness {
result := tr.witnesses
tr.witnesses = nil
return result
}
func (tr *Resolver) SetHistorical(h bool) {
tr.historical = h
}
// Resolver implements sort.Interface
// and sorts by resolve requests
// (more general requests come first)
func (tr *Resolver) Len() int {
return len(tr.requests)
}
func min(a, b int) int {
if a < b {
return a
}
return b
}
func (tr *Resolver) Less(i, j int) bool {
ci := tr.requests[i]
cj := tr.requests[j]
m := min(ci.resolvePos, cj.resolvePos)
c := bytes.Compare(ci.contract, cj.contract)
if c != 0 {
return c < 0
}
c = bytes.Compare(ci.resolveHex[:m], cj.resolveHex[:m])
if c != 0 {
return c < 0
}
return ci.resolvePos < cj.resolvePos
}
func (tr *Resolver) Swap(i, j int) {
tr.requests[i], tr.requests[j] = tr.requests[j], tr.requests[i]
}
func (tr *Resolver) AddRequest(req *ResolveRequest) {
tr.requests = append(tr.requests, req)
}
func (tr *Resolver) Print() {
for _, req := range tr.requests {
fmt.Printf("%s\n", req.String())
}
}
// Various values of the account field set
const (
AccountFieldNonceOnly uint32 = 0x01
AccountFieldBalanceOnly uint32 = 0x02
AccountFieldRootOnly uint32 = 0x04
AccountFieldCodeHashOnly uint32 = 0x08
AccountFieldSSizeOnly uint32 = 0x10
AccountFieldSetNotAccount uint32 = 0x00
AccountFieldSetNotContract uint32 = 0x03 // Bit 0 is set for nonce, bit 1 is set for balance
AccountFieldSetContract uint32 = 0x0f // Bits 0-3 are set for nonce, balance, storageRoot and codeHash
AccountFieldSetContractWithSize uint32 = 0x1f // Bits 0-4 are set for nonce, balance, storageRoot, codeHash and storageSize
)
// ResolveWithDb resolves and hooks subtries using a state database.
func (tr *Resolver) ResolveWithDb(db ethdb.Database, blockNr uint64) error {
if debug.IsIntermediateTrieHash() && !tr.historical {
return tr.ResolveStatefulCached(db, blockNr)
}
return tr.ResolveStateful(db, blockNr)
}
func (tr *Resolver) ResolveStateful(db ethdb.Database, blockNr uint64) error {
var hf hookFunction
if tr.collectWitnesses {
hf = tr.extractWitnessAndHookSubtrie
} else {
hf = hookSubtrie
}
sort.Stable(tr)
resolver := NewResolverStateful(tr.topLevels, tr.requests, hf)
return resolver.RebuildTrie(db, blockNr, tr.accounts, tr.historical)
}
func (tr *Resolver) ResolveStatefulCached(db ethdb.Database, blockNr uint64) error {
var hf hookFunction
if tr.collectWitnesses {
hf = tr.extractWitnessAndHookSubtrie
} else {
hf = hookSubtrie
}
sort.Stable(tr)
resolver := NewResolverStatefulCached(tr.topLevels, tr.requests, hf)
return resolver.RebuildTrie(db, blockNr, tr.accounts, tr.historical)
}
// ResolveStateless resolves and hooks subtries using a witnesses database instead of
// the state DB.
func (tr *Resolver) ResolveStateless(db WitnessStorage, blockNr uint64, trieLimit uint32, startPos int64) (int64, error) {
sort.Stable(tr)
resolver := NewResolverStateless(tr.requests, hookSubtrie)
return resolver.RebuildTrie(db, blockNr, trieLimit, startPos)
}
func hookSubtrie(currentReq *ResolveRequest, hbRoot node, hbHash common.Hash) error {
if currentReq.RequiresRLP {
hasher := newHasher(false)
defer returnHasherToPool(hasher)
h, err := hasher.hashChildren(hbRoot, 0)
if err != nil {
return err
}
currentReq.NodeRLP = h
}
var hookKey []byte
if currentReq.contract == nil {
hookKey = currentReq.resolveHex[:currentReq.resolvePos]
} else {
contractHex := keybytesToHex(currentReq.contract)
contractHex = contractHex[:len(contractHex)-1-16] // Remove terminal nibble and incarnation bytes
hookKey = append(contractHex, currentReq.resolveHex[:currentReq.resolvePos]...)
}
//fmt.Printf("hookKey: %x, %s\n", hookKey, hbRoot.fstring(""))
currentReq.t.hook(hookKey, hbRoot)
if len(currentReq.resolveHash) > 0 && !bytes.Equal(currentReq.resolveHash, hbHash[:]) {
return fmt.Errorf("mismatching hash: %s %x for prefix %x, resolveHex %x, resolvePos %d",
currentReq.resolveHash, hbHash, currentReq.contract, currentReq.resolveHex, currentReq.resolvePos)
}
return nil
}
func (tr *Resolver) extractWitnessAndHookSubtrie(currentReq *ResolveRequest, hbRoot node, hbHash common.Hash) error {
if tr.witnesses == nil {
tr.witnesses = make([]*Witness, 0)
}
witness, err := extractWitnessFromRootNode(hbRoot, tr.blockNr, false /*tr.hb.trace*/, nil, nil)
if err != nil {
return fmt.Errorf("error while extracting witness for resolver: %w", err)
}
tr.witnesses = append(tr.witnesses, witness)
return hookSubtrie(currentReq, hbRoot, hbHash)
}
func (t *Trie) rebuildHashes(db ethdb.Database, key []byte, pos int, blockNr uint64, accounts bool, expected hashNode) error {
req := t.NewResolveRequest(nil, key, pos, expected)
r := NewResolver(5, accounts, blockNr)
r.AddRequest(req)
return r.ResolveWithDb(db, blockNr)
}