diff --git a/core/trie/proof.go b/core/trie/proof.go index 008e0d6c08..d99ca3c05d 100644 --- a/core/trie/proof.go +++ b/core/trie/proof.go @@ -321,6 +321,9 @@ func VerifyProof(root *felt.Felt, key *Key, value *felt.Felt, proofs []ProofNode remainingPath := NewKey(key.len, key.bitset[:]) for i, proofNode := range proofs { if !proofNode.Hash(hash).Equal(expectedHash) { + proofHash := proofNode.Hash(hash) + fmt.Println("Proof verification failure", "node", proofHash, "expected", expectedHash) + fmt.Printf("%v\n", proofNode) return false } @@ -340,12 +343,13 @@ func VerifyProof(root *felt.Felt, key *Key, value *felt.Felt, proofs []ProofNode // Todo: // If we are verifying the key doesn't exist, then we should - // update.Status subKey to point in the other direction + // update subKey to point in the other direction if value == nil && i == len(proofs)-1 { return true } if !proofNode.Path.Equal(subKey) && !subKey.Equal(&Key{}) { + fmt.Println("Proof verification failure", "node", proofNode.Path, "expected", subKey) return false } expectedHash = proofNode.Child diff --git a/rpc/storage_test.go b/rpc/storage_test.go index 40f22991c8..f6774936b0 100644 --- a/rpc/storage_test.go +++ b/rpc/storage_test.go @@ -107,6 +107,8 @@ func TestStorageAt(t *testing.T) { } func TestStorageProof(t *testing.T) { + t.Parallel() + // dummy values var ( blkHash = utils.HexToFelt(t, "0x11ead") @@ -130,6 +132,9 @@ func TestStorageProof(t *testing.T) { require.NoError(t, err) require.NoError(t, tempTrie.Commit()) + trieRoot, err := tempTrie.Root() + require.NoError(t, err) + mockCtrl := gomock.NewController(t) t.Cleanup(mockCtrl.Finish) @@ -149,21 +154,9 @@ func TestStorageProof(t *testing.T) { log := utils.NewNopZapLogger() handler := rpc.New(mockReader, nil, nil, "", log) - verifyIf := func(proof []*rpc.HashToNode, key *felt.Felt, value *felt.Felt) { - root, err := tempTrie.Root() - require.NoError(t, err) - - pnodes := []trie.ProofNode{} - for _, hn := range proof { - pnodes = append(pnodes, hn.Node.AsProofNode()) - } - - kbs := key.Bytes() - kkey := trie.NewKey(251, kbs[:]) - require.True(t, trie.VerifyProof(root, &kkey, value, pnodes, tempTrie.HashFunc())) - } - t.Run("Trie proofs sanity check", func(t *testing.T) { + t.Parallel() + kbs := key.Bytes() kKey := trie.NewKey(251, kbs[:]) proof, err := trie.GetProof(&kKey, tempTrie) @@ -180,6 +173,8 @@ func TestStorageProof(t *testing.T) { require.True(t, trie.VerifyProof(root, &kKey, nil, proof, tempTrie.HashFunc())) }) t.Run("global roots are filled", func(t *testing.T) { + t.Parallel() + proof, rpcErr := handler.StorageProof(blockLatest, nil, nil, nil) require.Nil(t, rpcErr) @@ -190,36 +185,48 @@ func TestStorageProof(t *testing.T) { require.Equal(t, stgRoot, proof.GlobalRoots.ContractsTreeRoot) }) t.Run("error is returned whenever not latest block is requested", func(t *testing.T) { + t.Parallel() + proof, rpcErr := handler.StorageProof(rpc.BlockID{Number: 1}, nil, nil, nil) assert.Equal(t, rpc.ErrStorageProofNotSupported, rpcErr) require.Nil(t, proof) }) t.Run("error is returned even when blknum matches head", func(t *testing.T) { + t.Parallel() + proof, rpcErr := handler.StorageProof(rpc.BlockID{Number: blockNumber}, nil, nil, nil) assert.Equal(t, rpc.ErrStorageProofNotSupported, rpcErr) require.Nil(t, proof) }) t.Run("empty request", func(t *testing.T) { + t.Parallel() + proof, rpcErr := handler.StorageProof(blockLatest, nil, nil, nil) require.Nil(t, rpcErr) require.NotNil(t, proof) arityTest(t, proof, 0, 0, 0, 0) }) t.Run("class trie hash does not exist in a trie", func(t *testing.T) { + t.Parallel() + proof, rpcErr := handler.StorageProof(blockLatest, []felt.Felt{*noSuchKey}, nil, nil) require.Nil(t, rpcErr) require.NotNil(t, proof) arityTest(t, proof, 3, 0, 0, 0) - verifyIf(proof.ClassesProof, noSuchKey, nil) + verifyIf(t, trieRoot, noSuchKey, nil, proof.ClassesProof, tempTrie.HashFunc()) }) t.Run("class trie hash exists in a trie", func(t *testing.T) { + t.Parallel() + proof, rpcErr := handler.StorageProof(blockLatest, []felt.Felt{*key}, nil, nil) require.Nil(t, rpcErr) require.NotNil(t, proof) arityTest(t, proof, 3, 0, 0, 0) - verifyIf(proof.ClassesProof, key, value) + verifyIf(t, trieRoot, key, value, proof.ClassesProof, tempTrie.HashFunc()) }) t.Run("storage trie address does not exist in a trie", func(t *testing.T) { + t.Parallel() + mockState.EXPECT().ContractNonce(noSuchKey).Return(nil, db.ErrKeyNotFound).Times(1) mockState.EXPECT().ContractClassHash(noSuchKey).Return(nil, db.ErrKeyNotFound).Times(0) @@ -229,9 +236,11 @@ func TestStorageProof(t *testing.T) { arityTest(t, proof, 0, 3, 1, 0) require.Nil(t, proof.ContractsProof.LeavesData[0]) - verifyIf(proof.ContractsProof.Nodes, noSuchKey, nil) + verifyIf(t, trieRoot, noSuchKey, nil, proof.ContractsProof.Nodes, tempTrie.HashFunc()) }) t.Run("storage trie address exists in a trie", func(t *testing.T) { + t.Parallel() + nonce := new(felt.Felt).SetUint64(121) mockState.EXPECT().ContractNonce(key).Return(nonce, nil).Times(1) classHasah := new(felt.Felt).SetUint64(1234) @@ -247,9 +256,11 @@ func TestStorageProof(t *testing.T) { require.Equal(t, nonce, ld.Nonce) require.Equal(t, classHasah, ld.ClassHash) - verifyIf(proof.ContractsProof.Nodes, key, value) + verifyIf(t, trieRoot, key, value, proof.ContractsProof.Nodes, tempTrie.HashFunc()) }) t.Run("contract storage trie address does not exist in a trie", func(t *testing.T) { + t.Parallel() + contract := utils.HexToFelt(t, "0xdead") mockTrie.EXPECT().StorageTrieForAddr(contract).Return(emptyTrie(t), nil).Times(1) @@ -262,6 +273,8 @@ func TestStorageProof(t *testing.T) { }) //nolint:dupl t.Run("contract storage trie key slot does not exist in a trie", func(t *testing.T) { + t.Parallel() + contract := utils.HexToFelt(t, "0xabcd") mockTrie.EXPECT().StorageTrieForAddr(gomock.Any()).Return(tempTrie, nil).Times(1) @@ -272,10 +285,12 @@ func TestStorageProof(t *testing.T) { arityTest(t, proof, 0, 0, 0, 1) require.Len(t, proof.ContractsStorageProofs[0], 3) - verifyIf(proof.ContractsStorageProofs[0], noSuchKey, nil) + verifyIf(t, trieRoot, noSuchKey, nil, proof.ContractsStorageProofs[0], tempTrie.HashFunc()) }) //nolint:dupl t.Run("contract storage trie address/key exists in a trie", func(t *testing.T) { + t.Parallel() + contract := utils.HexToFelt(t, "0xabcd") mockTrie.EXPECT().StorageTrieForAddr(gomock.Any()).Return(tempTrie, nil).Times(1) @@ -286,9 +301,11 @@ func TestStorageProof(t *testing.T) { arityTest(t, proof, 0, 0, 0, 1) require.Len(t, proof.ContractsStorageProofs[0], 3) - verifyIf(proof.ContractsStorageProofs[0], key, value) + verifyIf(t, trieRoot, key, value, proof.ContractsStorageProofs[0], tempTrie.HashFunc()) }) t.Run("class & storage tries proofs requested", func(t *testing.T) { + t.Parallel() + nonce := new(felt.Felt).SetUint64(121) mockState.EXPECT().ContractNonce(key).Return(nonce, nil) classHasah := new(felt.Felt).SetUint64(1234) @@ -301,31 +318,9 @@ func TestStorageProof(t *testing.T) { }) } -func arityTest(t *testing.T, - proof *rpc.StorageProofResult, - classesProofArity int, - contractsProofNodesArity int, - contractsProofLeavesArity int, - contractStorageArity int, -) { - require.Len(t, proof.ClassesProof, classesProofArity) - require.Len(t, proof.ContractsStorageProofs, contractStorageArity) - require.NotNil(t, proof.ContractsProof) - require.Len(t, proof.ContractsProof.Nodes, contractsProofNodesArity) - require.Len(t, proof.ContractsProof.LeavesData, contractsProofLeavesArity) -} - -func emptyTrie(t *testing.T) *trie.Trie { - memdb := pebble.NewMemTest(t) - txn, err := memdb.NewTransaction(true) - require.NoError(t, err) - - tempTrie, err := trie.NewTriePedersen(trie.NewStorage(txn, []byte{0}), 251) - require.NoError(t, err) - return tempTrie -} - func TestStorageRoots(t *testing.T) { + t.Parallel() + mockCtrl := gomock.NewController(t) t.Cleanup(mockCtrl.Finish) @@ -351,6 +346,8 @@ func TestStorageRoots(t *testing.T) { ) t.Run("sanity check - mainnet block 2", func(t *testing.T) { + t.Parallel() + expectedBlockNumber := uint64(2) blk, err := bc.Head() @@ -361,6 +358,8 @@ func TestStorageRoots(t *testing.T) { }) t.Run("check class and storage roots matches the global", func(t *testing.T) { + t.Parallel() + reader, closer, err := bc.HeadTrie() assert.NoError(t, err) defer func() { _ = closer() }() @@ -375,6 +374,8 @@ func TestStorageRoots(t *testing.T) { }) t.Run("check requested contract and storage slot exists", func(t *testing.T) { + t.Parallel() + trieReader, closer, err := bc.HeadTrie() assert.NoError(t, err) defer func() { _ = closer() }() @@ -397,6 +398,8 @@ func TestStorageRoots(t *testing.T) { }) t.Run("get contract proof", func(t *testing.T) { + t.Parallel() + handler := rpc.New(bc, nil, nil, "", log) result, rpcErr := handler.StorageProof( rpc.BlockID{Latest: true}, nil, []felt.Felt{*expectedContractAddress}, nil) @@ -462,6 +465,269 @@ func TestStorageRoots(t *testing.T) { }) } +func TestVerifyPathfinderResponse(t *testing.T) { + t.Parallel() + + // Pathfinder response for query: + // "method": "starknet_getStorageProof", + // "params": [ + // "latest", + // [], + // [ + // "0x5a03b82d726f9bb31ba41ea3a0c1143f90241e37c9a4a92174d168cda9c716d", + // "0x5fbaa249500be29fee38fdd90a7a2651a8d3935c14167570f6863f563d838f0" + // ] + // ], + // Sepolia, at block 10434 + result := rpc.StorageProofResult{ + ClassesProof: []*rpc.HashToNode{}, + ContractsProof: &rpc.ContractProof{ + LeavesData: []*rpc.LeafData{ + { + Nonce: utils.HexToFelt(t, "0x0"), + ClassHash: utils.HexToFelt(t, "0x772164c9d6179a89e7f1167f099219f47d752304b16ed01f081b6e0b45c93c3"), + }, + { + Nonce: utils.HexToFelt(t, "0x0"), + ClassHash: utils.HexToFelt(t, "0x78401746828463e2c3f92ebb261fc82f7d4d4c8d9a80a356c44580dab124cb0"), + }, + }, + Nodes: []*rpc.HashToNode{ + { + Node: &rpc.MerkleBinaryNode{ + Left: utils.HexToFelt(t, "0x5c6be09d8faaa42a8525898b1047cebdd3526349b48decc2b767a4fa612263d"), + Right: utils.HexToFelt(t, "0xcd11aa7699c4157a287e5fe574df37e40c8b6a5ed5e1aee658fc2d634398ef"), + }, + Hash: utils.HexToFelt(t, "0x7884784e689e733c1ea2c4ee3b1f790c4ca4992b26d8aee31abb5d9270d4947"), + }, + { + Node: &rpc.MerkleBinaryNode{ + Left: utils.HexToFelt(t, "0x1cdf395ebbba2f3a6234ad9827b08453a4a0b7745e2d919fe7b07749efa5325"), + Right: utils.HexToFelt(t, "0xcdd37cf6cce8bc373e2c9d8d6754b057275ddd910a9d133b4d31086632d0f4"), + }, + Hash: utils.HexToFelt(t, "0x44fcfce222b7e5a098346615dc838d8ae90ff55da82db7cdce4303f34042ff6"), + }, + { + Node: &rpc.MerkleBinaryNode{ + Left: utils.HexToFelt(t, "0x2c55bc287a1b31a405c681c2bb720811dd9f33523241561ea4b356f717ff9f6"), + Right: utils.HexToFelt(t, "0x2012025c00174e3eb72baba21e58a56e5114e571f64cb1040f7de0c8daef618"), + }, + Hash: utils.HexToFelt(t, "0x7f2b62cf9713a0b635b967c2e2891282631519eebca6ea0bddaa1a1a804919f"), + }, + { + Node: &rpc.MerkleBinaryNode{ + Left: utils.HexToFelt(t, "0x211a80e63ac0b12b29279c3d57ea5771b5003ea464b055aeb8ad8618ff3cd69"), + Right: utils.HexToFelt(t, "0x44f55356be17913dcd79e0bb4dbc986d0642bb3f000e540bb54bfa2d4189a74"), + }, + Hash: utils.HexToFelt(t, "0x69e208899d9deeae0732e95ce9d68d123abd9b59f157435fc3554e1fa3a92a8"), + }, + { + Node: &rpc.MerkleEdgeNode{ + Child: utils.HexToFelt(t, "0x6b45780618ce075fb4543396b3a6949915c04962b2e411c4f1b2a6813d540da"), + Length: 239, + Path: "0x3b82d726f9bb31ba41ea3a0c1143f90241e37c9a4a92174d168cda9c716d", + }, + Hash: utils.HexToFelt(t, "0x2c55bc287a1b31a405c681c2bb720811dd9f33523241561ea4b356f717ff9f6"), + }, + { + Node: &rpc.MerkleBinaryNode{ + Left: utils.HexToFelt(t, "0x7be97a0f8a99126208712673c69c292a26273707c884e96e17c761ee7097ae5"), + Right: utils.HexToFelt(t, "0x3ae1731f598d03a9033c6f5d29871cd5a80c4eba36a7a0a73775ea9d8d522f3"), + }, + Hash: utils.HexToFelt(t, "0xcd11aa7699c4157a287e5fe574df37e40c8b6a5ed5e1aee658fc2d634398ef"), + }, + { + Node: &rpc.MerkleBinaryNode{ + Left: utils.HexToFelt(t, "0x7f2b62cf9713a0b635b967c2e2891282631519eebca6ea0bddaa1a1a804919f"), + Right: utils.HexToFelt(t, "0x77f807a73f0e7ccad122cd946d79d8f4ce9e02f01017467e7cf4ad993cfa482"), + }, + Hash: utils.HexToFelt(t, "0x326e52c7cba85fedb456bb1c25dda2075ebe3367a329eb297144cb7f8d1f7d9"), + }, + { + Node: &rpc.MerkleBinaryNode{ + Left: utils.HexToFelt(t, "0x35d32a880d122ffc43a46e280c0ff34a9de286c2cb2e3933229f419a6ceed8e"), + Right: utils.HexToFelt(t, "0x14c9f5368ebbe1cc8d1db2dde1f97d18cabf450bbc23f154985c7e15e15bdcf"), + }, + Hash: utils.HexToFelt(t, "0x1159575d44f9b716f2cfbb13da873f8e7d9824e6b7b615dac5ce9c7b0e2bffd"), + }, + { + Node: &rpc.MerkleBinaryNode{ + Left: utils.HexToFelt(t, "0x1e5dfbcf23a5e942208f5ccfa25db1147dbfb2984df32a692102851757998cd"), + Right: utils.HexToFelt(t, "0x69e208899d9deeae0732e95ce9d68d123abd9b59f157435fc3554e1fa3a92a8"), + }, + Hash: utils.HexToFelt(t, "0x2722e2a47b3f10db016928bcc7451cd2088a1caea2fbb5f08e1b71dfe1db1c2"), + }, + { + Node: &rpc.MerkleBinaryNode{ + Left: utils.HexToFelt(t, "0x2634833b52e930231b53d58286647d9818a276dd12ace8286dae63b896c3ba1"), + Right: utils.HexToFelt(t, "0x1f248a8796f18bc9d116e5f3c3956c47e091c05f1c9596453b2fefa2b725507"), + }, + Hash: utils.HexToFelt(t, "0x109e30040b25357cc51726d6041ba1f09ec02dd8b3ca2ffa686a858c9293796"), + }, + { + Node: &rpc.MerkleBinaryNode{ + Left: utils.HexToFelt(t, "0x7884784e689e733c1ea2c4ee3b1f790c4ca4992b26d8aee31abb5d9270d4947"), + Right: utils.HexToFelt(t, "0x4e354efe4fcc718d3454d532b50cd3c73ac84f05df918981433162c84650f6c"), + }, + Hash: utils.HexToFelt(t, "0x88648f7a7b355914ed41bb28101110cff8fb68f1a9b39958823c72992d8675"), + }, + { + Node: &rpc.MerkleEdgeNode{ + Child: utils.HexToFelt(t, "0x4169679eea4895011fb8e9029b4591a210b3b9e9aa23f12f25cf45cbcaadfe8"), + Length: 1, + Path: "0x1", + }, + Hash: utils.HexToFelt(t, "0x44f55356be17913dcd79e0bb4dbc986d0642bb3f000e540bb54bfa2d4189a74"), + }, + { + Node: &rpc.MerkleBinaryNode{ + Left: utils.HexToFelt(t, "0x192804e98b1f3fdad2d8fab79bfb922611edc5fb48dcd1e9db02cd46cfa9763"), + Right: utils.HexToFelt(t, "0x4717a5dd5048d62401bc7db57594d3bdbfd3c7b99788a83c5e77b6db9822149"), + }, + Hash: utils.HexToFelt(t, "0x14c9f5368ebbe1cc8d1db2dde1f97d18cabf450bbc23f154985c7e15e15bdcf"), + }, + { + Node: &rpc.MerkleEdgeNode{ + Child: utils.HexToFelt(t, "0x25790175fe1fbeed47cbf510a41fba8676bea20a0c8888d4b9090b8f5cf19b8"), + Length: 238, + Path: "0x2a249500be29fee38fdd90a7a2651a8d3935c14167570f6863f563d838f0", + }, + Hash: utils.HexToFelt(t, "0x331128166378265a07c0be65b242d47d1965e785b6f4f6e1bca3731de5d2d1d"), + }, + { + Node: &rpc.MerkleBinaryNode{ + Left: utils.HexToFelt(t, "0x331128166378265a07c0be65b242d47d1965e785b6f4f6e1bca3731de5d2d1d"), + Right: utils.HexToFelt(t, "0x12af5e7e95772777d98792be8ade3b18c06ab21aa492a1821d5be3ac291374a"), + }, + Hash: utils.HexToFelt(t, "0x4169679eea4895011fb8e9029b4591a210b3b9e9aa23f12f25cf45cbcaadfe8"), + }, + { + Node: &rpc.MerkleBinaryNode{ + Left: utils.HexToFelt(t, "0x485b298f33aa076113362f82f4bf64f23e2eb5b84209353a630a46cd20fdde5"), + Right: utils.HexToFelt(t, "0x1159575d44f9b716f2cfbb13da873f8e7d9824e6b7b615dac5ce9c7b0e2bffd"), + }, + Hash: utils.HexToFelt(t, "0x3ae1731f598d03a9033c6f5d29871cd5a80c4eba36a7a0a73775ea9d8d522f3"), + }, + { + Node: &rpc.MerkleBinaryNode{ + Left: utils.HexToFelt(t, "0x2358473807e0a43a66b918247c0fb0d0649c72a32f19eee8bcc76c090b37951"), + Right: utils.HexToFelt(t, "0x109e30040b25357cc51726d6041ba1f09ec02dd8b3ca2ffa686a858c9293796"), + }, + Hash: utils.HexToFelt(t, "0x485b298f33aa076113362f82f4bf64f23e2eb5b84209353a630a46cd20fdde5"), + }, + { + Node: &rpc.MerkleBinaryNode{ + Left: utils.HexToFelt(t, "0x326e52c7cba85fedb456bb1c25dda2075ebe3367a329eb297144cb7f8d1f7d9"), + Right: utils.HexToFelt(t, "0x41149879a9d24ba0a2ccfb56415c04bdabb1c51eb0900a17dee2c715d6b1c70"), + }, + Hash: utils.HexToFelt(t, "0x1cdf395ebbba2f3a6234ad9827b08453a4a0b7745e2d919fe7b07749efa5325"), + }, + { + Node: &rpc.MerkleBinaryNode{ + Left: utils.HexToFelt(t, "0x454a8b3fc492869e79b16e87461d0b5101eb5d25389f492039ef6a380878b39"), + Right: utils.HexToFelt(t, "0x5a99604af4e482d046afe656b6ebe7805c72a1b7979d00608f27b276eb33442"), + }, + Hash: utils.HexToFelt(t, "0x4717a5dd5048d62401bc7db57594d3bdbfd3c7b99788a83c5e77b6db9822149"), + }, + { + Node: &rpc.MerkleBinaryNode{ + Left: utils.HexToFelt(t, "0x2f6c0e4b8022b48461e54e4f9358c51d5444ae2e2253a31baa68d4cb0c938de"), + Right: utils.HexToFelt(t, "0x88648f7a7b355914ed41bb28101110cff8fb68f1a9b39958823c72992d8675"), + }, + Hash: utils.HexToFelt(t, "0x47182b7d8158a8f80ed15822719aa306af37383a0cf91518d21ba63e73fea13"), + }, + { + Node: &rpc.MerkleBinaryNode{ + Left: utils.HexToFelt(t, "0x44fcfce222b7e5a098346615dc838d8ae90ff55da82db7cdce4303f34042ff6"), + Right: utils.HexToFelt(t, "0xc3da9c726d244197963a8a7beb4a3aee353b3b663daf2aa1bcf1c087b5e20d"), + }, + Hash: utils.HexToFelt(t, "0x2634833b52e930231b53d58286647d9818a276dd12ace8286dae63b896c3ba1"), + }, + { + Node: &rpc.MerkleBinaryNode{ + Left: utils.HexToFelt(t, "0x2722e2a47b3f10db016928bcc7451cd2088a1caea2fbb5f08e1b71dfe1db1c2"), + Right: utils.HexToFelt(t, "0x79c09acd32044c7d455299ca67e2a8fafce25afaf6d5e89ff4632b251dddc8d"), + }, + Hash: utils.HexToFelt(t, "0x5a99604af4e482d046afe656b6ebe7805c72a1b7979d00608f27b276eb33442"), + }, + }, + }, + ContractsStorageProofs: [][]*rpc.HashToNode{}, + GlobalRoots: &rpc.GlobalRoots{ + BlockHash: utils.HexToFelt(t, "0xae4cc763c8b350913e00e12cffd51fb7e3b730e29036864a8afd8ec323ecd6"), + ClassesTreeRoot: utils.HexToFelt(t, "0xea1568e1ca4e5b8c19cdf130dc3194f9cb8e5eee2fa5ec54a338a4dccfd6e3"), + ContractsTreeRoot: utils.HexToFelt(t, "0x47182b7d8158a8f80ed15822719aa306af37383a0cf91518d21ba63e73fea13"), + }, + } + + root := result.GlobalRoots.ContractsTreeRoot + + t.Run("first contract proof verification", func(t *testing.T) { + t.Parallel() + + firstContractAddr := utils.HexToFelt(t, "0x5a03b82d726f9bb31ba41ea3a0c1143f90241e37c9a4a92174d168cda9c716d") + firstContractLeaf := utils.HexToFelt(t, "0x6b45780618ce075fb4543396b3a6949915c04962b2e411c4f1b2a6813d540da") + verifyIf(t, + root, firstContractAddr, firstContractLeaf, + extractAndReorder(t, root, firstContractLeaf, result.ContractsProof.Nodes), + crypto.Pedersen) + }) + + t.Run("second contract proof verification", func(t *testing.T) { + t.Parallel() + + t.Skip("verification issue on the length=1 edge node") + secondContractAddr := utils.HexToFelt(t, "0x5fbaa249500be29fee38fdd90a7a2651a8d3935c14167570f6863f563d838f0") + secondContractLeaf := utils.HexToFelt(t, "0x25790175fe1fbeed47cbf510a41fba8676bea20a0c8888d4b9090b8f5cf19b8") + verifyIf(t, + root, secondContractAddr, secondContractLeaf, + extractAndReorder(t, root, secondContractLeaf, result.ContractsProof.Nodes), + crypto.Pedersen) + }) +} + +func arityTest(t *testing.T, + proof *rpc.StorageProofResult, + classesProofArity int, + contractsProofNodesArity int, + contractsProofLeavesArity int, + contractStorageArity int, +) { + require.Len(t, proof.ClassesProof, classesProofArity) + require.Len(t, proof.ContractsStorageProofs, contractStorageArity) + require.NotNil(t, proof.ContractsProof) + require.Len(t, proof.ContractsProof.Nodes, contractsProofNodesArity) + require.Len(t, proof.ContractsProof.LeavesData, contractsProofLeavesArity) +} + +func emptyTrie(t *testing.T) *trie.Trie { + memdb := pebble.NewMemTest(t) + txn, err := memdb.NewTransaction(true) + require.NoError(t, err) + + tempTrie, err := trie.NewTriePedersen(trie.NewStorage(txn, []byte{0}), 251) + require.NoError(t, err) + return tempTrie +} + +func verifyIf( + t *testing.T, + root, key, value *felt.Felt, + proof []*rpc.HashToNode, + hashF trie.HashFunc, +) { + t.Helper() + + pnodes := []trie.ProofNode{} + for _, hn := range proof { + pnodes = append(pnodes, hn.Node.AsProofNode()) + } + + kbs := key.Bytes() + kkey := trie.NewKey(251, kbs[:]) + require.True(t, trie.VerifyProof(root, &kkey, value, pnodes, hashF)) +} + func verifyGlobalStateRoot(t *testing.T, globalStateRoot, classRoot, storageRoot *felt.Felt) { stateVersion := new(felt.Felt).SetBytes([]byte(`STARKNET_STATE_V0`)) if classRoot.IsZero() { @@ -470,3 +736,47 @@ func verifyGlobalStateRoot(t *testing.T, globalStateRoot, classRoot, storageRoot assert.Equal(t, globalStateRoot, crypto.PoseidonArray(stateVersion, storageRoot, classRoot)) } } + +// extractAndReorder extracts single proof path from the root node to the edge node with `Child == leaf` +// nodes may contain many paths for different leaves, so we select one starting from the root and leading to the leaf +func extractAndReorder(t *testing.T, root, leaf *felt.Felt, nodes []*rpc.HashToNode) []*rpc.HashToNode { + t.Helper() + + // parents is reversed child to parent node mapping + parents := make(map[felt.Felt]*rpc.HashToNode) + for _, node := range nodes { + switch it := node.Node.(type) { + case *rpc.MerkleEdgeNode: + parents[*it.Child] = node + case *rpc.MerkleBinaryNode: + parents[*it.Left] = node + parents[*it.Right] = node + } + } + require.Contains(t, parents, *leaf) + + // extract path from leaf to root + path := []*rpc.HashToNode{} + limit := 256 + for next := *leaf; next != *root; { + node := parents[next] + path = append(path, node) + next = *node.Hash + + limit-- + if limit == 0 { + t.Fatal("cycle in the proof path") + } + } + + edge := parents[*leaf].Hash + require.Equal(t, edge, path[0].Hash) + require.Equal(t, root, path[len(path)-1].Hash) + + // reverse the path to start from the root + for i, j := 0, len(path)-1; i < j; i, j = i+1, j-1 { + path[i], path[j] = path[j], path[i] + } + + return path +}