diff --git a/internal/trie/node/children.go b/internal/trie/node/children.go index b08c711c9ef..725366b42e1 100644 --- a/internal/trie/node/children.go +++ b/internal/trie/node/children.go @@ -30,3 +30,13 @@ func (n *Node) NumChildren() (count int) { } return count } + +// HasChild returns true if the node has at least one child. +func (n *Node) HasChild() (has bool) { + for _, child := range n.Children { + if child != nil { + return true + } + } + return false +} diff --git a/internal/trie/node/children_test.go b/internal/trie/node/children_test.go index 66a26030091..17ab48b2f16 100644 --- a/internal/trie/node/children_test.go +++ b/internal/trie/node/children_test.go @@ -118,3 +118,42 @@ func Test_Node_NumChildren(t *testing.T) { }) } } + +func Test_Node_HasChild(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + node Node + has bool + }{ + "no child": {}, + "one child at index 0": { + node: Node{ + Children: []*Node{ + {}, + }, + }, + has: true, + }, + "one child at index 1": { + node: Node{ + Children: []*Node{ + nil, + {}, + }, + }, + has: true, + }, + } + + for name, testCase := range testCases { + testCase := testCase + t.Run(name, func(t *testing.T) { + t.Parallel() + + has := testCase.node.HasChild() + + assert.Equal(t, testCase.has, has) + }) + } +} diff --git a/lib/trie/proof/generate_test.go b/lib/trie/proof/generate_test.go index 6075e1d22a2..8c2b2aba0e9 100644 --- a/lib/trie/proof/generate_test.go +++ b/lib/trie/proof/generate_test.go @@ -153,16 +153,8 @@ func Test_walk(t *testing.T) { errWrapped: ErrKeyNotFound, errMessage: "key not found", }, - "parent encode and hash error": { - parent: &node.Node{ - Key: make([]byte, int(^uint16(0))+63), - Value: []byte{1}, - }, - errWrapped: node.ErrPartialKeyTooBig, - errMessage: "encode node: " + - "cannot encode header: partial key length cannot " + - "be larger than or equal to 2^16: 65535", - }, + // The parent encode error cannot be triggered here + // since it can only be caused by a buffer.Write error. "parent leaf and empty full key": { parent: &node.Node{ Key: []byte{1, 2}, diff --git a/lib/trie/proof/helpers_test.go b/lib/trie/proof/helpers_test.go index 1df4a6b0441..f7279f75e2d 100644 --- a/lib/trie/proof/helpers_test.go +++ b/lib/trie/proof/helpers_test.go @@ -5,10 +5,12 @@ package proof import ( "bytes" + "math/rand" "testing" "github.com/ChainSafe/gossamer/internal/trie/node" "github.com/ChainSafe/gossamer/lib/common" + "github.com/ChainSafe/gossamer/pkg/scale" "github.com/stretchr/testify/require" ) @@ -29,8 +31,72 @@ func encodeNode(t *testing.T, node node.Node) (encoded []byte) { func blake2bNode(t *testing.T, node node.Node) (digest []byte) { t.Helper() encoding := encodeNode(t, node) - digestHash, err := common.Blake2bHash(encoding) + return blake2b(t, encoding) +} + +func scaleEncode(t *testing.T, data []byte) (encoded []byte) { + t.Helper() + encoded, err := scale.Marshal(data) + require.NoError(t, err) + return encoded +} + +func blake2b(t *testing.T, data []byte) (digest []byte) { + t.Helper() + digestHash, err := common.Blake2bHash(data) require.NoError(t, err) digest = digestHash[:] return digest } + +func concatBytes(slices [][]byte) (concatenated []byte) { + for _, slice := range slices { + concatenated = append(concatenated, slice...) + } + return concatenated +} + +// generateBytes generates a pseudo random byte slice +// of the given length. It uses `0` as its seed so +// calling it multiple times will generate the same +// byte slice. This is designed as such in order to have +// deterministic unit tests. +func generateBytes(t *testing.T, length uint) (bytes []byte) { + t.Helper() + generator := rand.New(rand.NewSource(0)) + bytes = make([]byte, length) + _, err := generator.Read(bytes) + require.NoError(t, err) + return bytes +} + +// getBadNodeEncoding returns a particular bad node encoding of 33 bytes. +func getBadNodeEncoding() (badEncoding []byte) { + return []byte{ + 0x1, 0x94, 0xfd, 0xc2, 0xfa, 0x2f, 0xfc, 0xc0, 0x41, 0xd3, + 0xff, 0x12, 0x4, 0x5b, 0x73, 0xc8, 0x6e, 0x4f, 0xf9, 0x5f, + 0xf6, 0x62, 0xa5, 0xee, 0xe8, 0x2a, 0xbd, 0xf4, 0x4a, 0x2d, + 0xb, 0x75, 0xfb} +} + +func Test_getBadNodeEncoding(t *testing.T) { + t.Parallel() + + badEncoding := getBadNodeEncoding() + _, err := node.Decode(bytes.NewBuffer(badEncoding)) + require.Error(t, err) +} + +func assertLongEncoding(t *testing.T, node node.Node) { + t.Helper() + + encoding := encodeNode(t, node) + require.Greater(t, len(encoding), 32) +} + +func assertShortEncoding(t *testing.T, node node.Node) { + t.Helper() + + encoding := encodeNode(t, node) + require.LessOrEqual(t, len(encoding), 32) +} diff --git a/lib/trie/proof/verify.go b/lib/trie/proof/verify.go index 1f3105748ab..b98b15476d7 100644 --- a/lib/trie/proof/verify.go +++ b/lib/trie/proof/verify.go @@ -57,76 +57,72 @@ func buildTrie(encodedProofNodes [][]byte, rootHash []byte) (t *trie.Trie, err e ErrEmptyProof, rootHash) } - merkleValueToNode := make(map[string]*node.Node, len(encodedProofNodes)) + merkleValueToEncoding := make(map[string][]byte, len(encodedProofNodes)) + // This loop finds the root node and decodes it. + // The other nodes have their Merkle value (blake2b digest or the encoding itself) + // inserted into a map from merkle value to encoding. They are only decoded + // later if the root or one of its descendant node reference their Merkle value. var root *node.Node - for i, encodedProofNode := range encodedProofNodes { - decodedNode, err := node.Decode(bytes.NewReader(encodedProofNode)) - if err != nil { - return nil, fmt.Errorf("decoding node at index %d: %w (node encoded is 0x%x)", - i, err, encodedProofNode) - } - - decodedNode.Encoding = encodedProofNode - // We compute the Merkle value of nodes treating them all - // as non-root nodes, meaning nodes with encoding smaller - // than 33 bytes will have their Merkle value set as their - // encoding. The Blake2b hash of the encoding is computed - // later if needed to compare with the root hash given to find - // which node is the root node. - const isRoot = false - decodedNode.HashDigest, err = node.MerkleValue(encodedProofNode, isRoot) - if err != nil { - return nil, fmt.Errorf("merkle value of node at index %d: %w", i, err) - } - - proofHash := common.BytesToHex(decodedNode.HashDigest) - merkleValueToNode[proofHash] = decodedNode - - if root != nil { - // Root node already found in proof - continue - } - - possibleRootMerkleValue := decodedNode.HashDigest - if len(possibleRootMerkleValue) <= 32 { - // If the root merkle value is smaller than 33 bytes, it means - // it is the encoding of the node. However, the root node merkle - // value is always the blake2b digest of the node, and not its own - // encoding. Therefore, in this case we force the computation of the - // blake2b digest of the node to check if it matches the root hash given. - const isRoot = true - possibleRootMerkleValue, err = node.MerkleValue(encodedProofNode, isRoot) + for _, encodedProofNode := range encodedProofNodes { + var digest []byte + if root == nil { + // root node not found yet + digestHash, err := common.Blake2bHash(encodedProofNode) if err != nil { - return nil, fmt.Errorf("merkle value of possible root node: %w", err) + return nil, fmt.Errorf("blake2b hash: %w", err) + } + digest = digestHash[:] + + if bytes.Equal(digest, rootHash) { + root, err = node.Decode(bytes.NewReader(encodedProofNode)) + if err != nil { + return nil, fmt.Errorf("decoding root node: %w", err) + } + continue // no need to add root to map of hash to encoding } } - if bytes.Equal(rootHash, possibleRootMerkleValue) { - decodedNode.HashDigest = rootHash - root = decodedNode + var merkleValue []byte + if len(encodedProofNode) <= 32 { + merkleValue = encodedProofNode + } else { + if digest == nil { + digestHash, err := common.Blake2bHash(encodedProofNode) + if err != nil { + return nil, fmt.Errorf("blake2b hash: %w", err) + } + digest = digestHash[:] + } + merkleValue = digest } + + merkleValueToEncoding[string(merkleValue)] = encodedProofNode } if root == nil { - proofMerkleValues := make([]string, 0, len(merkleValueToNode)) - for merkleValue := range merkleValueToNode { - proofMerkleValues = append(proofMerkleValues, merkleValue) + proofMerkleValues := make([]string, 0, len(merkleValueToEncoding)) + for merkleValueString := range merkleValueToEncoding { + merkleValueHex := common.BytesToHex([]byte(merkleValueString)) + proofMerkleValues = append(proofMerkleValues, merkleValueHex) } return nil, fmt.Errorf("%w: for Merkle root hash 0x%x in proof Merkle value(s) %s", ErrRootNodeNotFound, rootHash, strings.Join(proofMerkleValues, ", ")) } - loadProof(merkleValueToNode, root) + err = loadProof(merkleValueToEncoding, root) + if err != nil { + return nil, fmt.Errorf("loading proof: %w", err) + } return trie.NewTrie(root), nil } // loadProof is a recursive function that will create all the trie paths based // on the map from node hash to node starting at the root. -func loadProof(merkleValueToNode map[string]*node.Node, n *node.Node) { +func loadProof(merkleValueToEncoding map[string][]byte, n *node.Node) (err error) { if n.Type() != node.Branch { - return + return nil } branch := n @@ -135,15 +131,38 @@ func loadProof(merkleValueToNode map[string]*node.Node, n *node.Node) { continue } - merkleValueHex := common.BytesToHex(child.HashDigest) - node, ok := merkleValueToNode[merkleValueHex] + merkleValue := child.HashDigest + encoding, ok := merkleValueToEncoding[string(merkleValue)] if !ok { + inlinedChild := len(child.Value) > 0 || child.HasChild() + if !inlinedChild { + // hash not found and the child is not inlined, + // so clear the child from the branch. + branch.Descendants -= 1 + child.Descendants + branch.Children[i] = nil + if !branch.HasChild() { + // Convert branch to a leaf if all its children are nil. + branch.Children = nil + } + } continue } - branch.Children[i] = node - loadProof(merkleValueToNode, node) + child, err := node.Decode(bytes.NewReader(encoding)) + if err != nil { + return fmt.Errorf("decoding child node for Merkle value 0x%x: %w", + merkleValue, err) + } + + branch.Children[i] = child + branch.Descendants += child.Descendants + err = loadProof(merkleValueToEncoding, child) + if err != nil { + return err // do not wrap error since this is recursive + } } + + return nil } func bytesToString(b []byte) (s string) { diff --git a/lib/trie/proof/verify_test.go b/lib/trie/proof/verify_test.go index d52c8356ba8..db3b346a1be 100644 --- a/lib/trie/proof/verify_test.go +++ b/lib/trie/proof/verify_test.go @@ -20,18 +20,12 @@ func Test_Verify(t *testing.T) { Value: []byte{1}, } - longValue := []byte{ - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, - } // leafB is a leaf encoding to more than 32 bytes leafB := node.Node{ Key: []byte{2}, - Value: longValue, + Value: generateBytes(t, 40), } - require.Greater(t, len(encodeNode(t, leafB)), 32) + assertLongEncoding(t, leafB) branch := node.Node{ Key: []byte{3, 4}, @@ -43,7 +37,7 @@ func Test_Verify(t *testing.T) { &leafB, }), } - require.Greater(t, len(encodeNode(t, branch)), 32) + assertLongEncoding(t, branch) testCases := map[string]struct { encodedProofNodes [][]byte @@ -70,7 +64,7 @@ func Test_Verify(t *testing.T) { errWrapped: ErrKeyNotFoundInProofTrie, errMessage: "key not found in proof trie: " + "0x0101 in proof trie for root hash " + - "0xe92124f2c4d180493adb4c58250cbd8c5da9c4e3810d8f832e95b1c332de6103", + "0xec4bb0acfcf778ae8746d3ac3325fc73c3d9b376eb5f8d638dbf5eb462f5e703", }, "key found with nil search value": { encodedProofNodes: [][]byte{ @@ -102,7 +96,7 @@ func Test_Verify(t *testing.T) { }, rootHash: blake2bNode(t, branch), keyLE: []byte{0x34, 0x32}, // large hash-referenced leaf of branch - value: longValue, + value: generateBytes(t, 40), }, } @@ -128,29 +122,19 @@ func Test_buildTrie(t *testing.T) { Key: []byte{1}, Value: []byte{2}, } - leafAShortEncoded := encodeNode(t, leafAShort) - require.LessOrEqual(t, len(leafAShortEncoded), 32) - - longValue := []byte{ - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, - } + assertShortEncoding(t, leafAShort) leafBLarge := node.Node{ Key: []byte{2}, - Value: longValue, + Value: generateBytes(t, 40), } - leafBLargeEncoded := encodeNode(t, leafBLarge) - require.Greater(t, len(leafBLargeEncoded), 32) + assertLongEncoding(t, leafBLarge) leafCLarge := node.Node{ Key: []byte{3}, - Value: longValue, + Value: generateBytes(t, 40), } - leafCLargeEncoded := encodeNode(t, leafCLarge) - require.Greater(t, len(leafCLargeEncoded), 32) + assertLongEncoding(t, leafCLarge) testCases := map[string]struct { encodedProofNodes [][]byte @@ -164,14 +148,15 @@ func Test_buildTrie(t *testing.T) { rootHash: []byte{1}, errMessage: "proof slice empty: for Merkle root hash 0x01", }, - "proof node decoding error": { + "root node decoding error": { encodedProofNodes: [][]byte{ - {1, 2, 3}, + getBadNodeEncoding(), }, - rootHash: []byte{3}, - errWrapped: node.ErrUnknownNodeType, - errMessage: "decoding node at index 0: " + - "unknown node type: 0 (node encoded is 0x010203)", + rootHash: blake2b(t, getBadNodeEncoding()), + errWrapped: node.ErrVariantUnknown, + errMessage: "decoding root node: decoding header: " + + "decoding header byte: node variant is unknown: " + + "for header byte 00000001", }, "root proof encoding smaller than 32 bytes": { encodedProofNodes: [][]byte{ @@ -179,43 +164,37 @@ func Test_buildTrie(t *testing.T) { }, rootHash: blake2bNode(t, leafAShort), expectedTrie: trie.NewTrie(&node.Node{ - Key: leafAShort.Key, - Value: leafAShort.Value, - Encoding: leafAShortEncoded, - HashDigest: blake2bNode(t, leafAShort), - Dirty: true, + Key: leafAShort.Key, + Value: leafAShort.Value, + Dirty: true, }), }, "root proof encoding larger than 32 bytes": { encodedProofNodes: [][]byte{ - leafBLargeEncoded, + encodeNode(t, leafBLarge), }, rootHash: blake2bNode(t, leafBLarge), expectedTrie: trie.NewTrie(&node.Node{ - Key: leafBLarge.Key, - Value: leafBLarge.Value, - Encoding: leafBLargeEncoded, - HashDigest: blake2bNode(t, leafBLarge), - Dirty: true, + Key: leafBLarge.Key, + Value: leafBLarge.Value, + Dirty: true, }), }, "discard unused node": { encodedProofNodes: [][]byte{ - leafAShortEncoded, - leafBLargeEncoded, + encodeNode(t, leafAShort), + encodeNode(t, leafBLarge), }, rootHash: blake2bNode(t, leafAShort), expectedTrie: trie.NewTrie(&node.Node{ - Key: leafAShort.Key, - Value: leafAShort.Value, - Encoding: leafAShortEncoded, - HashDigest: blake2bNode(t, leafAShort), - Dirty: true, + Key: leafAShort.Key, + Value: leafAShort.Value, + Dirty: true, }), }, "multiple unordered nodes": { encodedProofNodes: [][]byte{ - leafBLargeEncoded, // chilren 1 and 3 + encodeNode(t, leafBLarge), // chilren 1 and 3 encodeNode(t, node.Node{ // root Key: []byte{1}, Children: padRightChildren([]*node.Node{ @@ -225,7 +204,7 @@ func Test_buildTrie(t *testing.T) { &leafBLarge, // referenced by Merkle value hash }), }), - leafCLargeEncoded, // children 2 + encodeNode(t, leafCLarge), // children 2 }, rootHash: blake2bNode(t, node.Node{ Key: []byte{1}, @@ -247,47 +226,46 @@ func Test_buildTrie(t *testing.T) { Dirty: true, }, { - Key: leafBLarge.Key, - Value: leafBLarge.Value, - Encoding: leafBLargeEncoded, - HashDigest: blake2bNode(t, leafBLarge), - Dirty: true, + Key: leafBLarge.Key, + Value: leafBLarge.Value, + Dirty: true, }, { - Key: leafCLarge.Key, - Value: leafCLarge.Value, - Encoding: leafCLargeEncoded, - HashDigest: blake2bNode(t, leafCLarge), - Dirty: true, + Key: leafCLarge.Key, + Value: leafCLarge.Value, + Dirty: true, }, { - Key: leafBLarge.Key, - Value: leafBLarge.Value, - Encoding: leafBLargeEncoded, - HashDigest: blake2bNode(t, leafBLarge), - Dirty: true, + Key: leafBLarge.Key, + Value: leafBLarge.Value, + Dirty: true, }, }), - Encoding: encodeNode(t, node.Node{ - Key: []byte{1}, - Children: padRightChildren([]*node.Node{ - &leafAShort, - &leafBLarge, - &leafCLarge, - &leafBLarge, - }), - }), - HashDigest: blake2bNode(t, node.Node{ - Key: []byte{1}, - Children: padRightChildren([]*node.Node{ - &leafAShort, - &leafBLarge, - &leafCLarge, - &leafBLarge, - }), - }), }), }, + "load proof decoding error": { + encodedProofNodes: [][]byte{ + getBadNodeEncoding(), + // root with one child pointing to hash of bad encoding above. + concatBytes([][]byte{ + {0b1000_0000 | 0b0000_0001}, // branch with key size 1 + {1}, // key + {0b0000_0001, 0b0000_0000}, // children bitmap + scaleEncode(t, blake2b(t, getBadNodeEncoding())), // child hash + }), + }, + rootHash: blake2b(t, concatBytes([][]byte{ + {0b1000_0000 | 0b0000_0001}, // branch with key size 1 + {1}, // key + {0b0000_0001, 0b0000_0000}, // children bitmap + scaleEncode(t, blake2b(t, getBadNodeEncoding())), // child hash + })), + errWrapped: node.ErrVariantUnknown, + errMessage: "loading proof: decoding child node for Merkle value " + + "0xcfa21f0ec11a3658d77701b7b1f52fbcb783fe3df662977b6e860252b6c37e1e: " + + "decoding header: decoding header byte: " + + "node variant is unknown: for header byte 00000001", + }, "root not found": { encodedProofNodes: [][]byte{ encodeNode(t, node.Node{ @@ -326,10 +304,20 @@ func Test_buildTrie(t *testing.T) { func Test_loadProof(t *testing.T) { t.Parallel() + largeValue := generateBytes(t, 40) + + leafLarge := node.Node{ + Key: []byte{3}, + Value: largeValue, + } + assertLongEncoding(t, leafLarge) + testCases := map[string]struct { - proofHashToNode map[string]*node.Node - node *node.Node - expectedNode *node.Node + merkleValueToEncoding map[string][]byte + node *node.Node + expectedNode *node.Node + errWrapped error + errMessage string }{ "leaf node": { node: &node.Node{ @@ -341,51 +329,244 @@ func Test_loadProof(t *testing.T) { Value: []byte{2}, }, }, - "branch node": { + "branch node with child hash not found": { node: &node.Node{ - Key: []byte{1}, + Key: []byte{1}, + Value: []byte{2}, + Descendants: 1, + Dirty: true, + Children: padRightChildren([]*node.Node{ + {HashDigest: []byte{3}}, + }), + }, + merkleValueToEncoding: map[string][]byte{}, + expectedNode: &node.Node{ + Key: []byte{1}, + Value: []byte{2}, + Dirty: true, + }, + }, + "branch node with child hash found": { + node: &node.Node{ + Key: []byte{1}, + Value: []byte{2}, + Descendants: 1, + Dirty: true, Children: padRightChildren([]*node.Node{ - nil, - { // hash not found in proof map - HashDigest: []byte{1}, + {HashDigest: []byte{2}}, + }), + }, + merkleValueToEncoding: map[string][]byte{ + string([]byte{2}): encodeNode(t, node.Node{ + Key: []byte{3}, + Value: []byte{1}, + }), + }, + expectedNode: &node.Node{ + Key: []byte{1}, + Value: []byte{2}, + Descendants: 1, + Dirty: true, + Children: padRightChildren([]*node.Node{ + { + Key: []byte{3}, + Value: []byte{1}, + Dirty: true, }, - { // hash found in proof map - HashDigest: []byte{2}, + }), + }, + }, + "branch node with one child hash found and one not found": { + node: &node.Node{ + Key: []byte{1}, + Value: []byte{2}, + Descendants: 2, + Dirty: true, + Children: padRightChildren([]*node.Node{ + {HashDigest: []byte{2}}, // found + {HashDigest: []byte{3}}, // not found + }), + }, + merkleValueToEncoding: map[string][]byte{ + string([]byte{2}): encodeNode(t, node.Node{ + Key: []byte{3}, + Value: []byte{1}, + }), + }, + expectedNode: &node.Node{ + Key: []byte{1}, + Value: []byte{2}, + Descendants: 1, + Dirty: true, + Children: padRightChildren([]*node.Node{ + { + Key: []byte{3}, + Value: []byte{1}, + Dirty: true, }, }), }, - proofHashToNode: map[string]*node.Node{ - "0x02": { - Key: []byte{2}, - Children: padRightChildren([]*node.Node{ - { // hash found in proof map - HashDigest: []byte{3}, - }, - }), - }, - "0x03": { + }, + "branch node with branch child hash": { + node: &node.Node{ + Key: []byte{1}, + Value: []byte{2}, + Descendants: 2, + Dirty: true, + Children: padRightChildren([]*node.Node{ + {HashDigest: []byte{2}}, + }), + }, + merkleValueToEncoding: map[string][]byte{ + string([]byte{2}): encodeNode(t, node.Node{ Key: []byte{3}, Value: []byte{1}, - }, + Children: padRightChildren([]*node.Node{ + {Key: []byte{4}, Value: []byte{2}}, + }), + }), }, expectedNode: &node.Node{ - Key: []byte{1}, + Key: []byte{1}, + Value: []byte{2}, + Descendants: 3, + Dirty: true, Children: padRightChildren([]*node.Node{ - nil, - { // hash not found in proof map - HashDigest: []byte{1}, + { + Key: []byte{3}, + Value: []byte{1}, + Dirty: true, + Descendants: 1, + Children: padRightChildren([]*node.Node{ + { + Key: []byte{4}, + Value: []byte{2}, + Dirty: true, + }, + }), }, - { // hash found in proof map - Key: []byte{2}, + }), + }, + }, + "child decoding error": { + node: &node.Node{ + Key: []byte{1}, + Value: []byte{2}, + Descendants: 1, + Dirty: true, + Children: padRightChildren([]*node.Node{ + {HashDigest: []byte{2}}, + }), + }, + merkleValueToEncoding: map[string][]byte{ + string([]byte{2}): getBadNodeEncoding(), + }, + expectedNode: &node.Node{ + Key: []byte{1}, + Value: []byte{2}, + Descendants: 1, + Dirty: true, + Children: padRightChildren([]*node.Node{ + {HashDigest: []byte{2}}, + }), + }, + errWrapped: node.ErrVariantUnknown, + errMessage: "decoding child node for Merkle value 0x02: " + + "decoding header: decoding header byte: node variant is unknown: " + + "for header byte 00000001", + }, + "grand child": { + node: &node.Node{ + Key: []byte{1}, + Value: []byte{1}, + Descendants: 1, + Dirty: true, + Children: padRightChildren([]*node.Node{ + {HashDigest: []byte{2}}, + }), + }, + merkleValueToEncoding: map[string][]byte{ + string([]byte{2}): encodeNode(t, node.Node{ + Key: []byte{2}, + Value: []byte{2}, + Descendants: 1, + Dirty: true, + Children: padRightChildren([]*node.Node{ + &leafLarge, // encoded to hash + }), + }), + string(blake2bNode(t, leafLarge)): encodeNode(t, leafLarge), + }, + expectedNode: &node.Node{ + Key: []byte{1}, + Value: []byte{1}, + Descendants: 2, + Dirty: true, + Children: padRightChildren([]*node.Node{ + { + Key: []byte{2}, + Value: []byte{2}, + Descendants: 1, + Dirty: true, + Children: padRightChildren([]*node.Node{ + { + Key: leafLarge.Key, + Value: leafLarge.Value, + Dirty: true, + }, + }), + }, + }), + }, + }, + + "grand child load proof error": { + node: &node.Node{ + Key: []byte{1}, + Value: []byte{1}, + Descendants: 1, + Dirty: true, + Children: padRightChildren([]*node.Node{ + {HashDigest: []byte{2}}, + }), + }, + merkleValueToEncoding: map[string][]byte{ + string([]byte{2}): encodeNode(t, node.Node{ + Key: []byte{2}, + Value: []byte{2}, + Descendants: 1, + Dirty: true, + Children: padRightChildren([]*node.Node{ + &leafLarge, // encoded to hash + }), + }), + string(blake2bNode(t, leafLarge)): getBadNodeEncoding(), + }, + expectedNode: &node.Node{ + Key: []byte{1}, + Value: []byte{1}, + Descendants: 2, + Dirty: true, + Children: padRightChildren([]*node.Node{ + { + Key: []byte{2}, + Value: []byte{2}, + Descendants: 1, + Dirty: true, Children: padRightChildren([]*node.Node{ - { // hash found in proof map - Key: []byte{3}, - Value: []byte{1}, + { + HashDigest: blake2bNode(t, leafLarge), + Dirty: true, }, }), }, }), }, + errWrapped: node.ErrVariantUnknown, + errMessage: "decoding child node for Merkle value " + + "0x6888b9403129c11350c6054b46875292c0ffedcfd581e66b79bdf350b775ebf2: " + + "decoding header: decoding header byte: node variant is unknown: " + + "for header byte 00000001", }, } @@ -394,9 +575,14 @@ func Test_loadProof(t *testing.T) { t.Run(name, func(t *testing.T) { t.Parallel() - loadProof(testCase.proofHashToNode, testCase.node) + err := loadProof(testCase.merkleValueToEncoding, testCase.node) + + assert.ErrorIs(t, err, testCase.errWrapped) + if testCase.errWrapped != nil { + assert.EqualError(t, err, testCase.errMessage) + } - assert.Equal(t, testCase.expectedNode, testCase.node) + assert.Equal(t, testCase.expectedNode.String(), testCase.node.String()) }) } }