erigon-pulse/trie/resolver_stateful.go
2020-04-11 08:22:23 +01:00

304 lines
8.6 KiB
Go

package trie
import (
"bytes"
"fmt"
"runtime/debug"
"strings"
"time"
"github.com/ledgerwatch/turbo-geth/common"
"github.com/ledgerwatch/turbo-geth/common/dbutils"
"github.com/ledgerwatch/turbo-geth/core/types/accounts"
"github.com/ledgerwatch/turbo-geth/ethdb"
"github.com/ledgerwatch/turbo-geth/metrics"
"github.com/ledgerwatch/turbo-geth/trie/rlphacks"
)
var (
trieResolveStatefulTimer = metrics.NewRegisteredTimer("trie/resolve/stateful", nil)
)
type hookFunction func(*ResolveRequest, node, common.Hash) error
type ResolverStateful struct {
rss []*ResolveSet
curr bytes.Buffer // Current key for the structure generation algorithm, as well as the input tape for the hash builder
succ bytes.Buffer
value bytes.Buffer // Current value to be used as the value tape for the hash builder
groups []uint16
reqIndices []int // Indices pointing back to request slice from slices returned by PrepareResolveParams
hb *HashBuilder
topLevels int // How many top levels of the trie to keep (not roll into hashes)
currentReq *ResolveRequest // Request currently being handled
currentRs *ResolveSet // ResolveSet currently being used
keyIdx int
fieldSet uint32 // fieldSet for the next invocation of genStructStep
a accounts.Account
leafData GenStructStepLeafData
accData GenStructStepAccountData
requests []*ResolveRequest
roots []node // roots of the tries that are being built
hookFunction hookFunction
}
func NewResolverStateful(topLevels int, requests []*ResolveRequest, hookFunction hookFunction) *ResolverStateful {
return &ResolverStateful{
topLevels: topLevels,
hb: NewHashBuilder(false),
reqIndices: []int{},
requests: requests,
hookFunction: hookFunction,
}
}
// Reset prepares the Resolver for reuse
func (tr *ResolverStateful) Reset(topLevels int, requests []*ResolveRequest, hookFunction hookFunction) {
tr.topLevels = topLevels
tr.requests = tr.requests[:0]
tr.reqIndices = tr.reqIndices[:0]
tr.keyIdx = 0
tr.currentReq = nil
tr.currentRs = nil
tr.fieldSet = 0
tr.rss = tr.rss[:0]
tr.requests = requests
tr.hookFunction = hookFunction
tr.curr.Reset()
tr.succ.Reset()
tr.value.Reset()
tr.groups = tr.groups[:0]
tr.a.Reset()
tr.hb.Reset()
}
func (tr *ResolverStateful) PopRoots() []node {
roots := tr.roots
tr.roots = nil
return roots
}
// PrepareResolveParams prepares information for the MultiWalk
func (tr *ResolverStateful) PrepareResolveParams() ([][]byte, []uint) {
// Remove requests strictly contained in the preceding ones
startkeys := [][]byte{}
fixedbits := []uint{}
tr.rss = tr.rss[:0]
if len(tr.requests) == 0 {
return startkeys, fixedbits
}
var prevReq *ResolveRequest
for i, req := range tr.requests {
if prevReq == nil ||
!bytes.Equal(req.contract, prevReq.contract) ||
!bytes.Equal(req.resolveHex[:req.resolvePos], prevReq.resolveHex[:prevReq.resolvePos]) {
tr.reqIndices = append(tr.reqIndices, i)
pLen := len(req.contract)
key := make([]byte, pLen+32)
copy(key[:], req.contract)
decodeNibbles(req.resolveHex[:req.resolvePos], key[pLen:])
startkeys = append(startkeys, key)
req.extResolvePos = req.resolvePos + 2*pLen
fixedbits = append(fixedbits, uint(4*req.extResolvePos))
prevReq = req
var minLength int
if req.resolvePos >= tr.topLevels {
minLength = 0
} else {
minLength = tr.topLevels - req.resolvePos
}
rs := NewResolveSet(minLength)
tr.rss = append(tr.rss, rs)
rs.AddHex(req.resolveHex[req.resolvePos:])
} else {
rs := tr.rss[len(tr.rss)-1]
rs.AddHex(req.resolveHex[req.resolvePos:])
}
}
tr.currentReq = tr.requests[tr.reqIndices[0]]
tr.currentRs = tr.rss[0]
return startkeys, fixedbits
}
func (tr *ResolverStateful) finaliseRoot() error {
tr.curr.Reset()
tr.curr.Write(tr.succ.Bytes())
tr.succ.Reset()
if tr.curr.Len() > 0 {
var err error
var data GenStructStepData
if tr.fieldSet == 0 {
tr.leafData.Value = rlphacks.RlpSerializableBytes(tr.value.Bytes())
data = &tr.leafData
} else {
tr.accData.FieldSet = tr.fieldSet
tr.accData.StorageSize = tr.a.StorageSize
tr.accData.Balance.Set(&tr.a.Balance)
tr.accData.Nonce = tr.a.Nonce
tr.accData.Incarnation = tr.a.Incarnation
data = &tr.accData
}
tr.groups, err = GenStructStep(tr.currentRs.HashOnly, tr.curr.Bytes(), tr.succ.Bytes(), tr.hb, data, tr.groups, false)
if err != nil {
return err
}
}
if tr.hb.hasRoot() {
hbRoot := tr.hb.root()
hbHash := tr.hb.rootHash()
return tr.hookFunction(tr.currentReq, hbRoot, hbHash)
}
return nil
}
func (tr *ResolverStateful) RebuildTrie(
db ethdb.Database,
blockNr uint64,
accounts bool,
historical bool) error {
defer trieResolveStatefulTimer.UpdateSince(time.Now())
startkeys, fixedbits := tr.PrepareResolveParams()
if db == nil {
var b strings.Builder
fmt.Fprintf(&b, "ResolveWithDb(db=nil), accounts: %t\n", accounts)
for i, sk := range startkeys {
fmt.Fprintf(&b, "sk %x, bits: %d\n", sk, fixedbits[i])
}
return fmt.Errorf("unexpected resolution: %s at %s", b.String(), debug.Stack())
}
var err error
if accounts {
if historical {
err = db.MultiWalkAsOf(dbutils.AccountsBucket, dbutils.AccountsHistoryBucket, startkeys, fixedbits, blockNr+1, tr.WalkerAccounts)
} else {
err = db.MultiWalk(dbutils.AccountsBucket, startkeys, fixedbits, tr.WalkerAccounts)
}
} else {
if historical {
err = db.MultiWalkAsOf(dbutils.StorageBucket, dbutils.StorageHistoryBucket, startkeys, fixedbits, blockNr+1, tr.WalkerStorage)
} else {
err = db.MultiWalk(dbutils.StorageBucket, startkeys, fixedbits, tr.WalkerStorage)
}
}
if err != nil {
return err
}
if err = tr.finaliseRoot(); err != nil {
return fmt.Errorf("error in finaliseRoot, for block %d: %w", blockNr, err)
}
return nil
}
func (tr *ResolverStateful) AttachRequestedCode(db ethdb.Getter, requests []*ResolveRequestForCode) error {
for _, req := range requests {
codeHash := req.codeHash
code, err := db.Get(dbutils.CodeBucket, codeHash[:])
if err != nil {
return err
}
if req.bytecode {
if err := req.t.UpdateAccountCode(req.addrHash[:], codeNode(code)); err != nil {
return err
}
} else {
if err := req.t.UpdateAccountCodeSize(req.addrHash[:], len(code)); err != nil {
return err
}
}
}
return nil
}
func (tr *ResolverStateful) WalkerAccounts(keyIdx int, k []byte, v []byte) error {
return tr.Walker(true, keyIdx, k, v)
}
func (tr *ResolverStateful) WalkerStorage(keyIdx int, k []byte, v []byte) error {
return tr.Walker(false, keyIdx, k, v)
}
// Walker - k, v - shouldn't be reused in the caller's code
func (tr *ResolverStateful) Walker(isAccount bool, keyIdx int, k []byte, v []byte) error {
//fmt.Printf("Walker: keyIdx: %d key:%x value:%x\n", keyIdx, k, v)
if keyIdx != tr.keyIdx {
if err := tr.finaliseRoot(); err != nil {
return err
}
tr.hb.Reset()
tr.groups = nil
tr.keyIdx = keyIdx
tr.currentReq = tr.requests[tr.reqIndices[keyIdx]]
tr.currentRs = tr.rss[keyIdx]
tr.curr.Reset()
}
if len(v) > 0 {
tr.curr.Reset()
tr.curr.Write(tr.succ.Bytes())
tr.succ.Reset()
skip := tr.currentReq.extResolvePos // how many first nibbles to skip
i := 0
for _, b := range k {
if i >= skip {
tr.succ.WriteByte(b / 16)
}
i++
if i >= skip {
tr.succ.WriteByte(b % 16)
}
i++
}
tr.succ.WriteByte(16)
if tr.curr.Len() > 0 {
var err error
var data GenStructStepData
if tr.fieldSet == 0 {
tr.leafData.Value = rlphacks.RlpSerializableBytes(tr.value.Bytes())
data = &tr.leafData
} else {
tr.accData.FieldSet = tr.fieldSet
tr.accData.StorageSize = tr.a.StorageSize
tr.accData.Balance.Set(&tr.a.Balance)
tr.accData.Nonce = tr.a.Nonce
tr.accData.Incarnation = tr.a.Incarnation
data = &tr.accData
}
tr.groups, err = GenStructStep(tr.currentRs.HashOnly, tr.curr.Bytes(), tr.succ.Bytes(), tr.hb, data, tr.groups, false)
if err != nil {
return err
}
}
// Remember the current key and value
if isAccount {
if err := tr.a.DecodeForStorage(v); err != nil {
return err
}
if tr.a.IsEmptyCodeHash() && tr.a.IsEmptyRoot() {
tr.fieldSet = AccountFieldSetNotContract
} else {
if tr.a.HasStorageSize {
tr.fieldSet = AccountFieldSetContractWithSize
} else {
tr.fieldSet = AccountFieldSetContract
}
// the first item ends up deepest on the stack, the seccond item - on the top
if err := tr.hb.hash(tr.a.CodeHash[:]); err != nil {
return err
}
if err := tr.hb.hash(tr.a.Root[:]); err != nil {
return err
}
}
} else {
tr.value.Reset()
tr.value.Write(v)
tr.fieldSet = AccountFieldSetNotAccount
}
}
return nil
}