Skip to content

Commit

Permalink
add test cases
Browse files Browse the repository at this point in the history
  • Loading branch information
shotasilagadzetaal committed Jan 7, 2025
1 parent 4e48fdc commit 650dc19
Show file tree
Hide file tree
Showing 5 changed files with 72 additions and 169 deletions.
109 changes: 0 additions & 109 deletions erigon-lib/commitment/hex_patricia_hashed.go
Original file line number Diff line number Diff line change
Expand Up @@ -1879,115 +1879,6 @@ func (hph *HexPatriciaHashed) RootHash() ([]byte, error) {
return rootHash[1:], nil // first byte is 128+hash_len=160
}

func (hph *HexPatriciaHashed) GenerateProofTrie(ctx context.Context, updates *Updates, expectedRootHash []byte, logPrefix string) (proofTrie *trie.Trie, rootHash []byte, err error) {
var (
m runtime.MemStats
ki uint64

updatesCount = updates.Size()
logEvery = time.NewTicker(20 * time.Second)
)
defer logEvery.Stop()
var tries []*trie.Trie = make([]*trie.Trie, 0, len(updates.keys)) // slice of tries, i.e the witness for each key, these will be all merged into single trie
err = updates.HashSort(ctx, func(hashedKey, plainKey []byte, stateUpdate *Update) error {
select {
case <-logEvery.C:
dbg.ReadMemStats(&m)
log.Info(fmt.Sprintf("[%s][agg] computing trie", logPrefix),
"progress", fmt.Sprintf("%s/%s", common.PrettyCounter(ki), common.PrettyCounter(updatesCount)),
"alloc", common.ByteCount(m.Alloc), "sys", common.ByteCount(m.Sys))

default:
}

var tr *trie.Trie
var computedRootHash []byte

fmt.Printf("\n%d/%d) plainKey [%x] hashedKey [%x] currentKey [%x]\n", ki+1, updatesCount, plainKey, hashedKey, hph.currentKey[:hph.currentKeyLen])

if len(plainKey) == 20 { // account
account, err := hph.ctx.Account(plainKey)
if err != nil {
return fmt.Errorf("account with plainkey=%x not found: %w", plainKey, err)
} else {
addrHash := ecrypto.Keccak256(plainKey)
fmt.Printf("account with plainKey=%x, addrHash=%x FOUND = %v\n", plainKey, addrHash, account)
}
} else {
storage, err := hph.ctx.Storage(plainKey)
if err != nil {
return fmt.Errorf("storage with plainkey=%x not found: %w", plainKey, err)
}
fmt.Printf("storage found = %v\n", storage.Storage)
}

// Keep folding until the currentKey is the prefix of the key we modify
for hph.needFolding(hashedKey) {
if err := hph.fold(); err != nil {
return fmt.Errorf("fold: %w", err)
}
}
// Now unfold until we step on an empty cell
for unfolding := hph.needUnfolding(hashedKey); unfolding > 0; unfolding = hph.needUnfolding(hashedKey) {
if err := hph.unfold(hashedKey, unfolding); err != nil {
return fmt.Errorf("unfold: %w", err)
}
}
hph.PrintGrid()

// convert grid to trie.Trie
tr, err = hph.ToTrie(hashedKey, nil) // build witness trie for this key, based on the current state of the grid
if err != nil {
return err
}
computedRootHash = tr.Root()
fmt.Printf("computedRootHash = %x\n", computedRootHash)

if !bytes.Equal(computedRootHash, expectedRootHash) {
return nil
}

tries = append(tries, tr)
ki++
return nil
})

if err != nil {
return nil, nil, fmt.Errorf("hash sort failed: %w", err)
}

// Folding everything up to the root
for hph.activeRows > 0 {
if err := hph.fold(); err != nil {
return nil, nil, fmt.Errorf("final fold: %w", err)
}
}

rootHash, err = hph.RootHash()
if err != nil {
return nil, nil, fmt.Errorf("root hash evaluation failed: %w", err)
}
if hph.trace {
fmt.Printf("root hash %x updates %d\n", rootHash, updatesCount)
}

// merge all individual tries
proofTrie, err = trie.MergeTries(tries)
if err != nil {
return nil, nil, err
}

proofTrieRootHash := proofTrie.Root()

fmt.Printf("mergedTrieRootHash = %x\n", proofTrieRootHash)

if !bytes.Equal(proofTrieRootHash, expectedRootHash) {
return nil, nil, fmt.Errorf("root hash mismatch witnessTrieRootHash(%x)!=expectedRootHash(%x)", proofTrieRootHash, expectedRootHash)
}

return proofTrie, rootHash, nil
}

// Generate the block witness. This works by loading each key from the list of updates (they are not really updates since we won't modify the trie,
// but currently need to be defined like that for the fold/unfold algorithm) into the grid and traversing the grid to convert it into `trie.Trie`.
// All the individual tries are combined to create the final witness trie.
Expand Down
14 changes: 14 additions & 0 deletions erigon-lib/state/domain_shared.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import (
"unsafe"

"github.com/erigontech/erigon-lib/seg"
"github.com/erigontech/erigon-lib/trie"
"github.com/pkg/errors"
"golang.org/x/crypto/sha3"

Expand Down Expand Up @@ -161,6 +162,10 @@ func (sd *SharedDomains) SavePastChangesetAccumulator(blockHash common.Hash, blo
sd.pastChangesAccumulator[toStringZeroCopy(key)] = acc
}

func (sd *SharedDomains) GetCommitmentContext() *SharedDomainsCommitmentContext {
return sd.sdCtx
}

func (sd *SharedDomains) GetDiffset(tx kv.RwTx, blockHash common.Hash, blockNumber uint64) ([kv.DomainLen][]DomainEntryDiff, bool, error) {
var key [40]byte
binary.BigEndian.PutUint64(key[:8], blockNumber)
Expand Down Expand Up @@ -1318,6 +1323,15 @@ func (sdc *SharedDomainsCommitmentContext) TouchKey(d kv.Domain, key string, val
}
}

func (sdc *SharedDomainsCommitmentContext) GenerateTouchedKeyTrie(ctx context.Context, expectedRoot []byte, logPrefix string) (proofTrie *trie.Trie, rootHash []byte, err error) {
hexPatriciaHashed, ok := sdc.Trie().(*commitment.HexPatriciaHashed)
if ok {
return hexPatriciaHashed.GenerateWitness(ctx, sdc.updates, nil, expectedRoot, logPrefix)
}

return nil, nil, errors.New("shared domains commitment context doesn't have HexPatriciaHashed")
}

// Evaluates commitment for processed state.
func (sdc *SharedDomainsCommitmentContext) ComputeCommitment(ctx context.Context, saveState bool, blockNum uint64, logPrefix string) (rootHash []byte, err error) {
sdc.ResetBranchCache()
Expand Down
24 changes: 15 additions & 9 deletions erigon-lib/trie/proof.go
Original file line number Diff line number Diff line change
Expand Up @@ -355,15 +355,21 @@ func VerifyStorageProofByHash(storageRoot libcommon.Hash, keyHash libcommon.Hash
if proof.Value.ToInt().Sign() != 0 {
return errors.New("empty storage root cannot have non-zero values")
}
// The spec here is a bit unclear. The yellow paper makes it clear that the
// EmptyRoot hash is a special case where the trie is empty. Since the trie
// is empty there are no proof elements to collect. But, EIP-1186 also
// clearly states that the proof must be "starting with the
// storageHash-Node", which could imply an RLP encoded `[]byte(nil)` (the
// pre-image of the EmptyRoot) should be included. This implementation
// chooses to require the proof be empty.
if len(proof.Proof) > 0 {
return errors.New("empty storage root should not have proof nodes")
// if storage root is zero (0000000) then we should have an empty proof
// if it corresponds to empty storage tree, having value EmptyRoot above
// then proof should be RLP encoding of empty proof (0x80)
if storageRoot == EmptyRoot {
for i, _ := range proof.Proof {
if len(proof.Proof[i]) != 1 || proof.Proof[i][0] != 0x80 {
return errors.New("empty storage root should have RLP encoding of empty proof")
}
}
} else {
for i, _ := range proof.Proof {
if len(proof.Proof[i]) != 0 {
return errors.New("zero storage root should have empty proof")
}
}
}
return nil
}
Expand Down
74 changes: 43 additions & 31 deletions turbo/jsonrpc/eth_call.go
Original file line number Diff line number Diff line change
Expand Up @@ -387,33 +387,18 @@ func (api *APIImpl) getProof(ctx context.Context, address libcommon.Address, sto
return nil, err
}
defer roTx2.Rollback()
txBatch2 := membatchwithdb.NewMemoryBatch(roTx2, "", logger)
defer txBatch2.Rollback()

domains, err := libstate.NewSharedDomains(txBatch2, log.New())
domains, err := libstate.NewSharedDomains(roTx2, log.New())
if err != nil {
return nil, err
}
sdCtx := libstate.NewSharedDomainsCommitmentContext(domains, commitment.ModeUpdate, commitment.VariantHexPatriciaTrie)
patricieTrie := sdCtx.Trie()
hph, ok := patricieTrie.(*commitment.HexPatriciaHashed)
if !ok {
return nil, errors.New("casting to HexPatriciaTrieHashed failed")
}
sdCtx := domains.GetCommitmentContext()

// define these keys as "updates", but we are not really updating anything, we just want to load them into the grid,
// so this is just to satisfy the current hex patricia trie api.
updates := commitment.NewUpdates(commitment.ModeDirect, sdCtx.TempDir(), hph.HashAndNibblizeKey)
// touch account
updates.TouchPlainKey(string(address.Bytes()), nil, updates.TouchAccount)
// touch storage keys
for _, storageKey := range storageKeys {
updates.TouchPlainKey(string(common.FromHex(address.Hex()[2:]+storageKey.String()[2:])), nil, updates.TouchStorage)
}
hph.SetTrace(false)
sdCtx.TouchKey(kv.AccountsDomain, string(address.Bytes()), nil)

// generate the trie for proofs, this works by loading the merkle paths to the touched keys
proofTrie, proofRootHash, err := hph.GenerateProofTrie(ctx, updates, header.Root[:], "eth_getProof")
proofTrie, proofRootHash, err := sdCtx.GenerateTouchedKeyTrie(ctx, header.Root[:], "eth_getProof")
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -448,19 +433,50 @@ func (api *APIImpl) getProof(ctx context.Context, address libcommon.Address, sto
proof.StorageProof[i] = accounts.StorProofResult{
Key: k,
Value: (*hexutil.Big)(a),
Proof: []hexutility.Bytes{},
Proof: nil,
}
}
return proof, nil
} else {
proof.Balance = (*hexutil.Big)(acc.Balance.ToBig())
proof.Nonce = hexutil.Uint64(acc.Nonce)
proof.CodeHash = acc.CodeHash
proof.StorageHash = acc.Root
}

proof.Balance = (*hexutil.Big)(acc.Balance.ToBig())
proof.Nonce = hexutil.Uint64(acc.Nonce)
proof.CodeHash = acc.CodeHash
proof.StorageHash = acc.Root

// if storage is not empty touch keys and build trie
fmt.Println("shota", proof.StorageHash.String())
if proof.StorageHash.Cmp(libcommon.BytesToHash(commitment.EmptyRootHash)) != 0 && len(storageKeys) != 0 {
// touch storage keys
for _, storageKey := range storageKeys {
sdCtx.TouchKey(kv.StorageDomain, string(common.FromHex(address.Hex()[2:]+storageKey.String()[2:])), nil)
}

// generate the trie for proofs, this works by loading the merkle paths to the touched keys
proofTrie, proofRootHash, err = sdCtx.GenerateTouchedKeyTrie(ctx, header.Root[:], "eth_getProof")
if err != nil {
return nil, err
}

// verify hash
if !bytes.Equal(proofRootHash, header.Root[:]) {
return nil, fmt.Errorf("proof root hash mismatch actual(%x)!=expected(%x)", proofRootHash, header.Root[:])
}
}

// get storage key proofs
for i, keyHash := range storageKeys {
proof.StorageProof[i].Key = keyHash

n := new(big.Int)
// if we have simple non contract account just set values directly without requesting any key proof
if proof.StorageHash.Cmp(libcommon.BytesToHash(commitment.EmptyRootHash)) == 0 {
n.SetInt64(0)
proof.StorageProof[i].Proof = nil
proof.StorageProof[i].Value = (*hexutil.Big)(unsafe.Pointer(n))
continue
}

// prepare key path (keccak(address) | keccak(key))
var fullKey []byte
fullKey = append(fullKey, crypto.Keccak256(address.Bytes())...)
Expand All @@ -472,14 +488,10 @@ func (api *APIImpl) getProof(ctx context.Context, address libcommon.Address, sto
return nil, errors.New("cannot verify store proof")
}

// Decode the hexadecimal string into the big.Int
// The base is 16 for hexadecimal
n := new(big.Int)
n.SetString(hex.EncodeToString(value), 16)

// set key proof
proof.StorageProof[i].Key = keyHash
proof.StorageProof[i].Value = (*hexutil.Big)(unsafe.Pointer(n))

// 0x80 represents RLP encoding of an empty proof slice
proof.StorageProof[i].Proof = []hexutility.Bytes{[]byte{0x80}}
if storageProof != nil && len(storageProof) != 0 {
proof.StorageProof[i].Proof = *(*[]hexutility.Bytes)(unsafe.Pointer(&storageProof))
Expand Down
20 changes: 0 additions & 20 deletions turbo/jsonrpc/eth_call_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -162,26 +162,6 @@ func TestGetProof(t *testing.T) {
blockNum: 3,
stateVal: 0,
},
{
name: "currentBlockNoAccountMissingState",
addr: libcommon.HexToAddress("0xdeaddeaddeaddeaddeaddeaddeaddeaddeaddead0"),
storageKeys: []libcommon.Hash{libcommon.HexToHash("0xdeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddead")},
blockNum: 3,
stateVal: 0,
},
{
name: "olderBlockWithState",
addr: contractAddr,
blockNum: 2,
storageKeys: []libcommon.Hash{key(1), key(5), key(9), key(13)},
stateVal: 1,
},
{
name: "tooOldBlock",
addr: contractAddr,
blockNum: 1,
expectedErr: "requested block is too old, block must be within 1 blocks of the head block number (currently 3)",
},
}

for _, tt := range tests {
Expand Down

0 comments on commit 650dc19

Please sign in to comment.