From ef93b63dfcfd5f30c185bfcf0dc7a3725c7267cd Mon Sep 17 00:00:00 2001 From: gary rong Date: Wed, 23 Sep 2020 16:03:21 +0800 Subject: [PATCH] trie: support empty range proof (#21199) --- trie/proof.go | 21 +++++++++++++++++---- trie/proof_test.go | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 50 insertions(+), 4 deletions(-) diff --git a/trie/proof.go b/trie/proof.go index 6fc21fc1f7ee..326235a3fdea 100644 --- a/trie/proof.go +++ b/trie/proof.go @@ -393,7 +393,7 @@ func hasRightElement(node node, key []byte) bool { // (unless firstProof is an existent proof). // // Expect the normal case, this function can also be used to verify the following -// range proofs(note this function doesn't accept zero element proof): +// range proofs: // // - All elements proof. In this case the left and right proof can be nil, but the // range should be all the leaves in the trie. @@ -401,15 +401,16 @@ func hasRightElement(node node, key []byte) bool { // - One element proof. In this case no matter the left edge proof is a non-existent // proof or not, we can always verify the correctness of the proof. // +// - Zero element proof(left edge proof should be a non-existent proof). In this +// case if there are still some other leaves available on the right side, then +// an error will be returned. +// // Except returning the error to indicate the proof is valid or not, the function will // also return a flag to indicate whether there exists more accounts/slots in the trie. func VerifyRangeProof(rootHash common.Hash, firstKey []byte, keys [][]byte, values [][]byte, firstProof ethdb.KeyValueReader, lastProof ethdb.KeyValueReader) (error, bool) { if len(keys) != len(values) { return fmt.Errorf("inconsistent proof data, keys: %d, values: %d", len(keys), len(values)), false } - if len(keys) == 0 { - return errors.New("empty proof"), false - } // Ensure the received batch is monotonic increasing. for i := 0; i < len(keys)-1; i++ { if bytes.Compare(keys[i], keys[i+1]) >= 0 { @@ -431,6 +432,18 @@ func VerifyRangeProof(rootHash common.Hash, firstKey []byte, keys [][]byte, valu } return nil, false // no more element. } + // Special case, there is a provided left edge proof and zero key/value + // pairs, ensure there are no more accounts / slots in the trie. + if len(keys) == 0 { + root, val, err := proofToPath(rootHash, nil, firstKey, firstProof, true) + if err != nil { + return err, false + } + if val != nil || hasRightElement(root, firstKey) { + return errors.New("more entries available"), false + } + return nil, false + } // Special case, there is only one element and left edge // proof is an existent one. if len(keys) == 1 && bytes.Equal(keys[0], firstKey) { diff --git a/trie/proof_test.go b/trie/proof_test.go index db0e4051c8bf..4d76f3f783a4 100644 --- a/trie/proof_test.go +++ b/trie/proof_test.go @@ -571,6 +571,39 @@ func TestHasRightElement(t *testing.T) { } } +// TestEmptyRangeProof tests the range proof with "no" element. +// The first edge proof must be a non-existent proof. +func TestEmptyRangeProof(t *testing.T) { + trie, vals := randomTrie(4096) + var entries entrySlice + for _, kv := range vals { + entries = append(entries, kv) + } + sort.Sort(entries) + + var cases = []struct { + pos int + err bool + }{ + {len(entries) - 1, false}, + {500, true}, + } + for _, c := range cases { + firstProof := memorydb.New() + first := increseKey(common.CopyBytes(entries[c.pos].k)) + if err := trie.Prove(first, 0, firstProof); err != nil { + t.Fatalf("Failed to prove the first node %v", err) + } + err, _ := VerifyRangeProof(trie.Hash(), first, nil, nil, firstProof, nil) + if c.err && err == nil { + t.Fatalf("Expected error, got nil") + } + if !c.err && err != nil { + t.Fatalf("Expected no error, got %v", err) + } + } +} + // mutateByte changes one byte in b. func mutateByte(b []byte) { for r := mrand.Intn(len(b)); ; {