Skip to content

Commit

Permalink
chore(lib/trie/proof): simplify Verify function (#2661)
Browse files Browse the repository at this point in the history
- Merkle value for all encoded proof nodes is their encoding hash digest
- Add comments
- Update error wrapping strings
  • Loading branch information
qdm12 authored Jul 22, 2022
1 parent 70181cd commit 91d6619
Show file tree
Hide file tree
Showing 2 changed files with 44 additions and 50 deletions.
85 changes: 39 additions & 46 deletions lib/trie/proof/verify.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand All @@ -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
}
Expand All @@ -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 {
Expand All @@ -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
}
Expand Down
9 changes: 5 additions & 4 deletions lib/trie/proof/verify_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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",
},
}

Expand Down Expand Up @@ -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",
},
Expand Down Expand Up @@ -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",
Expand Down

0 comments on commit 91d6619

Please sign in to comment.