erigon-pulse/cmd/rpctest/proofs.go
ledgerwatch b2ca635d86
Debug tool for recursively comparing state (in DB or in a file) with geth archive node (#191)
* Fetching results of eth_getProof

* Dump 5 levels of the trie in a file for repeated runs

* Drill down to 6 levels of the trie

* Fix lint

* Fix lint

* Fix lint

* verifySnapshot to check accounts with emptyRoot

* Descend into short nodes

* Latest tool fixes

* Fix lint

* Fix state properly working
2019-11-25 13:36:21 +00:00

293 lines
8.4 KiB
Go

package main
import (
"bytes"
"encoding/binary"
"fmt"
"net/http"
"os"
"time"
"github.com/ledgerwatch/turbo-geth/common"
"github.com/ledgerwatch/turbo-geth/common/dbutils"
"github.com/ledgerwatch/turbo-geth/common/hexutil"
"github.com/ledgerwatch/turbo-geth/consensus/ethash"
"github.com/ledgerwatch/turbo-geth/core"
"github.com/ledgerwatch/turbo-geth/core/state"
"github.com/ledgerwatch/turbo-geth/core/types/accounts"
"github.com/ledgerwatch/turbo-geth/core/vm"
"github.com/ledgerwatch/turbo-geth/crypto"
"github.com/ledgerwatch/turbo-geth/ethdb"
"github.com/ledgerwatch/turbo-geth/params"
"github.com/ledgerwatch/turbo-geth/rlp"
"github.com/ledgerwatch/turbo-geth/trie"
)
type EthGetProof struct {
CommonResponse
Result AccountResult `json:"result"`
}
// 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 proofs(chaindata string, url string, block int) {
fileName := "trie.txt"
ethDb, err := ethdb.NewBoltDatabase(chaindata)
if err != nil {
panic(err)
}
defer ethDb.Close()
var t *trie.Trie
if _, err = os.Stat(fileName); err != nil {
if os.IsNotExist(err) {
// Resolve 6 top levels of the accounts trie
r := trie.NewResolver(6, true, uint64(block))
t = trie.New(common.Hash{})
req := t.NewResolveRequest(nil, []byte{}, 0, nil)
r.AddRequest(req)
err = r.ResolveWithDb(ethDb, uint64(block))
if err != nil {
panic(err)
}
fmt.Printf("Resolved with hash: %x\n", t.Hash())
f, err1 := os.Create(fileName)
if err1 == nil {
defer f.Close()
t.Print(f)
} else {
panic(err1)
}
fmt.Printf("Saved trie to file\n")
} else {
panic(err)
}
} else {
f, err1 := os.Open(fileName)
if err1 == nil {
defer f.Close()
var err2 error
t, err2 = trie.Load(f)
if err2 != nil {
panic(err2)
}
fmt.Printf("Restored from file with hash: %x\n", t.Hash())
} else {
panic(err1)
}
}
var client = &http.Client{
Timeout: time.Second * 600,
}
reqID := 0
level := 0
diffKeys := [][]byte{{}}
var newDiffKeys [][]byte
for len(diffKeys) > 0 && level < 6 {
fmt.Printf("================================================\n")
fmt.Printf("LEVEL %d, diffKeys: %d\n", level, len(diffKeys))
fmt.Printf("================================================\n")
for _, diffKey := range diffKeys {
// Find account with the suitable hash
var startKey common.Hash
for i, d := range diffKey {
if i%2 == 0 {
startKey[i/2] |= (d << 4)
} else {
startKey[i/2] |= d
}
}
var account common.Address
var found bool
err = ethDb.Walk(dbutils.PreimagePrefix, startKey[:], uint(4*len(diffKey)), func(k, v []byte) (bool, error) {
if len(v) == common.AddressLength {
copy(account[:], v)
found = true
return false, nil
}
return true, nil
})
if err != nil {
fmt.Printf("Error when looking for suitable account for diffKey %x\n", diffKey)
return
}
if !found {
fmt.Printf("Could not find suitable account for diffKey %x\n", diffKey)
return
}
reqID++
template := `{"jsonrpc":"2.0","method":"eth_getProof","params":["0x%x",[],"0x%x"],"id":%d}`
var proof EthGetProof
if err := post(client, url, fmt.Sprintf(template, account, block, reqID), &proof); err != nil {
fmt.Printf("Could not get block number: %v\n", err)
return
}
if proof.Error != nil {
fmt.Printf("Error retrieving proof: %d %s\n", proof.Error.Code, proof.Error.Message)
return
}
if len(proof.Result.AccountProof) <= len(diffKey) {
fmt.Printf("RPC result needs to be at least %d levels deep\n", len(diffKey)+1)
return
}
p := proof.Result.AccountProof[len(diffKey)]
b := common.FromHex(p)
h := common.BytesToHash(crypto.Keccak256(b))
hE, err := t.HashOfHexKey(diffKey)
if err != nil {
fmt.Printf("Error computing partial hash for %x: %v\n", diffKey, err)
return
}
if h != hE {
fmt.Printf("key %x: %x %x adding nibbles: ", diffKey, h, hE)
var branch [17][]byte
err = rlp.DecodeBytes(b, &branch)
if err != nil {
fmt.Printf("Error decoding: %v\n", err)
fmt.Printf("%s\n", p)
}
// Expand keys further
for nibble := byte(0); nibble < 16; nibble++ {
newDiff := make([]byte, len(diffKey)+1)
copy(newDiff, diffKey)
newDiff[len(diffKey)] = nibble
var proofHash common.Hash
copy(proofHash[:], branch[nibble])
if proofHash != (common.Hash{}) || err != nil {
newHash, err := t.HashOfHexKey(newDiff)
if err != nil {
fmt.Printf("Error computing partial hash for %x: %v\n", newDiff, err)
}
if proofHash != newHash {
newDiffKeys = append(newDiffKeys, newDiff)
fmt.Printf("%x", nibble)
}
}
}
fmt.Printf("\n")
} else {
fmt.Printf("MATCH key %x: %x %x\n", diffKey, h, hE)
}
}
diffKeys = newDiffKeys
newDiffKeys = nil
level++
}
fmt.Printf("\n\nRESULT:\n")
for _, diffKey := range diffKeys {
fmt.Printf("%x\n", diffKey)
}
}
func fixState(chaindata string, url string) {
stateDb, err := ethdb.NewBoltDatabase(chaindata)
if err != nil {
panic(err)
}
defer stateDb.Close()
engine := ethash.NewFullFaker()
chainConfig := params.MainnetChainConfig
bc, err := core.NewBlockChain(stateDb, nil, chainConfig, engine, vm.Config{}, nil)
if err != nil {
panic(err)
}
currentBlock := bc.CurrentBlock()
blockNum := currentBlock.NumberU64()
blockHash := currentBlock.Hash()
fmt.Printf("Block number: %d\n", blockNum)
fmt.Printf("Block root hash: %x\n", currentBlock.Root())
reqID := 0
roots := make(map[common.Hash]*accounts.Account)
var client = &http.Client{
Timeout: time.Second * 600,
}
err = stateDb.Walk(dbutils.StorageBucket, nil, 0, func(k, v []byte) (bool, error) {
var addrHash common.Hash
copy(addrHash[:], k[:32])
if _, ok := roots[addrHash]; !ok {
if enc, _ := stateDb.Get(dbutils.AccountsBucket, addrHash[:]); enc == nil {
roots[addrHash] = nil
} else {
var account accounts.Account
if err = account.DecodeForStorage(enc); err != nil {
return false, err
}
roots[addrHash] = &account
}
}
return true, nil
})
if err != nil {
panic(err)
}
for addrHash, account := range roots {
if account != nil && account.Root != trie.EmptyRoot {
st := trie.New(account.Root)
sr := trie.NewResolver(32, false, blockNum)
key := []byte{}
contractPrefix := make([]byte, common.HashLength+state.IncarnationLength)
copy(contractPrefix, addrHash[:])
binary.BigEndian.PutUint64(contractPrefix[common.HashLength:], account.Incarnation^^uint64(0))
streq := st.NewResolveRequest(contractPrefix, key, 0, account.Root[:])
sr.AddRequest(streq)
err = sr.ResolveWithDb(stateDb, blockNum)
if err != nil {
fmt.Printf("%x: %v\n", addrHash, err)
address, _ := stateDb.Get(dbutils.PreimagePrefix, addrHash[:])
template := `{"jsonrpc":"2.0","method":"debug_storageRangeAt","params":["0x%x", %d,"0x%x","0x%x",%d],"id":%d}`
sm := make(map[common.Hash]storageEntry)
nextKey := &common.Hash{}
for nextKey != nil {
reqID++
var sr DebugStorageRange
if err = post(client, url, fmt.Sprintf(template, blockHash, 0, address, *nextKey, 1024, reqID), &sr); err != nil {
fmt.Printf("Could not get storageRange: %v\n", err)
return
}
if sr.Error != nil {
fmt.Printf("Error getting storageRange: %d %s\n", sr.Error.Code, sr.Error.Message)
break
} else {
nextKey = sr.Result.NextKey
for k, v := range sr.Result.Storage {
sm[k] = v
}
}
}
for key, entry := range sm {
var cKey [common.HashLength + state.IncarnationLength + common.HashLength]byte
copy(cKey[:], addrHash[:])
binary.BigEndian.PutUint64(cKey[common.HashLength:], account.Incarnation^^uint64(0))
copy(cKey[common.HashLength+state.IncarnationLength:], key[:])
dbValue, _ := stateDb.Get(dbutils.StorageBucket, cKey[:])
value := bytes.TrimLeft(entry.Value[:], "\x00")
if !bytes.Equal(dbValue, value) {
fmt.Printf("Key: %x, value: %x, dbValue: %x\n", key, value, dbValue)
}
}
}
}
}
if err != nil {
panic(err)
}
}