erigon-pulse/trie/resolver.go
2020-04-05 07:33:06 +01:00

224 lines
6.5 KiB
Go

package trie
import (
"bytes"
"fmt"
"sort"
"github.com/ledgerwatch/turbo-geth/common"
"github.com/ledgerwatch/turbo-geth/ethdb"
)
var emptyHash [32]byte
type ResolveFunc func(*Resolver) error
// 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
codeRequests []*ResolveRequestForCode
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{},
codeRequests: []*ResolveRequestForCode{},
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.codeRequests = tr.codeRequests[: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
}
// AddCodeRequest add a request for code resolution
func (tr *Resolver) AddCodeRequest(req *ResolveRequestForCode) {
tr.codeRequests = append(tr.codeRequests, req)
}
// 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, trace bool) error {
if !tr.historical {
return tr.ResolveStatefulCached(db, blockNr, trace)
}
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)
if err := resolver.RebuildTrie(db, blockNr, tr.accounts, tr.historical); err != nil {
return err
}
return resolver.AttachRequestedCode(db, tr.codeRequests)
}
func (tr *Resolver) ResolveStatefulCached(db ethdb.Database, blockNr uint64, trace bool) error {
var hf hookFunction
if tr.collectWitnesses {
hf = tr.extractWitnessAndHookSubtrie
} else {
hf = hookSubtrie
}
sort.Stable(tr)
resolver := NewResolverStatefulCached(tr.topLevels, tr.requests, hf)
if err := resolver.RebuildTrie(db, blockNr, tr.accounts, tr.historical, trace); err != nil {
return err
}
return resolver.AttachRequestedCode(db, tr.codeRequests)
}
// 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)
// we expect CodeNodes to be already attached to the trie in stateless resolution
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)
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)
}