From 91d66199f3ab944e27b28d01f54a1f44cef6ba7e Mon Sep 17 00:00:00 2001 From: Quentin McGaw Date: Fri, 22 Jul 2022 09:44:06 -0400 Subject: [PATCH] chore(lib/trie/proof): simplify `Verify` function (#2661) - Merkle value for all encoded proof nodes is their encoding hash digest - Add comments - Update error wrapping strings --- lib/trie/proof/verify.go | 85 ++++++++++++++++------------------- lib/trie/proof/verify_test.go | 9 ++-- 2 files changed, 44 insertions(+), 50 deletions(-) diff --git a/lib/trie/proof/verify.go b/lib/trie/proof/verify.go index b98b15476d..a75d96b78a 100644 --- a/lib/trie/proof/verify.go +++ b/lib/trie/proof/verify.go @@ -57,60 +57,50 @@ func buildTrie(encodedProofNodes [][]byte, rootHash []byte) (t *trie.Trie, err e ErrEmptyProof, rootHash) } - merkleValueToEncoding := make(map[string][]byte, len(encodedProofNodes)) + digestToEncoding := 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. + // This loop does two things: + // 1. It finds the root node by comparing it with the root hash and decodes it. + // 2. It stores other encoded nodes in a mapping from their encoding digest to + // their encoding. They are only decoded later if the root or one of its + // descendant nodes reference their hash digest. var root *node.Node 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("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 - } + // Note all encoded proof nodes are one of the following: + // - trie root node + // - child trie root node + // - child node with an encoding larger than 32 bytes + // In all cases, their Merkle value is the encoding hash digest. + digestHash, err := common.Blake2bHash(encodedProofNode) + if err != nil { + return nil, fmt.Errorf("blake2b hash: %w", err) } + digest := digestHash[:] - 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 + if root != nil || !bytes.Equal(digest, rootHash) { + // root node already found or the hash doesn't match the root hash. + digestToEncoding[string(digest)] = encodedProofNode + continue + // Note: no need to add the root node to the map of hash to encoding } - merkleValueToEncoding[string(merkleValue)] = encodedProofNode + root, err = node.Decode(bytes.NewReader(encodedProofNode)) + if err != nil { + return nil, fmt.Errorf("decoding root node: %w", err) + } } if root == nil { - proofMerkleValues := make([]string, 0, len(merkleValueToEncoding)) - for merkleValueString := range merkleValueToEncoding { - merkleValueHex := common.BytesToHex([]byte(merkleValueString)) - proofMerkleValues = append(proofMerkleValues, merkleValueHex) + proofHashDigests := make([]string, 0, len(digestToEncoding)) + for hashDigestString := range digestToEncoding { + hashDigestHex := common.BytesToHex([]byte(hashDigestString)) + proofHashDigests = append(proofHashDigests, hashDigestHex) } - return nil, fmt.Errorf("%w: for Merkle root hash 0x%x in proof Merkle value(s) %s", - ErrRootNodeNotFound, rootHash, strings.Join(proofMerkleValues, ", ")) + return nil, fmt.Errorf("%w: for root hash 0x%x in proof hash digests %s", + ErrRootNodeNotFound, rootHash, strings.Join(proofHashDigests, ", ")) } - err = loadProof(merkleValueToEncoding, root) + err = loadProof(digestToEncoding, root) if err != nil { return nil, fmt.Errorf("loading proof: %w", err) } @@ -119,8 +109,8 @@ func buildTrie(encodedProofNodes [][]byte, rootHash []byte) (t *trie.Trie, err e } // 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(merkleValueToEncoding map[string][]byte, n *node.Node) (err error) { +// on the map from node hash digest to node encoding, starting from the node `n`. +func loadProof(digestToEncoding map[string][]byte, n *node.Node) (err error) { if n.Type() != node.Branch { return nil } @@ -131,8 +121,11 @@ func loadProof(merkleValueToEncoding map[string][]byte, n *node.Node) (err error continue } + // for inlined child nodes, the hash digest field is the + // encoding itself instead of the encoding hash digest, so we + // use the `merkleValue` variable name below to avoid confusion. merkleValue := child.HashDigest - encoding, ok := merkleValueToEncoding[string(merkleValue)] + encoding, ok := digestToEncoding[string(merkleValue)] if !ok { inlinedChild := len(child.Value) > 0 || child.HasChild() if !inlinedChild { @@ -150,13 +143,13 @@ func loadProof(merkleValueToEncoding map[string][]byte, n *node.Node) (err error child, err := node.Decode(bytes.NewReader(encoding)) if err != nil { - return fmt.Errorf("decoding child node for Merkle value 0x%x: %w", + return fmt.Errorf("decoding child node for hash digest 0x%x: %w", merkleValue, err) } branch.Children[i] = child branch.Descendants += child.Descendants - err = loadProof(merkleValueToEncoding, child) + err = loadProof(digestToEncoding, child) if err != nil { return err // do not wrap error since this is recursive } diff --git a/lib/trie/proof/verify_test.go b/lib/trie/proof/verify_test.go index db3b346a1b..eaa9d0e708 100644 --- a/lib/trie/proof/verify_test.go +++ b/lib/trie/proof/verify_test.go @@ -261,7 +261,7 @@ func Test_buildTrie(t *testing.T) { scaleEncode(t, blake2b(t, getBadNodeEncoding())), // child hash })), errWrapped: node.ErrVariantUnknown, - errMessage: "loading proof: decoding child node for Merkle value " + + errMessage: "loading proof: decoding child node for hash digest " + "0xcfa21f0ec11a3658d77701b7b1f52fbcb783fe3df662977b6e860252b6c37e1e: " + "decoding header: decoding header byte: " + "node variant is unknown: for header byte 00000001", @@ -276,7 +276,8 @@ func Test_buildTrie(t *testing.T) { rootHash: []byte{3}, errWrapped: ErrRootNodeNotFound, errMessage: "root node not found in proof: " + - "for Merkle root hash 0x03 in proof Merkle value(s) 0x41010402", + "for root hash 0x03 in proof hash digests " + + "0x60516d0bb6e1bbfb1293f1b276ea9505e9f4a4e7d98f620d05115e0b85274ae1", }, } @@ -471,7 +472,7 @@ func Test_loadProof(t *testing.T) { }), }, errWrapped: node.ErrVariantUnknown, - errMessage: "decoding child node for Merkle value 0x02: " + + errMessage: "decoding child node for hash digest 0x02: " + "decoding header: decoding header byte: node variant is unknown: " + "for header byte 00000001", }, @@ -563,7 +564,7 @@ func Test_loadProof(t *testing.T) { }), }, errWrapped: node.ErrVariantUnknown, - errMessage: "decoding child node for Merkle value " + + errMessage: "decoding child node for hash digest " + "0x6888b9403129c11350c6054b46875292c0ffedcfd581e66b79bdf350b775ebf2: " + "decoding header: decoding header byte: node variant is unknown: " + "for header byte 00000001",