Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(trie): use cached Merkle values for root hash #2943

Merged
merged 1 commit into from
Nov 15, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion internal/trie/node/branch_encode.go
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ func encodeChild(child *Node, buffer io.Writer) (err error) {
return nil
}

_, merkleValue, err := child.EncodeAndHash()
merkleValue, err := child.CalculateMerkleValue()
if err != nil {
return fmt.Errorf("computing %s Merkle value: %w", child.Kind(), err)
}
Expand Down
77 changes: 0 additions & 77 deletions lib/trie/buffer_mock_test.go

This file was deleted.

9 changes: 0 additions & 9 deletions lib/trie/helpers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
package trie

import (
"errors"
"math/rand"
"testing"
"time"
Expand All @@ -13,14 +12,6 @@ import (
"github.com/stretchr/testify/require"
)

type writeCall struct {
written []byte
n int
err error
}

var errTest = errors.New("test error")

type keyValues struct {
key []byte
value []byte
Expand Down
27 changes: 9 additions & 18 deletions lib/trie/trie.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import (
)

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

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

// encodeRoot writes the encoding of the root node to the buffer.
func encodeRoot(root *Node, buffer node.Buffer) (err error) {
if root == nil {
_, err = buffer.Write([]byte{0})
if err != nil {
return fmt.Errorf("cannot write nil root node to buffer: %w", err)
}
return nil
}
return root.Encode(buffer)
}

// MustHash returns the hashed root of the trie.
// It panics if it fails to hash the root node.
func (t *Trie) MustHash() common.Hash {
Expand All @@ -195,13 +183,16 @@ func (t *Trie) MustHash() common.Hash {

// Hash returns the hashed root of the trie.
func (t *Trie) Hash() (rootHash common.Hash, err error) {
buffer := bytes.NewBuffer(nil)
err = encodeRoot(t.root, buffer)
if err != nil {
return [32]byte{}, err
if t.root == nil {
return EmptyHash, nil
}

return common.Blake2bHash(buffer.Bytes()) // TODO optimisation: use hashers sync pools
merkleValue, err := t.root.CalculateRootMerkleValue()
if err != nil {
return rootHash, err
}
copy(rootHash[:], merkleValue)
return rootHash, nil
}

// Entries returns all the key-value pairs in the trie as a map of keys to values
Expand Down
123 changes: 26 additions & 97 deletions lib/trie/trie_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,22 @@ import (

"github.com/ChainSafe/gossamer/internal/trie/node"
"github.com/ChainSafe/gossamer/lib/common"
gomock "github.com/golang/mock/gomock"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func Test_EmptyHash(t *testing.T) {
t.Parallel()

expected := common.Hash{
0x3, 0x17, 0xa, 0x2e, 0x75, 0x97, 0xb7, 0xb7,
0xe3, 0xd8, 0x4c, 0x5, 0x39, 0x1d, 0x13, 0x9a,
0x62, 0xb1, 0x57, 0xe7, 0x87, 0x86, 0xd8, 0xc0,
0x82, 0xf2, 0x9d, 0xcf, 0x4c, 0x11, 0x13, 0x14,
}
qdm12 marked this conversation as resolved.
Show resolved Hide resolved
assert.Equal(t, expected, EmptyHash)
}

func Test_NewEmptyTrie(t *testing.T) {
expectedTrie := &Trie{
childTries: make(map[common.Hash]*Trie),
Expand Down Expand Up @@ -290,100 +301,6 @@ func Test_Trie_RootNode(t *testing.T) {
assert.Equal(t, expectedRoot, root)
}

//go:generate mockgen -destination=buffer_mock_test.go -package $GOPACKAGE github.com/ChainSafe/gossamer/internal/trie/node Buffer

func Test_encodeRoot(t *testing.T) {
t.Parallel()

testCases := map[string]struct {
root *Node
writeCalls []writeCall
errWrapped error
errMessage string
expectedRoot *Node
}{
"nil root and no error": {
writeCalls: []writeCall{
{written: []byte{0}},
},
},
"nil root and write error": {
writeCalls: []writeCall{
{
written: []byte{0},
err: errTest,
},
},
errWrapped: errTest,
errMessage: "cannot write nil root node to buffer: test error",
},
"root encoding error": {
root: &Node{
Key: []byte{1, 2},
SubValue: []byte{1},
},
writeCalls: []writeCall{
{
written: []byte{66},
err: errTest,
},
},
errWrapped: errTest,
errMessage: "cannot encode header: test error",
expectedRoot: &Node{
Key: []byte{1, 2},
SubValue: []byte{1},
},
},
"root encoding success": {
root: &Node{
Key: []byte{1, 2},
SubValue: []byte{1},
},
writeCalls: []writeCall{
{written: []byte{66}},
{written: []byte{18}},
{written: []byte{4}},
{written: []byte{1}},
},
expectedRoot: &Node{
Key: []byte{1, 2},
SubValue: []byte{1},
},
},
}

for name, testCase := range testCases {
testCase := testCase
t.Run(name, func(t *testing.T) {
t.Parallel()
ctrl := gomock.NewController(t)

buffer := NewMockBuffer(ctrl)

var previousCall *gomock.Call
for _, write := range testCase.writeCalls {
call := buffer.EXPECT().
Write(write.written).
Return(write.n, write.err)

if previousCall != nil {
call.After(previousCall)
}
previousCall = call
}

err := encodeRoot(testCase.root, buffer)

assert.ErrorIs(t, err, testCase.errWrapped)
if testCase.errWrapped != nil {
assert.EqualError(t, err, testCase.errMessage)
}
assert.Equal(t, testCase.expectedRoot, testCase.root)
})
}
}

func Test_Trie_MustHash(t *testing.T) {
t.Parallel()

Expand Down Expand Up @@ -436,6 +353,12 @@ func Test_Trie_Hash(t *testing.T) {
root: &Node{
Key: []byte{1, 2, 3},
SubValue: []byte{1},
MerkleValue: []byte{
0xa8, 0x13, 0x7c, 0xee, 0xb4, 0xad, 0xea, 0xac,
0x9e, 0x5b, 0x37, 0xe2, 0x8e, 0x7d, 0x64, 0x78,
0xac, 0xba, 0xb0, 0x6e, 0x90, 0x76, 0xe4, 0x67,
0xa1, 0xd8, 0xa2, 0x29, 0x4e, 0x4a, 0xd9, 0xa3,
},
qdm12 marked this conversation as resolved.
Show resolved Hide resolved
},
},
},
Expand All @@ -457,8 +380,14 @@ func Test_Trie_Hash(t *testing.T) {
0xf0, 0xe, 0xd3, 0x39, 0x48, 0x21, 0xe3, 0xdd},
expectedTrie: Trie{
root: &Node{
Key: []byte{1, 2, 3},
SubValue: []byte("branch"),
Key: []byte{1, 2, 3},
SubValue: []byte("branch"),
MerkleValue: []byte{
0xaa, 0x7e, 0x57, 0x48, 0xb0, 0x27, 0x4d, 0x18,
0xf5, 0x1c, 0xfd, 0x36, 0x4c, 0x4b, 0x56, 0x4a,
0xf5, 0x37, 0x9d, 0xd7, 0xcb, 0xf5, 0x80, 0x15,
0xf0, 0x0e, 0xd3, 0x39, 0x48, 0x21, 0xe3, 0xdd,
},
Descendants: 1,
Children: padRightChildren([]*Node{
{
Expand Down