erigon-pulse/turbo/trie/retain_list_test.go

152 lines
4.6 KiB
Go
Raw Normal View History

package trie
import (
"github.com/ledgerwatch/erigon-lib/common/hexutil"
"testing"
"github.com/holiman/uint256"
libcommon "github.com/ledgerwatch/erigon-lib/common"
"github.com/ledgerwatch/erigon/core/types/accounts"
"github.com/stretchr/testify/require"
)
func FakePreimage(hash libcommon.Hash) libcommon.Hash {
result := hash
for i, b := range hash {
result[i] = b ^ 1
}
return result
}
Add additional trie proof testing (#7382) This PR extends the function merged in #7337 to cover the proof generation paths as well. The first commit simply migrates the internal proof checking code from the rpc `eth_getProof` test to be available externally exported under `turbo/trie`. In the process, it translates the in place testing assertions to return more normally as errors and adapts to use the pre-existing trie node types which are defined slightly differently (although this code is still intended only for testing purposes). The second commit refactors the existing trie fuzzing tests from the previous PR and adds new fuzzing tests for the proof generation. In order to validate the proofs, these fuzzing tests fundamentally do two things. Firstly, after bootstrapping the test (by seeding, and modifying the db), for each key we compute the 'naive' proof utilizing the existing code in `proof.go` and the in memory `trie.Trie` structure. The `trie.Trie` code actually had a couple small bugs which are fixed in this PR (not handling value nodes, and not honoring the `NewTestRLPTrie` contract of pre-RLP encoded nodes in proof generation). Secondly, we re-compute the same proof for the flatDB production variant, and verify that it is exactly the same proof as computed by the naive implementation. This fuzzing has been run for ~72 hours locally with no errors. Although this code hasn't identified any new bugs in the proof generation path, it improves coverage and should help to prevent regressions. Additional extensions will be provided in a subsequent PR. --------- Co-authored-by: Jason Yellick <jason@enya.ai>
2023-04-25 22:33:46 -04:00
// NewManualProofRetainer is a way to allow external tests in this package to
// manually construct a ProofRetainer based on a set of keys. This is
// especially useful for tests which want to manually manipulate the hash
// databases without worrying about generating and tracking pre-images.
func NewManualProofRetainer(t *testing.T, acc *accounts.Account, rl *RetainList, keys [][]byte) *ProofRetainer {
Add additional trie proof testing (#7382) This PR extends the function merged in #7337 to cover the proof generation paths as well. The first commit simply migrates the internal proof checking code from the rpc `eth_getProof` test to be available externally exported under `turbo/trie`. In the process, it translates the in place testing assertions to return more normally as errors and adapts to use the pre-existing trie node types which are defined slightly differently (although this code is still intended only for testing purposes). The second commit refactors the existing trie fuzzing tests from the previous PR and adds new fuzzing tests for the proof generation. In order to validate the proofs, these fuzzing tests fundamentally do two things. Firstly, after bootstrapping the test (by seeding, and modifying the db), for each key we compute the 'naive' proof utilizing the existing code in `proof.go` and the in memory `trie.Trie` structure. The `trie.Trie` code actually had a couple small bugs which are fixed in this PR (not handling value nodes, and not honoring the `NewTestRLPTrie` contract of pre-RLP encoded nodes in proof generation). Secondly, we re-compute the same proof for the flatDB production variant, and verify that it is exactly the same proof as computed by the naive implementation. This fuzzing has been run for ~72 hours locally with no errors. Although this code hasn't identified any new bugs in the proof generation path, it improves coverage and should help to prevent regressions. Additional extensions will be provided in a subsequent PR. --------- Co-authored-by: Jason Yellick <jason@enya.ai>
2023-04-25 22:33:46 -04:00
var accHexKey []byte
var storageKeys []libcommon.Hash
var storageHexKeys [][]byte
for _, key := range keys {
switch len(key) {
case 32:
require.Nil(t, accHexKey, "only one account key may be provided")
accHexKey = rl.AddKey(key)
case 72:
if accHexKey == nil {
accHexKey = rl.AddKey(key[:32])
}
storageKeys = append(storageKeys, FakePreimage(libcommon.BytesToHash(key[40:])))
Add additional trie proof testing (#7382) This PR extends the function merged in #7337 to cover the proof generation paths as well. The first commit simply migrates the internal proof checking code from the rpc `eth_getProof` test to be available externally exported under `turbo/trie`. In the process, it translates the in place testing assertions to return more normally as errors and adapts to use the pre-existing trie node types which are defined slightly differently (although this code is still intended only for testing purposes). The second commit refactors the existing trie fuzzing tests from the previous PR and adds new fuzzing tests for the proof generation. In order to validate the proofs, these fuzzing tests fundamentally do two things. Firstly, after bootstrapping the test (by seeding, and modifying the db), for each key we compute the 'naive' proof utilizing the existing code in `proof.go` and the in memory `trie.Trie` structure. The `trie.Trie` code actually had a couple small bugs which are fixed in this PR (not handling value nodes, and not honoring the `NewTestRLPTrie` contract of pre-RLP encoded nodes in proof generation). Secondly, we re-compute the same proof for the flatDB production variant, and verify that it is exactly the same proof as computed by the naive implementation. This fuzzing has been run for ~72 hours locally with no errors. Although this code hasn't identified any new bugs in the proof generation path, it improves coverage and should help to prevent regressions. Additional extensions will be provided in a subsequent PR. --------- Co-authored-by: Jason Yellick <jason@enya.ai>
2023-04-25 22:33:46 -04:00
storageHexKeys = append(storageHexKeys, rl.AddKey(key))
require.Equal(t, accHexKey, storageHexKeys[0][:64], "all storage keys must be for the same account")
default:
require.Fail(t, "unexpected key length %d", len(key))
}
}
return &ProofRetainer{
rl: rl,
acc: acc,
Add additional trie proof testing (#7382) This PR extends the function merged in #7337 to cover the proof generation paths as well. The first commit simply migrates the internal proof checking code from the rpc `eth_getProof` test to be available externally exported under `turbo/trie`. In the process, it translates the in place testing assertions to return more normally as errors and adapts to use the pre-existing trie node types which are defined slightly differently (although this code is still intended only for testing purposes). The second commit refactors the existing trie fuzzing tests from the previous PR and adds new fuzzing tests for the proof generation. In order to validate the proofs, these fuzzing tests fundamentally do two things. Firstly, after bootstrapping the test (by seeding, and modifying the db), for each key we compute the 'naive' proof utilizing the existing code in `proof.go` and the in memory `trie.Trie` structure. The `trie.Trie` code actually had a couple small bugs which are fixed in this PR (not handling value nodes, and not honoring the `NewTestRLPTrie` contract of pre-RLP encoded nodes in proof generation). Secondly, we re-compute the same proof for the flatDB production variant, and verify that it is exactly the same proof as computed by the naive implementation. This fuzzing has been run for ~72 hours locally with no errors. Although this code hasn't identified any new bugs in the proof generation path, it improves coverage and should help to prevent regressions. Additional extensions will be provided in a subsequent PR. --------- Co-authored-by: Jason Yellick <jason@enya.ai>
2023-04-25 22:33:46 -04:00
accHexKey: accHexKey,
storageKeys: storageKeys,
storageHexKeys: storageHexKeys,
}
}
func TestProofRetainerConstruction(t *testing.T) {
rl := NewRetainList(0)
pr, err := NewProofRetainer(
libcommon.Address{0x1},
&accounts.Account{
Initialised: true,
Nonce: 2,
Balance: *uint256.NewInt(6e9),
CodeHash: libcommon.Hash{3},
Incarnation: 3,
},
[]libcommon.Hash{{1}, {2}, {3}},
rl,
)
require.NoError(t, err)
require.Len(t, rl.hexes, 4)
validKeys := [][]byte{
pr.storageHexKeys[2][:],
pr.storageHexKeys[2][:98],
pr.storageHexKeys[2][:95],
pr.storageHexKeys[1][:],
pr.storageHexKeys[1][:90],
pr.storageHexKeys[0][:],
pr.storageHexKeys[0][:85],
pr.accHexKey[:],
pr.accHexKey[:15],
{},
}
invalidKeys := [][]byte{
pr.accHexKey[1:16],
pr.storageHexKeys[0][12:80],
pr.storageHexKeys[2][19:90],
}
for _, key := range validKeys {
pe := pr.ProofElement(key)
require.NotNil(t, pe)
require.Equal(t, pe.hexKey, key)
switch len(key) {
case 64: // Account leaf key
pe.storageRoot = libcommon.Hash{3}
pe.storageRootKey = key
case 144: // Storage leaf key
pe.storageValue = uint256.NewInt(5)
pe.storageKey = key[2*(32+8):]
}
pe.proof.Write(key)
}
for _, key := range invalidKeys {
pe := pr.ProofElement(key)
require.Nil(t, pe)
}
require.Equal(t, len(validKeys), len(pr.proofs))
accProof, err := pr.ProofResult()
require.NoError(t, err)
require.Len(t, accProof.AccountProof, 3)
require.Equal(t, []byte(nil), []byte(accProof.AccountProof[0]))
require.Equal(t, validKeys[8], []byte(accProof.AccountProof[1]))
require.Equal(t, validKeys[7], []byte(accProof.AccountProof[2]))
require.Len(t, accProof.StorageProof, 3)
require.Equal(t, accProof.StorageProof[0].Key, libcommon.Hash{1})
require.Len(t, accProof.StorageProof[0].Proof, 2)
require.Equal(t, validKeys[6], []byte(accProof.StorageProof[0].Proof[0]))
require.Equal(t, validKeys[5], []byte(accProof.StorageProof[0].Proof[1]))
require.Equal(t, accProof.StorageProof[1].Key, libcommon.Hash{2})
require.Len(t, accProof.StorageProof[1].Proof, 2)
require.Equal(t, validKeys[4], []byte(accProof.StorageProof[1].Proof[0]))
require.Equal(t, validKeys[3], []byte(accProof.StorageProof[1].Proof[1]))
require.Equal(t, accProof.StorageProof[2].Key, libcommon.Hash{3})
require.Len(t, accProof.StorageProof[2].Proof, 3)
require.Equal(t, validKeys[2], []byte(accProof.StorageProof[2].Proof[0]))
require.Equal(t, validKeys[1], []byte(accProof.StorageProof[2].Proof[1]))
require.Equal(t, validKeys[0], []byte(accProof.StorageProof[2].Proof[2]))
t.Run("missingStorageRoot", func(t *testing.T) {
oldStorageHash := pr.proofs[2].storageRoot
pr.proofs[2].storageRoot = libcommon.Hash{}
defer func() { pr.proofs[2].storageRoot = oldStorageHash }()
_, err := pr.ProofResult()
require.Error(t, err, "did not find storage root in proof elements")
})
t.Run("missingStorageValue", func(t *testing.T) {
oldKey := pr.proofs[4].storageValue
pr.proofs[4].storageValue = nil
defer func() { pr.proofs[4].storageValue = oldKey }()
accProof, err := pr.ProofResult()
require.NoError(t, err)
require.Equal(t, &hexutil.Big{}, accProof.StorageProof[0].Value)
})
}