Skip to content

Commit a475bfa

Browse files
committed
Generate generates deduplicated encoded proof nodes for N full keys
- Load trie only once from database - Move StorageState `GenerateTrieProof` to `lib/trie/proof` package - Update tests
1 parent 58dbc47 commit a475bfa

File tree

5 files changed

+151
-51
lines changed

5 files changed

+151
-51
lines changed

dot/state/storage.go

Lines changed: 2 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -303,30 +303,6 @@ func (s *StorageState) LoadCodeHash(hash *common.Hash) (common.Hash, error) {
303303

304304
// GenerateTrieProof returns the proofs related to the keys on the state root trie
305305
func (s *StorageState) GenerateTrieProof(stateRoot common.Hash, keys [][]byte) (
306-
proofs [][]byte, err error) {
307-
proofHashes := make(map[common.Hash]struct{}) // to avoid duplicates
308-
for _, key := range keys {
309-
encodedProofNodes, err := proof.Generate(stateRoot[:], key, s.db)
310-
if err != nil {
311-
return nil, err
312-
}
313-
314-
for _, encodedProofNode := range encodedProofNodes {
315-
proofHash, err := common.Blake2bHash(encodedProofNode)
316-
if err != nil {
317-
return nil, fmt.Errorf("blake2b hash: %w", err)
318-
}
319-
320-
_, exists := proofHashes[proofHash]
321-
if exists {
322-
// this encoded node was encountered previously, skip it.
323-
continue
324-
}
325-
326-
proofHashes[proofHash] = struct{}{}
327-
proofs = append(proofs, encodedProofNode)
328-
}
329-
}
330-
331-
return proofs, nil
306+
encodedProofNodes [][]byte, err error) {
307+
return proof.Generate(stateRoot[:], keys, s.db)
332308
}

lib/runtime/wasmer/imports_test.go

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1797,12 +1797,8 @@ func Test_ext_trie_blake2_256_verify_proof_version_1(t *testing.T) {
17971797
root := hash.ToBytes()
17981798
otherRoot := otherHash.ToBytes()
17991799

1800-
var allProofs [][]byte
1801-
for _, key := range keys {
1802-
singleProof, err := proof.Generate(root, key, memdb)
1803-
require.NoError(t, err)
1804-
allProofs = append(allProofs, singleProof...)
1805-
}
1800+
allProofs, err := proof.Generate(root, keys, memdb)
1801+
require.NoError(t, err)
18061802

18071803
testcases := map[string]struct {
18081804
root, key, value []byte

lib/trie/proof/generate.go

Lines changed: 31 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -24,24 +24,43 @@ type Database interface {
2424
Get(key []byte) (value []byte, err error)
2525
}
2626

27-
// Generate returns the encoded proof nodes for the trie
28-
// corresponding to the root hash given, and for the (Little Endian)
29-
// full key given. The database given is used to load the trie
30-
// using the root hash given.
31-
func Generate(rootHash []byte, fullKey []byte, database Database) (
27+
// Generate generates and deduplicates the encoded proof nodes
28+
// for the trie corresponding to the root hash given, and for
29+
// the slice of (Little Endian) full keys given. The database given
30+
// is used to load the trie using the root hash given.
31+
func Generate(rootHash []byte, fullKeys [][]byte, database Database) (
3232
encodedProofNodes [][]byte, err error) {
3333
trie := trie.NewEmptyTrie()
3434
if err := trie.Load(database, common.BytesToHash(rootHash)); err != nil {
3535
return nil, fmt.Errorf("loading trie: %w", err)
3636
}
37-
3837
rootNode := trie.RootNode()
39-
fullKeyNibbles := codec.KeyLEToNibbles(fullKey)
40-
encodedProofNodes, err = walk(rootNode, fullKeyNibbles)
41-
if err != nil {
42-
// Note we wrap the full key context here since walk is recursive and
43-
// may not be aware of the initial full key.
44-
return nil, fmt.Errorf("walking to node at key 0x%x: %w", fullKey, err)
38+
39+
hashesSeen := make(map[string]struct{})
40+
for _, fullKey := range fullKeys {
41+
fullKeyNibbles := codec.KeyLEToNibbles(fullKey)
42+
newEncodedProofNodes, err := walk(rootNode, fullKeyNibbles)
43+
if err != nil {
44+
// Note we wrap the full key context here since walk is recursive and
45+
// may not be aware of the initial full key.
46+
return nil, fmt.Errorf("walking to node at key 0x%x: %w", fullKey, err)
47+
}
48+
49+
for _, encodedProofNode := range newEncodedProofNodes {
50+
digest, err := common.Blake2bHash(encodedProofNode)
51+
if err != nil {
52+
return nil, fmt.Errorf("blake2b hash: %w", err)
53+
}
54+
hashString := string(digest.ToBytes())
55+
56+
_, seen := hashesSeen[hashString]
57+
if seen {
58+
continue
59+
}
60+
hashesSeen[hashString] = struct{}{}
61+
62+
encodedProofNodes = append(encodedProofNodes, encodedProofNode)
63+
}
4564
}
4665

4766
return encodedProofNodes, nil

lib/trie/proof/generate_test.go

Lines changed: 114 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ func Test_Generate(t *testing.T) {
2727

2828
testCases := map[string]struct {
2929
rootHash []byte
30-
fullKey []byte // nibbles
30+
fullKeysNibbles [][]byte
3131
databaseBuilder func(ctrl *gomock.Controller) Database
3232
encodedProofNodes [][]byte
3333
errWrapped error
@@ -48,7 +48,8 @@ func Test_Generate(t *testing.T) {
4848
"test error",
4949
},
5050
"walk error": {
51-
rootHash: someHash,
51+
rootHash: someHash,
52+
fullKeysNibbles: [][]byte{{1}},
5253
databaseBuilder: func(ctrl *gomock.Controller) Database {
5354
mockDatabase := NewMockDatabase(ctrl)
5455
encodedRoot := encodeNode(t, node.Node{
@@ -59,12 +60,12 @@ func Test_Generate(t *testing.T) {
5960
Return(encodedRoot, nil)
6061
return mockDatabase
6162
},
62-
fullKey: []byte{1},
6363
errWrapped: ErrKeyNotFound,
6464
errMessage: "walking to node at key 0x01: key not found",
6565
},
6666
"leaf root": {
67-
rootHash: someHash,
67+
rootHash: someHash,
68+
fullKeysNibbles: [][]byte{{}},
6869
databaseBuilder: func(ctrl *gomock.Controller) Database {
6970
mockDatabase := NewMockDatabase(ctrl)
7071
encodedRoot := encodeNode(t, node.Node{
@@ -83,7 +84,8 @@ func Test_Generate(t *testing.T) {
8384
},
8485
},
8586
"branch root": {
86-
rootHash: someHash,
87+
rootHash: someHash,
88+
fullKeysNibbles: [][]byte{{}},
8789
databaseBuilder: func(ctrl *gomock.Controller) Database {
8890
mockDatabase := NewMockDatabase(ctrl)
8991
encodedRoot := encodeNode(t, node.Node{
@@ -115,6 +117,108 @@ func Test_Generate(t *testing.T) {
115117
}),
116118
},
117119
},
120+
"target leaf of branch": {
121+
rootHash: someHash,
122+
fullKeysNibbles: [][]byte{
123+
{1, 2, 3, 4},
124+
},
125+
databaseBuilder: func(ctrl *gomock.Controller) Database {
126+
mockDatabase := NewMockDatabase(ctrl)
127+
encodedRoot := encodeNode(t, node.Node{
128+
Key: []byte{1, 2},
129+
Value: []byte{2},
130+
Children: padRightChildren([]*node.Node{
131+
nil, nil, nil,
132+
{ // full key 1, 2, 3, 4
133+
Key: []byte{4},
134+
Value: []byte{4},
135+
},
136+
}),
137+
})
138+
mockDatabase.EXPECT().Get(someHash).
139+
Return(encodedRoot, nil)
140+
return mockDatabase
141+
},
142+
encodedProofNodes: [][]byte{
143+
encodeNode(t, node.Node{
144+
Key: []byte{1, 2},
145+
Value: []byte{2},
146+
Children: padRightChildren([]*node.Node{
147+
nil, nil, nil,
148+
{
149+
Key: []byte{4},
150+
Value: []byte{4},
151+
},
152+
}),
153+
}),
154+
encodeNode(t, node.Node{
155+
Key: []byte{4},
156+
Value: []byte{4},
157+
}),
158+
},
159+
},
160+
"deduplicate proof nodes": {
161+
rootHash: someHash,
162+
fullKeysNibbles: [][]byte{
163+
{1, 2, 3, 4},
164+
{1, 2, 4, 4},
165+
{1, 2, 5, 4},
166+
},
167+
databaseBuilder: func(ctrl *gomock.Controller) Database {
168+
mockDatabase := NewMockDatabase(ctrl)
169+
encodedRoot := encodeNode(t, node.Node{
170+
Key: []byte{1, 2},
171+
Value: []byte{2},
172+
Children: padRightChildren([]*node.Node{
173+
nil, nil, nil,
174+
{ // full key 1, 2, 3, 4
175+
Key: []byte{4},
176+
Value: []byte{4},
177+
},
178+
{ // full key 1, 2, 4, 4
179+
Key: []byte{4},
180+
Value: []byte{4},
181+
},
182+
{ // full key 1, 2, 5, 4
183+
Key: []byte{4},
184+
Value: []byte{5},
185+
},
186+
}),
187+
})
188+
mockDatabase.EXPECT().Get(someHash).
189+
Return(encodedRoot, nil)
190+
return mockDatabase
191+
},
192+
encodedProofNodes: [][]byte{
193+
encodeNode(t, node.Node{
194+
Key: []byte{1, 2},
195+
Value: []byte{2},
196+
Children: padRightChildren([]*node.Node{
197+
nil, nil, nil,
198+
{ // full key 1, 2, 3, 4
199+
Key: []byte{4},
200+
Value: []byte{4},
201+
},
202+
{ // full key 1, 2, 4, 4
203+
Key: []byte{4},
204+
Value: []byte{4},
205+
},
206+
{ // full key 1, 2, 5, 4
207+
Key: []byte{4},
208+
Value: []byte{5},
209+
},
210+
}),
211+
}),
212+
encodeNode(t, node.Node{
213+
Key: []byte{4},
214+
Value: []byte{4},
215+
}),
216+
encodeNode(t, node.Node{
217+
Key: []byte{4},
218+
Value: []byte{5},
219+
}),
220+
},
221+
},
118222
}
119223

120224
for name, testCase := range testCases {
@@ -124,9 +228,13 @@ func Test_Generate(t *testing.T) {
124228
ctrl := gomock.NewController(t)
125229

126230
database := testCase.databaseBuilder(ctrl)
231+
fullKeysLE := make([][]byte, len(testCase.fullKeysNibbles))
232+
for i, fullKeyNibbles := range testCase.fullKeysNibbles {
233+
fullKeysLE[i] = codec.NibblesToKeyLE(fullKeyNibbles)
234+
}
127235

128236
encodedProofNodes, err := Generate(testCase.rootHash,
129-
testCase.fullKey, database)
237+
fullKeysLE, database)
130238

131239
assert.ErrorIs(t, err, testCase.errWrapped)
132240
if testCase.errWrapped != nil {

lib/trie/proof/proof_test.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,8 @@ func Test_Generate_Verify(t *testing.T) {
4141
require.NoError(t, err)
4242

4343
for i, key := range keys {
44-
proof, err := Generate(rootHash.ToBytes(), []byte(key), database)
44+
fullKeys := [][]byte{[]byte(key)}
45+
proof, err := Generate(rootHash.ToBytes(), fullKeys, database)
4546
require.NoError(t, err)
4647

4748
expectedValue := fmt.Sprintf("%x-%d", key, i)

0 commit comments

Comments
 (0)