diff --git a/CHANGELOG.md b/CHANGELOG.md index dd3e5e36a..24615000e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,24 @@ # Changelog +## 0.9.1 (July 1, 2018) + +IMPROVEMENTS + +- RangeProof.ComputeRootHash() to compute root rather than provide as in Verify(hash) +- RangeProof.Verify\*() first require .Verify(root), which memoizes + +## 0.9.0 (July 1, 2018) + +BREAKING CHANGES + +- RangeProof.VerifyItem doesn't require an index. +- Only return values in range when getting proof. +- Return keys as well. + +BUG FIXES + +- traversal bugs in traverseRange. + ## 0.8.1 *July 1st, 2018* diff --git a/basic_test.go b/basic_test.go index 6e05215ee..0710afe36 100644 --- a/basic_test.go +++ b/basic_test.go @@ -450,7 +450,7 @@ func TestTreeProof(t *testing.T) { assert.Equal(t, key, value) err := proof.Verify(root) assert.NoError(t, err, "#### %v", proof.String()) - err = proof.VerifyItem(0, key, key) + err = proof.VerifyItem(key, key) assert.NoError(t, err, "#### %v", proof.String()) } } diff --git a/proof_path.go b/proof_path.go index 29b9ebc76..dbde9fa10 100644 --- a/proof_path.go +++ b/proof_path.go @@ -28,8 +28,19 @@ func (pwl pathWithLeaf) StringIndented(indent string) string { indent) } +// `verify` checks that the leaf node's hash + the inner nodes merkle-izes to +// the given root. If it returns an error, it means the leafHash or the +// PathToLeaf is incorrect. func (pwl pathWithLeaf) verify(root []byte) cmn.Error { - return pwl.Path.verify(pwl.Leaf.Hash(), root) + leafHash := pwl.Leaf.Hash() + return pwl.Path.verify(leafHash, root) +} + +// `computeRootHash` computes the root hash with leaf node. +// Does not verify the root hash. +func (pwl pathWithLeaf) computeRootHash() []byte { + leafHash := pwl.Leaf.Hash() + return pwl.Path.computeRootHash(leafHash) } //---------------------------------------- @@ -62,9 +73,9 @@ func (pl PathToLeaf) StringIndented(indent string) string { indent) } -// verify checks that the leaf node's hash + the inner nodes merkle-izes to the -// given root. If it returns an error, it means the leafHash or the PathToLeaf -// is incorrect. +// `verify` checks that the leaf node's hash + the inner nodes merkle-izes to +// the given root. If it returns an error, it means the leafHash or the +// PathToLeaf is incorrect. func (pl PathToLeaf) verify(leafHash []byte, root []byte) cmn.Error { hash := leafHash for i := len(pl) - 1; i >= 0; i-- { @@ -77,6 +88,17 @@ func (pl PathToLeaf) verify(leafHash []byte, root []byte) cmn.Error { return nil } +// `computeRootHash` computes the root hash assuming some leaf hash. +// Does not verify the root hash. +func (pl PathToLeaf) computeRootHash(leafHash []byte) []byte { + hash := leafHash + for i := len(pl) - 1; i >= 0; i-- { + pin := pl[i] + hash = pin.Hash(hash) + } + return hash +} + func (pl PathToLeaf) isLeftmost() bool { for _, node := range pl { if len(node.Left) > 0 { @@ -125,3 +147,21 @@ func (pl PathToLeaf) isLeftAdjacentTo(pl2 PathToLeaf) bool { return pl.isRightmost() && pl2.isLeftmost() } + +// returns -1 if invalid. +func (pl PathToLeaf) Index() (idx int64) { + for i, node := range pl { + if node.Left == nil { + continue + } else if node.Right == nil { + if i < len(pl)-1 { + idx += node.Size - pl[i+1].Size + } else { + idx += node.Size - 1 + } + } else { + return -1 + } + } + return idx +} diff --git a/proof_range.go b/proof_range.go index 7daa80006..ad4f4d915 100644 --- a/proof_range.go +++ b/proof_range.go @@ -3,6 +3,7 @@ package iavl import ( "bytes" "fmt" + "sort" "strings" "github.com/tendermint/iavl/sha256truncated" @@ -12,16 +13,39 @@ import ( type RangeProof struct { // You don't need the right path because // it can be derived from what we have. - RootHash cmn.HexBytes `json:"root_hash"` LeftPath PathToLeaf `json:"left_path"` InnerNodes []PathToLeaf `json:"inner_nodes"` Leaves []proofLeafNode `json:"leaves"` - // temporary - treeEnd int // 0 if not set, 1 if true, -1 if false. + + // memoize + rootVerified bool + rootHash []byte // valid iff rootVerified is true + treeEnd bool // valid iff rootVerified is true + +} + +// Keys returns all the keys in the RangeProof. NOTE: The keys here may +// include more keys than provided by tree.GetRangeWithProof or +// VersionedTree.GetVersionedRangeWithProof. The keys returned there are only +// in the provided [startKey,endKey){limit} range. The keys returned here may +// include extra keys, such as: +// - the key before startKey if startKey is provided and doesn't exist; +// - the key after a queried key with tree.GetWithProof, when the key is absent. +func (proof *RangeProof) Keys() (keys [][]byte) { + if proof == nil { + return nil + } + for _, leaf := range proof.Leaves { + keys = append(keys, leaf.Key) + } + return keys } // String returns a string representation of the proof. func (proof *RangeProof) String() string { + if proof == nil { + return "" + } return proof.StringIndented("") } @@ -35,36 +59,54 @@ func (proof *RangeProof) StringIndented(indent string) string { lstrs = append(lstrs, leaf.StringIndented(indent+" ")) } return fmt.Sprintf(`RangeProof{ -%s RootHash: %X %s LeftPath: %v %s InnerNodes: %s %v %s Leaves: %s %v +%s (rootVerified): %v +%s (rootHash): %X %s (treeEnd): %v %s}`, - indent, proof.RootHash, indent, proof.LeftPath.StringIndented(indent+" "), indent, indent, strings.Join(istrs, "\n"+indent+" "), indent, indent, strings.Join(lstrs, "\n"+indent+" "), + indent, proof.rootVerified, + indent, proof.rootHash, indent, proof.treeEnd, indent) } -// Verify that a leaf is some value. -// Does not assume that the proof itself is value. -// For that, use Verify(root). -func (proof *RangeProof) VerifyItem(i int, key, value []byte) error { +// The index of the first leaf (of the whole tree). +// Returns -1 if the proof is nil. +func (proof *RangeProof) LeftIndex() int64 { + if proof == nil { + return -1 + } + return proof.LeftPath.Index() +} + +// Also see LeftIndex(). +// Verify that a key has some value. +// Does not assume that the proof itself is valid, call Verify() first. +func (proof *RangeProof) VerifyItem(key, value []byte) error { + leaves := proof.Leaves if proof == nil { return cmn.ErrorWrap(ErrInvalidProof, "proof is nil") } - if !bytes.Equal(proof.Leaves[i].Key, key) { - return cmn.ErrorWrap(ErrInvalidProof, "leaf key not same") + if !proof.rootVerified { + return cmn.NewError("must call Verify(root) first.") + } + i := sort.Search(len(leaves), func(i int) bool { + return bytes.Compare(key, leaves[i].Key) <= 0 + }) + if i >= len(leaves) || !bytes.Equal(leaves[i].Key, key) { + return cmn.ErrorWrap(ErrInvalidProof, "leaf key not found in proof") } valueHash := sha256truncated.Hash(value) - if !bytes.Equal(proof.Leaves[i].ValueHash, valueHash) { + if !bytes.Equal(leaves[i].ValueHash, valueHash) { return cmn.ErrorWrap(ErrInvalidProof, "leaf value hash not same") } return nil @@ -77,7 +119,7 @@ func (proof *RangeProof) VerifyAbsence(key []byte) error { if proof == nil { return cmn.ErrorWrap(ErrInvalidProof, "proof is nil") } - if proof.treeEnd == 0 { + if !proof.rootVerified { return cmn.NewError("must call Verify(root) first.") } cmp := bytes.Compare(key, proof.Leaves[0].Key) @@ -116,7 +158,7 @@ func (proof *RangeProof) VerifyAbsence(key []byte) error { } // It's still a valid proof if our last leaf is the rightmost child. - if proof.treeEnd == 1 { + if proof.treeEnd { return nil // OK! } @@ -133,26 +175,53 @@ func (proof *RangeProof) Verify(root []byte) error { if proof == nil { return cmn.ErrorWrap(ErrInvalidProof, "proof is nil") } - treeEnd, err := proof._verify(root) - if err == nil { - if treeEnd { - proof.treeEnd = 1 // memoize - } else { - proof.treeEnd = -1 // memoize + err := proof.verify(root) + return err +} + +func (proof *RangeProof) verify(root []byte) (err error) { + rootHash := proof.rootHash + if rootHash == nil { + derivedHash, err := proof.computeRootHash() + if err != nil { + return err } + rootHash = derivedHash } - return err + if !bytes.Equal(rootHash, root) { + return cmn.ErrorWrap(ErrInvalidRoot, "root hash doesn't match") + } else { + proof.rootVerified = true + } + return nil } -func (proof *RangeProof) _verify(root []byte) (treeEnd bool, err error) { - if !bytes.Equal(proof.RootHash, root) { - return false, cmn.ErrorWrap(ErrInvalidRoot, "root hash doesn't match") +// ComputeRootHash computes the root hash with leaves. +// Returns nil if error or proof is nil. +// Does not verify the root hash. +func (proof *RangeProof) ComputeRootHash() []byte { + if proof == nil { + return nil } + rootHash, _ := proof.computeRootHash() + return rootHash +} + +func (proof *RangeProof) computeRootHash() (rootHash []byte, err error) { + rootHash, treeEnd, err := proof._computeRootHash() + if err == nil { + proof.rootHash = rootHash // memoize + proof.treeEnd = treeEnd // memoize + } + return rootHash, err +} + +func (proof *RangeProof) _computeRootHash() (rootHash []byte, treeEnd bool, err error) { if len(proof.Leaves) == 0 { - return false, cmn.ErrorWrap(ErrInvalidProof, "no leaves") + return nil, false, cmn.ErrorWrap(ErrInvalidProof, "no leaves") } if len(proof.InnerNodes)+1 != len(proof.Leaves) { - return false, cmn.ErrorWrap(ErrInvalidProof, "InnerNodes vs Leaves length mismatch, leaves should be 1 more.") + return nil, false, cmn.ErrorWrap(ErrInvalidProof, "InnerNodes vs Leaves length mismatch, leaves should be 1 more.") } // Start from the left path and prove each leaf. @@ -160,28 +229,27 @@ func (proof *RangeProof) _verify(root []byte) (treeEnd bool, err error) { // shared across recursive calls var leaves = proof.Leaves var innersq = proof.InnerNodes - var VERIFY func(path PathToLeaf, root []byte, rightmost bool) (treeEnd bool, done bool, err error) + var COMPUTEHASH func(path PathToLeaf, rightmost bool) (hash []byte, treeEnd bool, done bool, err error) + // rightmost: is the root a rightmost child of the tree? // treeEnd: true iff the last leaf is the last item of the tree. - // NOTE: root doesn't necessarily mean root of the tree here. - VERIFY = func(path PathToLeaf, root []byte, rightmost bool) (treeEnd bool, done bool, err error) { + // Returns the (possibly intermediate, possibly root) hash. + COMPUTEHASH = func(path PathToLeaf, rightmost bool) (hash []byte, treeEnd bool, done bool, err error) { // Pop next leaf. nleaf, rleaves := leaves[0], leaves[1:] leaves = rleaves - // Verify leaf with path. - if err := (pathWithLeaf{ + // Compute hash. + hash = (pathWithLeaf{ Path: path, Leaf: nleaf, - }).verify(root); err != nil { - return false, false, err.Trace(0, "verifying some path to a leaf") - } + }).computeRootHash() // If we don't have any leaves left, we're done. if len(leaves) == 0 { rightmost = rightmost && path.isRightmost() - return rightmost, true, nil + return hash, rightmost, true, nil } // Prove along path (until we run out of leaves). @@ -203,31 +271,35 @@ func (proof *RangeProof) _verify(root []byte) (treeEnd bool, err error) { innersq = rinnersq // Recursively verify inners against remaining leaves. - treeEnd, done, err := VERIFY(inners, lpath.Right, rightmost && rpath.isRightmost()) + derivedRoot, treeEnd, done, err := COMPUTEHASH(inners, rightmost && rpath.isRightmost()) if err != nil { - return treeEnd, false, cmn.ErrorWrap(err, "recursive VERIFY call") - } else if done { - return treeEnd, true, nil + return nil, treeEnd, false, cmn.ErrorWrap(err, "recursive COMPUTEHASH call") + } + if !bytes.Equal(derivedRoot, lpath.Right) { + return nil, treeEnd, false, cmn.ErrorWrap(ErrInvalidRoot, "intermediate root hash %X doesn't match, got %X", lpath.Right, derivedRoot) + } + if done { + return hash, treeEnd, true, nil } } - // We're not done yet. No error, not done either. Technically if - // rightmost, we know there's an error "left over leaves -- malformed - // proof", but we return that at the top level, below. - return false, false, nil + // We're not done yet (leaves left over). No error, not done either. + // Technically if rightmost, we know there's an error "left over leaves + // -- malformed proof", but we return that at the top level, below. + return hash, false, false, nil } // Verify! path := proof.LeftPath - treeEnd, done, err := VERIFY(path, root, true) + rootHash, treeEnd, done, err := COMPUTEHASH(path, true) if err != nil { - return treeEnd, cmn.ErrorWrap(err, "root VERIFY call") + return nil, treeEnd, cmn.ErrorWrap(err, "root COMPUTEHASH call") } else if !done { - return treeEnd, cmn.ErrorWrap(ErrInvalidProof, "left over leaves -- malformed proof") + return nil, treeEnd, cmn.ErrorWrap(ErrInvalidProof, "left over leaves -- malformed proof") } // Ok! - return treeEnd, nil + return rootHash, treeEnd, nil } /////////////////////////////////////////////////////////////////////////////// @@ -236,13 +308,17 @@ func (proof *RangeProof) _verify(root []byte) (treeEnd bool, err error) { // If keyStart or keyEnd don't exist, the leaf before keyStart // or after keyEnd will also be included, but not be included in values. // If keyEnd-1 exists, no later leaves will be included. +// If keyStart >= keyEnd and both not nil, panics. // Limit is never exceeded. -func (t *Tree) getRangeProof(keyStart, keyEnd []byte, limit int) (proof *RangeProof, values [][]byte, err error) { +func (t *Tree) getRangeProof(keyStart, keyEnd []byte, limit int) (proof *RangeProof, keys, values [][]byte, err error) { + if keyStart != nil && keyEnd != nil && bytes.Compare(keyStart, keyEnd) >= 0 { + panic("if keyStart and keyEnd are present, need keyStart < keyEnd.") + } if limit < 0 { panic("limit must be greater or equal to 0 -- 0 means no limit") } if t.root == nil { - return nil, nil, cmn.ErrorWrap(ErrNilRoot, "") + return nil, nil, nil, cmn.ErrorWrap(ErrNilRoot, "") } t.root.hashWithCount() // Ensure that all hashes are calculated. @@ -250,10 +326,17 @@ func (t *Tree) getRangeProof(keyStart, keyEnd []byte, limit int) (proof *RangePr path, left, err := t.root.PathToLeaf(t, keyStart) if err != nil { // Key doesn't exist, but instead we got the prev leaf (or the - // first leaf), which provides proof of absence). + // first or last leaf), which provides proof of absence). err = nil } - values = append(values, left.value) + startOK := keyStart == nil || bytes.Compare(keyStart, left.key) <= 0 + endOK := keyEnd == nil || bytes.Compare(left.key, keyEnd) < 0 + // If left.key is in range, add it to key/values. + if startOK && endOK { + keys = append(keys, left.key) // == keyStart + values = append(values, left.value) + } + // Either way, add to proof leaves. var leaves = []proofLeafNode{proofLeafNode{ Key: left.key, ValueHash: sha256truncated.Hash(left.value), @@ -270,18 +353,9 @@ func (t *Tree) getRangeProof(keyStart, keyEnd []byte, limit int) (proof *RangePr } if _stop { return &RangeProof{ - RootHash: t.root.hash, LeftPath: path, Leaves: leaves, - }, values, nil - } - - if keyEnd != nil && bytes.Compare(cpIncr(left.key), keyEnd) >= 0 { - return &RangeProof{ - RootHash: t.root.hash, - LeftPath: path, - Leaves: leaves, - }, values, nil + }, keys, values, nil } // Get the key after left.key to iterate from. @@ -295,7 +369,7 @@ func (t *Tree) getRangeProof(keyStart, keyEnd []byte, limit int) (proof *RangePr var lastDepth uint8 = 0 var leafCount = 1 // from left above. var pathCount = 0 - // var values [][]byte defined as function outs. + // var keys, values [][]byte defined as function outs. t.root.traverseInRange(t, afterLeft, nil, true, false, 0, func(node *Node, depth uint8) (stop bool) { @@ -331,14 +405,20 @@ func (t *Tree) getRangeProof(keyStart, keyEnd []byte, limit int) (proof *RangePr ValueHash: sha256truncated.Hash(node.value), Version: node.version, }) - // Append value to values. - values = append(values, node.value) leafCount += 1 // Maybe terminate because we found enough leaves. if limit > 0 && limit <= leafCount { return true } - // Maybe terminate because we've found keyEnd-1 or after. + // Terminate if we've found keyEnd or after. + if keyEnd != nil && bytes.Compare(node.key, keyEnd) >= 0 { + return true + } + // Value is in range, append to keys and values. + keys = append(keys, node.key) + values = append(values, node.value) + // Terminate if we've found keyEnd-1 or after. + // We don't want to fetch any leaves for it. if keyEnd != nil && bytes.Compare(cpIncr(node.key), keyEnd) >= 0 { return true } @@ -362,11 +442,10 @@ func (t *Tree) getRangeProof(keyStart, keyEnd []byte, limit int) (proof *RangePr ) return &RangeProof{ - RootHash: t.root.hash, LeftPath: path, InnerNodes: innersq, Leaves: leaves, - }, values, nil + }, keys, values, nil } //---------------------------------------- @@ -374,7 +453,7 @@ func (t *Tree) getRangeProof(keyStart, keyEnd []byte, limit int) (proof *RangePr // GetWithProof gets the value under the key if it exists, or returns nil. // A proof of existence or absence is returned alongside the value. func (t *Tree) GetWithProof(key []byte) (value []byte, proof *RangeProof, err error) { - proof, values, err := t.getRangeProof(key, cpIncr(key), 2) + proof, _, values, err := t.getRangeProof(key, cpIncr(key), 2) if err == nil { if len(values) > 0 { if !bytes.Equal(proof.Leaves[0].Key, key) { @@ -390,18 +469,13 @@ func (t *Tree) GetWithProof(key []byte) (value []byte, proof *RangeProof, err er } // GetRangeWithProof gets key/value pairs within the specified range and limit. -// To specify a descending range, swap the start and end keys. func (t *Tree) GetRangeWithProof(startKey []byte, endKey []byte, limit int) (keys, values [][]byte, proof *RangeProof, err error) { - proof, values, err = t.getRangeProof(startKey, endKey, limit) - for _, leaf := range proof.Leaves { - keys = append(keys, leaf.Key) - } + proof, keys, values, err = t.getRangeProof(startKey, endKey, limit) return } // GetVersionedWithProof gets the value under the key at the specified version -// if it exists, or returns nil. A proof of existence or absence is returned -// alongside the value. +// if it exists, or returns nil. func (tree *VersionedTree) GetVersionedWithProof(key []byte, version int64) ([]byte, *RangeProof, error) { if t, ok := tree.versions[version]; ok { return t.GetWithProof(key) @@ -410,10 +484,10 @@ func (tree *VersionedTree) GetVersionedWithProof(key []byte, version int64) ([]b } // GetVersionedRangeWithProof gets key/value pairs within the specified range -// and limit. To specify a descending range, swap the start and end keys. -// -// Returns a list of values, a list of keys, and a proof. -func (tree *VersionedTree) GetVersionedRangeWithProof(startKey, endKey []byte, limit int, version int64) ([][]byte, [][]byte, *RangeProof, error) { +// and limit. +func (tree *VersionedTree) GetVersionedRangeWithProof(startKey, endKey []byte, limit int, version int64) ( + keys, values [][]byte, proof *RangeProof, err error) { + if t, ok := tree.versions[version]; ok { return t.GetRangeWithProof(startKey, endKey, limit) } diff --git a/proof_test.go b/proof_test.go index 43c0ce53f..77f423464 100644 --- a/proof_test.go +++ b/proof_test.go @@ -25,9 +25,11 @@ func TestTreeGetWithProof(t *testing.T) { require.NoError(err) require.NotEmpty(val) require.NotNil(proof) + err = proof.VerifyItem(key, val) + require.Error(err, "%+v", err) // Verifying item before calling Verify(root) err = proof.Verify(root) require.NoError(err, "%+v", err) - err = proof.VerifyItem(0, key, val) + err = proof.VerifyItem(key, val) require.NoError(err, "%+v", err) key = []byte{0x1} @@ -35,6 +37,8 @@ func TestTreeGetWithProof(t *testing.T) { require.NoError(err) require.Empty(val) require.NotNil(proof) + err = proof.VerifyAbsence(key) + require.Error(err, "%+v", err) // Verifying absence before calling Verify(root) err = proof.Verify(root) require.NoError(err, "%+v", err) err = proof.VerifyAbsence(key) @@ -46,52 +50,50 @@ func TestTreeKeyExistsProof(t *testing.T) { root := tree.Hash() // should get false for proof with nil root - proof, _, err := tree.getRangeProof([]byte("foo"), nil, 1) + proof, _, _, err := tree.getRangeProof([]byte("foo"), nil, 1) assert.NotNil(t, err) assert.NotNil(t, proof.Verify(root)) // insert lots of info and store the bytes - keys := make([][]byte, 200) + allkeys := make([][]byte, 200) for i := 0; i < 200; i++ { key := randstr(20) value := "value_for_" + key tree.Set([]byte(key), []byte(value)) - keys[i] = []byte(key) + allkeys[i] = []byte(key) } - sortByteSlices(keys) // Sort keys + sortByteSlices(allkeys) // Sort all keys root = tree.Hash() // query random key fails - proof, _, err = tree.getRangeProof([]byte("foo"), nil, 2) + proof, _, _, err = tree.getRangeProof([]byte("foo"), nil, 2) assert.Nil(t, err) assert.Nil(t, proof.Verify(root)) assert.Nil(t, proof.VerifyAbsence([]byte("foo")), proof.String()) // query min key fails - proof, _, err = tree.getRangeProof([]byte{0x00}, []byte{0x00}, 2) + proof, _, _, err = tree.getRangeProof([]byte{0x00}, []byte{0x01}, 2) assert.Nil(t, err) assert.Nil(t, proof.Verify(root)) assert.Nil(t, proof.VerifyAbsence([]byte{0x00})) // valid proof for real keys - for i, key := range keys { - var values [][]byte - proof, values, err = tree.getRangeProof(key, nil, 2) + for i, key := range allkeys { + var keys, values [][]byte + proof, keys, values, err = tree.getRangeProof(key, nil, 2) require.Nil(t, err) require.Equal(t, append([]byte("value_for_"), key...), values[0], ) + require.Equal(t, key, keys[0]) require.Nil(t, proof.Verify(root)) require.Nil(t, proof.VerifyAbsence(cpIncr(key))) - if i < len(keys)-1 { - // There should be 2 items as per limit. - if len(values) != 2 { - printNode(tree.ndb, tree.root, 0) - } - require.Equal(t, 2, len(values), proof.String()) - if i < len(keys)-2 { + require.Equal(t, 1, len(keys), proof.String()) + require.Equal(t, 1, len(values), proof.String()) + if i < len(allkeys)-1 { + if i < len(allkeys)-2 { // No last item... not a proof of absence of large key. require.NotNil(t, proof.VerifyAbsence(bytes.Repeat([]byte{0xFF}, 20)), proof.String()) } else { @@ -99,8 +101,6 @@ func TestTreeKeyExistsProof(t *testing.T) { require.Nil(t, proof.VerifyAbsence(bytes.Repeat([]byte{0xFF}, 20))) } } else { - // There should be 1 item since no more can be queried. - require.Equal(t, 1, len(values), values) // last item of tree... valid proof of absence of large key. require.Nil(t, proof.VerifyAbsence(bytes.Repeat([]byte{0xFF}, 20))) } @@ -118,29 +118,41 @@ func TestTreeKeyInRangeProofs(t *testing.T) { } root := tree.Hash() + // For spacing: + T := 10 + nil______ := []byte(nil) + cases := []struct { - startKey byte - endKey byte - keys []byte // one byte per key. + start byte + end byte + pkeys []byte // proof keys, one byte per key. + vals []byte // keys and values, one byte per key. + lidx int64 // proof left index (index of first proof key). + pnc bool // does panic }{ - {startKey: 0x0a, endKey: 0xf7, keys: keys[:10]}, - {startKey: 0x0a, endKey: 0xf8, keys: keys[:10]}, - {startKey: 0x0, endKey: 0xff, keys: keys[:]}, - {startKey: 0x14, endKey: 0xe4, keys: keys[1:9]}, - {startKey: 0x14, endKey: 0xe5, keys: keys[1:9]}, - {startKey: 0x14, endKey: 0xe6, keys: keys[1:10]}, - {startKey: 0x14, endKey: 0xf1, keys: keys[1:10]}, - {startKey: 0x14, endKey: 0xf7, keys: keys[1:10]}, - {startKey: 0x14, endKey: 0xff, keys: keys[1:10]}, - {startKey: 0x2e, endKey: 0x31, keys: keys[2:4]}, - {startKey: 0x2e, endKey: 0x32, keys: keys[2:4]}, - {startKey: 0x2f, endKey: 0x32, keys: keys[2:4]}, - {startKey: 0x2e, endKey: 0x31, keys: keys[2:4]}, - {startKey: 0x2e, endKey: 0x2f, keys: keys[2:3]}, - {startKey: 0x12, endKey: 0x31, keys: keys[1:4]}, - {startKey: 0xf8, endKey: 0xff, keys: keys[9:10]}, - {startKey: 0x12, endKey: 0x20, keys: keys[1:3]}, - {startKey: 0x0, endKey: 0x09, keys: keys[0:1]}, + {start: 0x0a, end: 0xf7, pkeys: keys[0:T], vals: keys[0:9], lidx: 0}, // #0 + {start: 0x0a, end: 0xf8, pkeys: keys[0:T], vals: keys[0:T], lidx: 0}, // #1 + {start: 0x00, end: 0xff, pkeys: keys[0:T], vals: keys[0:T], lidx: 0}, // #2 + {start: 0x14, end: 0xe4, pkeys: keys[1:9], vals: keys[2:8], lidx: 1}, // #3 + {start: 0x14, end: 0xe5, pkeys: keys[1:9], vals: keys[2:9], lidx: 1}, // #4 + {start: 0x14, end: 0xe6, pkeys: keys[1:T], vals: keys[2:9], lidx: 1}, // #5 + {start: 0x14, end: 0xf1, pkeys: keys[1:T], vals: keys[2:9], lidx: 1}, // #6 + {start: 0x14, end: 0xf7, pkeys: keys[1:T], vals: keys[2:9], lidx: 1}, // #7 + {start: 0x14, end: 0xff, pkeys: keys[1:T], vals: keys[2:T], lidx: 1}, // #8 + {start: 0x2e, end: 0x31, pkeys: keys[2:4], vals: keys[2:3], lidx: 2}, // #9 + {start: 0x2e, end: 0x32, pkeys: keys[2:4], vals: keys[2:3], lidx: 2}, // #10 + {start: 0x2f, end: 0x32, pkeys: keys[2:4], vals: nil______, lidx: 2}, // #11 + {start: 0x2e, end: 0x31, pkeys: keys[2:4], vals: keys[2:3], lidx: 2}, // #12 + {start: 0x2e, end: 0x2f, pkeys: keys[2:3], vals: keys[2:3], lidx: 2}, // #13 + {start: 0x12, end: 0x31, pkeys: keys[1:4], vals: keys[2:3], lidx: 1}, // #14 + {start: 0xf8, end: 0xff, pkeys: keys[9:T], vals: nil______, lidx: 9}, // #15 + {start: 0x12, end: 0x20, pkeys: keys[1:3], vals: nil______, lidx: 1}, // #16 + {start: 0x00, end: 0x09, pkeys: keys[0:1], vals: nil______, lidx: 0}, // #17 + {start: 0xf7, end: 0x00, pnc: true}, // #18 + {start: 0xf8, end: 0x00, pnc: true}, // #19 + {start: 0x10, end: 0x10, pnc: true}, // #20 + {start: 0x12, end: 0x12, pnc: true}, // #21 + {start: 0xff, end: 0xf7, pnc: true}, // #22 } // fmt.Println("PRINT TREE") @@ -149,24 +161,36 @@ func TestTreeKeyInRangeProofs(t *testing.T) { for i, c := range cases { t.Logf("case %v", i) - startKey := []byte{c.startKey} - endKey := []byte{c.endKey} + start := []byte{c.start} + end := []byte{c.end} + + if c.pnc { + require.Panics(func() { tree.GetRangeWithProof(start, end, 0) }) + continue + } // Compute range proof. - keys, values, proof, err := tree.GetRangeWithProof(startKey, endKey, 0) + keys, values, proof, err := tree.GetRangeWithProof(start, end, 0) require.NoError(err, "%+v", err) - require.Equal(c.keys, flatten(keys)) - require.Equal(c.keys, flatten(values)) + require.Equal(c.pkeys, flatten(proof.Keys())) + require.Equal(c.vals, flatten(keys)) + require.Equal(c.vals, flatten(values)) + require.Equal(c.lidx, proof.LeftIndex()) // Verify that proof is valid. err = proof.Verify(root) - require.NoError(err, "%+v", err) verifyProof(t, proof, root) - // Verify each value. - for i, key := range c.keys { - err := proof.VerifyItem(i, []byte{key}, []byte{key}) + // Verify each value of pkeys. + for _, key := range c.pkeys { + err := proof.VerifyItem([]byte{key}, []byte{key}) + require.NoError(err) + } + + // Verify each value of vals. + for _, key := range c.vals { + err := proof.VerifyItem([]byte{key}, []byte{key}) require.NoError(err) } } @@ -203,10 +227,6 @@ func verifyProof(t *testing.T, proof *RangeProof, root []byte) { proofBytes, badProofBytes) } } - - // targetted changes fails... - proof.RootHash = test.MutateByteSlice(proof.RootHash) - assert.Error(t, proof.Verify(root)) } //---------------------------------------- diff --git a/tree_test.go b/tree_test.go index a2302285a..4308d6e78 100644 --- a/tree_test.go +++ b/tree_test.go @@ -987,7 +987,7 @@ func TestVersionedTreeProofs(t *testing.T) { require.NoError(err) require.EqualValues(val, []byte("v1")) require.NoError(proof.Verify(root1), proof.String()) - require.NoError(proof.VerifyItem(0, []byte("k2"), val)) + require.NoError(proof.VerifyItem([]byte("k2"), val)) val, proof, err = tree.GetVersionedWithProof([]byte("k4"), 1) require.NoError(err) @@ -999,13 +999,13 @@ func TestVersionedTreeProofs(t *testing.T) { require.NoError(err) require.EqualValues(val, []byte("v2")) require.NoError(proof.Verify(root2), proof.String()) - require.NoError(proof.VerifyItem(0, []byte("k2"), val)) + require.NoError(proof.VerifyItem([]byte("k2"), val)) val, proof, err = tree.GetVersionedWithProof([]byte("k1"), 2) require.NoError(err) require.EqualValues(val, []byte("v1")) require.NoError(proof.Verify(root2)) - require.NoError(proof.VerifyItem(0, []byte("k1"), val)) + require.NoError(proof.VerifyItem([]byte("k1"), val)) val, proof, err = tree.GetVersionedWithProof([]byte("k2"), 3) @@ -1036,7 +1036,7 @@ func TestVersionedTreeHash(t *testing.T) { require.NoError(err) require.EqualValues(val, []byte("F")) require.NoError(proof.Verify(hash2)) - require.NoError(proof.VerifyItem(0, []byte("I"), val)) + require.NoError(proof.VerifyItem([]byte("I"), val)) } func TestNilValueSemantics(t *testing.T) { diff --git a/version.go b/version.go index b9241d031..4c734cb35 100644 --- a/version.go +++ b/version.go @@ -1,3 +1,3 @@ package iavl -const Version = "0.8.1" +const Version = "0.9.1"