Skip to content

Commit ec2549a

Browse files
authored
fix(trie): use cached Merkle values for root hash (#2943)
- Use in-memory cached Merkle value for encoding children, instead of re-encoding all children - Use in-memory cached root hash for returning the trie root hash - Set the `EmptyHash` global variable using `hash([]byte{0})` directly
1 parent bb637ff commit ec2549a

File tree

5 files changed

+36
-202
lines changed

5 files changed

+36
-202
lines changed

internal/trie/node/branch_encode.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ func encodeChild(child *Node, buffer io.Writer) (err error) {
118118
return nil
119119
}
120120

121-
_, merkleValue, err := child.EncodeAndHash()
121+
merkleValue, err := child.CalculateMerkleValue()
122122
if err != nil {
123123
return fmt.Errorf("computing %s Merkle value: %w", child.Kind(), err)
124124
}

lib/trie/buffer_mock_test.go

Lines changed: 0 additions & 77 deletions
This file was deleted.

lib/trie/helpers_test.go

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
package trie
55

66
import (
7-
"errors"
87
"math/rand"
98
"testing"
109
"time"
@@ -13,14 +12,6 @@ import (
1312
"github.com/stretchr/testify/require"
1413
)
1514

16-
type writeCall struct {
17-
written []byte
18-
n int
19-
err error
20-
}
21-
22-
var errTest = errors.New("test error")
23-
2415
type keyValues struct {
2516
key []byte
2617
value []byte

lib/trie/trie.go

Lines changed: 9 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import (
1313
)
1414

1515
// EmptyHash is the empty trie hash.
16-
var EmptyHash, _ = NewEmptyTrie().Hash()
16+
var EmptyHash = common.MustBlake2bHash([]byte{0})
1717

1818
// Trie is a base 16 modified Merkle Patricia trie.
1919
type Trie struct {
@@ -170,18 +170,6 @@ func (t *Trie) RootNode() *Node {
170170
return t.root.Copy(copySettings)
171171
}
172172

173-
// encodeRoot writes the encoding of the root node to the buffer.
174-
func encodeRoot(root *Node, buffer node.Buffer) (err error) {
175-
if root == nil {
176-
_, err = buffer.Write([]byte{0})
177-
if err != nil {
178-
return fmt.Errorf("cannot write nil root node to buffer: %w", err)
179-
}
180-
return nil
181-
}
182-
return root.Encode(buffer)
183-
}
184-
185173
// MustHash returns the hashed root of the trie.
186174
// It panics if it fails to hash the root node.
187175
func (t *Trie) MustHash() common.Hash {
@@ -195,13 +183,16 @@ func (t *Trie) MustHash() common.Hash {
195183

196184
// Hash returns the hashed root of the trie.
197185
func (t *Trie) Hash() (rootHash common.Hash, err error) {
198-
buffer := bytes.NewBuffer(nil)
199-
err = encodeRoot(t.root, buffer)
200-
if err != nil {
201-
return [32]byte{}, err
186+
if t.root == nil {
187+
return EmptyHash, nil
202188
}
203189

204-
return common.Blake2bHash(buffer.Bytes()) // TODO optimisation: use hashers sync pools
190+
merkleValue, err := t.root.CalculateRootMerkleValue()
191+
if err != nil {
192+
return rootHash, err
193+
}
194+
copy(rootHash[:], merkleValue)
195+
return rootHash, nil
205196
}
206197

207198
// Entries returns all the key-value pairs in the trie as a map of keys to values

lib/trie/trie_test.go

Lines changed: 26 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,22 @@ import (
1111

1212
"github.com/ChainSafe/gossamer/internal/trie/node"
1313
"github.com/ChainSafe/gossamer/lib/common"
14-
gomock "github.com/golang/mock/gomock"
1514
"github.com/stretchr/testify/assert"
1615
"github.com/stretchr/testify/require"
1716
)
1817

18+
func Test_EmptyHash(t *testing.T) {
19+
t.Parallel()
20+
21+
expected := common.Hash{
22+
0x3, 0x17, 0xa, 0x2e, 0x75, 0x97, 0xb7, 0xb7,
23+
0xe3, 0xd8, 0x4c, 0x5, 0x39, 0x1d, 0x13, 0x9a,
24+
0x62, 0xb1, 0x57, 0xe7, 0x87, 0x86, 0xd8, 0xc0,
25+
0x82, 0xf2, 0x9d, 0xcf, 0x4c, 0x11, 0x13, 0x14,
26+
}
27+
assert.Equal(t, expected, EmptyHash)
28+
}
29+
1930
func Test_NewEmptyTrie(t *testing.T) {
2031
expectedTrie := &Trie{
2132
childTries: make(map[common.Hash]*Trie),
@@ -290,100 +301,6 @@ func Test_Trie_RootNode(t *testing.T) {
290301
assert.Equal(t, expectedRoot, root)
291302
}
292303

293-
//go:generate mockgen -destination=buffer_mock_test.go -package $GOPACKAGE github.com/ChainSafe/gossamer/internal/trie/node Buffer
294-
295-
func Test_encodeRoot(t *testing.T) {
296-
t.Parallel()
297-
298-
testCases := map[string]struct {
299-
root *Node
300-
writeCalls []writeCall
301-
errWrapped error
302-
errMessage string
303-
expectedRoot *Node
304-
}{
305-
"nil root and no error": {
306-
writeCalls: []writeCall{
307-
{written: []byte{0}},
308-
},
309-
},
310-
"nil root and write error": {
311-
writeCalls: []writeCall{
312-
{
313-
written: []byte{0},
314-
err: errTest,
315-
},
316-
},
317-
errWrapped: errTest,
318-
errMessage: "cannot write nil root node to buffer: test error",
319-
},
320-
"root encoding error": {
321-
root: &Node{
322-
Key: []byte{1, 2},
323-
SubValue: []byte{1},
324-
},
325-
writeCalls: []writeCall{
326-
{
327-
written: []byte{66},
328-
err: errTest,
329-
},
330-
},
331-
errWrapped: errTest,
332-
errMessage: "cannot encode header: test error",
333-
expectedRoot: &Node{
334-
Key: []byte{1, 2},
335-
SubValue: []byte{1},
336-
},
337-
},
338-
"root encoding success": {
339-
root: &Node{
340-
Key: []byte{1, 2},
341-
SubValue: []byte{1},
342-
},
343-
writeCalls: []writeCall{
344-
{written: []byte{66}},
345-
{written: []byte{18}},
346-
{written: []byte{4}},
347-
{written: []byte{1}},
348-
},
349-
expectedRoot: &Node{
350-
Key: []byte{1, 2},
351-
SubValue: []byte{1},
352-
},
353-
},
354-
}
355-
356-
for name, testCase := range testCases {
357-
testCase := testCase
358-
t.Run(name, func(t *testing.T) {
359-
t.Parallel()
360-
ctrl := gomock.NewController(t)
361-
362-
buffer := NewMockBuffer(ctrl)
363-
364-
var previousCall *gomock.Call
365-
for _, write := range testCase.writeCalls {
366-
call := buffer.EXPECT().
367-
Write(write.written).
368-
Return(write.n, write.err)
369-
370-
if previousCall != nil {
371-
call.After(previousCall)
372-
}
373-
previousCall = call
374-
}
375-
376-
err := encodeRoot(testCase.root, buffer)
377-
378-
assert.ErrorIs(t, err, testCase.errWrapped)
379-
if testCase.errWrapped != nil {
380-
assert.EqualError(t, err, testCase.errMessage)
381-
}
382-
assert.Equal(t, testCase.expectedRoot, testCase.root)
383-
})
384-
}
385-
}
386-
387304
func Test_Trie_MustHash(t *testing.T) {
388305
t.Parallel()
389306

@@ -436,6 +353,12 @@ func Test_Trie_Hash(t *testing.T) {
436353
root: &Node{
437354
Key: []byte{1, 2, 3},
438355
SubValue: []byte{1},
356+
MerkleValue: []byte{
357+
0xa8, 0x13, 0x7c, 0xee, 0xb4, 0xad, 0xea, 0xac,
358+
0x9e, 0x5b, 0x37, 0xe2, 0x8e, 0x7d, 0x64, 0x78,
359+
0xac, 0xba, 0xb0, 0x6e, 0x90, 0x76, 0xe4, 0x67,
360+
0xa1, 0xd8, 0xa2, 0x29, 0x4e, 0x4a, 0xd9, 0xa3,
361+
},
439362
},
440363
},
441364
},
@@ -457,8 +380,14 @@ func Test_Trie_Hash(t *testing.T) {
457380
0xf0, 0xe, 0xd3, 0x39, 0x48, 0x21, 0xe3, 0xdd},
458381
expectedTrie: Trie{
459382
root: &Node{
460-
Key: []byte{1, 2, 3},
461-
SubValue: []byte("branch"),
383+
Key: []byte{1, 2, 3},
384+
SubValue: []byte("branch"),
385+
MerkleValue: []byte{
386+
0xaa, 0x7e, 0x57, 0x48, 0xb0, 0x27, 0x4d, 0x18,
387+
0xf5, 0x1c, 0xfd, 0x36, 0x4c, 0x4b, 0x56, 0x4a,
388+
0xf5, 0x37, 0x9d, 0xd7, 0xcb, 0xf5, 0x80, 0x15,
389+
0xf0, 0x0e, 0xd3, 0x39, 0x48, 0x21, 0xe3, 0xdd,
390+
},
462391
Descendants: 1,
463392
Children: padRightChildren([]*Node{
464393
{

0 commit comments

Comments
 (0)