Skip to content

Commit

Permalink
fix(trie): use cached Merkle values for root hash
Browse files Browse the repository at this point in the history
  • Loading branch information
qdm12 committed Nov 14, 2022
1 parent 46e19da commit f6275f6
Show file tree
Hide file tree
Showing 3 changed files with 37 additions and 113 deletions.
2 changes: 1 addition & 1 deletion internal/trie/node/branch_encode.go
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ func encodeChild(child *Node, buffer io.Writer) (err error) {
// and then SCALE encodes it. This is used to encode children
// nodes of branches.
func scaleEncodeHash(node *Node) (encoding []byte, err error) {
_, merkleValue, err := node.EncodeAndHash()
merkleValue, err := node.CalculateMerkleValue()
if err != nil {
return nil, fmt.Errorf("encoding and hashing %s: %w", node.Kind(), err)
}
Expand Down
28 changes: 10 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,17 @@ 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 {
// no need to wrap the error really
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
120 changes: 26 additions & 94 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,
}
assert.Equal(t, expected, EmptyHash)
}

func Test_NewEmptyTrie(t *testing.T) {
expectedTrie := &Trie{
childTries: make(map[common.Hash]*Trie),
Expand Down Expand Up @@ -292,97 +303,6 @@ func Test_Trie_RootNode(t *testing.T) {

//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, 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 @@ -435,6 +355,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,
},
},
},
},
Expand All @@ -456,8 +382,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

0 comments on commit f6275f6

Please sign in to comment.