erigon-pulse/internal/ethapi/get_proof.go

239 lines
6.9 KiB
Go

package ethapi
import (
"bytes"
"context"
"math/big"
"sort"
"github.com/ledgerwatch/turbo-geth/common"
"github.com/ledgerwatch/turbo-geth/common/changeset"
"github.com/ledgerwatch/turbo-geth/common/dbutils"
"github.com/ledgerwatch/turbo-geth/common/hexutil"
"github.com/ledgerwatch/turbo-geth/core/rawdb"
"github.com/ledgerwatch/turbo-geth/core/types/accounts"
"github.com/ledgerwatch/turbo-geth/rpc"
"github.com/ledgerwatch/turbo-geth/turbo/trie"
)
// Result structs for GetProof
type AccountResult struct {
Address common.Address `json:"address"`
AccountProof []string `json:"accountProof"`
Balance *hexutil.Big `json:"balance"`
CodeHash common.Hash `json:"codeHash"`
Nonce hexutil.Uint64 `json:"nonce"`
StorageHash common.Hash `json:"storageHash"`
StorageProof []StorageResult `json:"storageProof"`
}
type StorageResult struct {
Key string `json:"key"`
Value *hexutil.Big `json:"value"`
Proof []string `json:"proof"`
}
func (s *PublicBlockChainAPI) GetProof(ctx context.Context, address common.Address, storageKeys []string, blockNr rpc.BlockNumber) (*AccountResult, error) {
block := uint64(blockNr.Int64()) + 1
db := s.b.ChainDb()
ts := dbutils.EncodeBlockNumber(block)
accountMap := make(map[string]*accounts.Account)
if err := changeset.Walk(db, dbutils.PlainAccountChangeSetBucket, ts, 0, func(blockN uint64, a, v []byte) (bool, error) {
a, v = common.CopyBytes(a), common.CopyBytes(v)
var kHash, err = common.HashData(a)
if err != nil {
return false, err
}
k := kHash[:]
if _, ok := accountMap[string(k)]; !ok {
if len(v) > 0 {
var a accounts.Account
if innerErr := a.DecodeForStorage(v); innerErr != nil {
return false, innerErr
}
accountMap[string(k)] = &a
} else {
accountMap[string(k)] = nil
}
}
return true, nil
}); err != nil {
return nil, err
}
storageMap := make(map[string][]byte)
if err := changeset.Walk(db, dbutils.PlainAccountChangeSetBucket, ts, 0, func(blockN uint64, a, v []byte) (bool, error) {
a, v = common.CopyBytes(a), common.CopyBytes(v)
var kHash, err = common.HashData(a)
if err != nil {
return true, err
}
k := kHash[:]
if _, ok := storageMap[string(k)]; !ok {
storageMap[string(k)] = v
}
return true, nil
}); err != nil {
return nil, err
}
var unfurlList = make([]string, len(accountMap)+len(storageMap))
unfurl := trie.NewRetainList(0)
i := 0
for ks, acc := range accountMap {
unfurlList[i] = ks
i++
unfurl.AddKey([]byte(ks))
if acc != nil {
// Fill the code hashes
if acc.Incarnation > 0 && acc.IsEmptyCodeHash() {
if codeHash, err1 := db.Get(dbutils.ContractCodeBucket, dbutils.GenerateStoragePrefix([]byte(ks), acc.Incarnation)); err1 == nil {
copy(acc.CodeHash[:], codeHash)
} else {
return nil, err1
}
}
}
}
for ks := range storageMap {
unfurlList[i] = ks
i++
var sk [64]byte
copy(sk[:], []byte(ks)[:common.HashLength])
copy(sk[common.HashLength:], []byte(ks)[common.HashLength+common.IncarnationLength:])
unfurl.AddKey(sk[:])
}
rl := trie.NewRetainList(0)
addrHash, err := common.HashData(address[:])
if err != nil {
return nil, err
}
rl.AddKey(addrHash[:])
unfurl.AddKey(addrHash[:])
for _, key := range storageKeys {
keyAsHash := common.HexToHash(key)
if keyHash, err1 := common.HashData(keyAsHash[:]); err1 == nil {
trieKey := append(addrHash[:], keyHash[:]...)
rl.AddKey(trieKey)
unfurl.AddKey(trieKey)
} else {
return nil, err1
}
}
sort.Strings(unfurlList)
loader := trie.NewFlatDbSubTrieLoader()
if err = loader.Reset(db, unfurl, unfurl, nil /* hashCollector */, [][]byte{nil}, []int{0}, false); err != nil {
return nil, err
}
r := &Receiver{defaultReceiver: trie.NewDefaultReceiver(), unfurlList: unfurlList, accountMap: accountMap, storageMap: storageMap}
r.defaultReceiver.Reset(rl, nil /* hashCollector */, false)
loader.SetStreamReceiver(r)
subTries, err1 := loader.LoadSubTries()
if err1 != nil {
return nil, err1
}
hash, err := rawdb.ReadCanonicalHash(db, block-1)
if err != nil {
return nil, err
}
header := rawdb.ReadHeader(db, hash, block-1)
tr := trie.New(header.Root)
if err = tr.HookSubTries(subTries, [][]byte{nil}); err != nil {
return nil, err
}
accountProof, err2 := tr.Prove(addrHash[:], 0, false /* storage */)
if err2 != nil {
return nil, err2
}
storageProof := make([]StorageResult, len(storageKeys))
for i, key := range storageKeys {
keyAsHash := common.HexToHash(key)
if keyHash, err1 := common.HashData(keyAsHash[:]); err1 == nil {
trieKey := append(addrHash[:], keyHash[:]...)
if proof, err3 := tr.Prove(trieKey, 64 /* nibbles to get to the storage sub-trie */, true /* storage */); err3 == nil {
v, _ := tr.Get(trieKey)
bv := new(big.Int)
bv.SetBytes(v)
storageProof[i] = StorageResult{key, (*hexutil.Big)(bv), toHexSlice(proof)}
} else {
return nil, err3
}
} else {
return nil, err1
}
}
acc, found := tr.GetAccount(addrHash[:])
if !found {
return nil, nil
}
return &AccountResult{
Address: address,
AccountProof: toHexSlice(accountProof),
Balance: (*hexutil.Big)(acc.Balance.ToBig()),
CodeHash: acc.CodeHash,
Nonce: hexutil.Uint64(acc.Nonce),
StorageHash: acc.Root,
StorageProof: storageProof,
}, nil
}
type Receiver struct {
defaultReceiver *trie.DefaultReceiver
accountMap map[string]*accounts.Account
storageMap map[string][]byte
unfurlList []string
currentIdx int
}
func (r *Receiver) Root() common.Hash { panic("don't call me") }
func (r *Receiver) Receive(
itemType trie.StreamItem,
accountKey []byte,
storageKey []byte,
accountValue *accounts.Account,
storageValue []byte,
hash []byte,
hasTree bool,
cutoff int,
) error {
for r.currentIdx < len(r.unfurlList) {
ks := r.unfurlList[r.currentIdx]
k := []byte(ks)
var c int
switch itemType {
case trie.StorageStreamItem, trie.SHashStreamItem:
c = bytes.Compare(k, storageKey)
case trie.AccountStreamItem, trie.AHashStreamItem:
c = bytes.Compare(k, accountKey)
case trie.CutoffStreamItem:
c = -1
}
if c > 0 {
return r.defaultReceiver.Receive(itemType, accountKey, storageKey, accountValue, storageValue, hash, hasTree, cutoff)
}
if len(k) > common.HashLength {
v := r.storageMap[ks]
if c <= 0 && len(v) > 0 {
if err := r.defaultReceiver.Receive(trie.StorageStreamItem, nil, k, nil, v, nil, hasTree, 0); err != nil {
return err
}
}
} else {
v := r.accountMap[ks]
if c <= 0 && v != nil {
if err := r.defaultReceiver.Receive(trie.AccountStreamItem, k, nil, v, nil, nil, hasTree, 0); err != nil {
return err
}
}
}
r.currentIdx++
if c == 0 {
return nil
}
}
// We ran out of modifications, simply pass through
return r.defaultReceiver.Receive(itemType, accountKey, storageKey, accountValue, storageValue, hash, hasTree, cutoff)
}
func (r *Receiver) Result() trie.SubTries {
return r.defaultReceiver.Result()
}