diff --git a/CHANGELOG.md b/CHANGELOG.md index bc67fbb1b..918f98739 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,17 @@ # Changelog +## 0.12.0 (November 26, 2018) + +BREAKING CHANGES + +- Uses new Tendermint ReverseIterator API. See https://github.com/tendermint/tendermint/pull/2913 + +## 0.11.1 (October 29, 2018) + +IMPROVEMENTS + +- Uses GoAmino v0.14 + ## 0.11.0 (September 7, 2018) BREAKING CHANGES @@ -35,6 +47,12 @@ IMPROVEMENTS - Change tendermint dep to ^v0.22.0 (#91) +## 0.10.0 (July 11, 2018) + +BREAKING CHANGES + +- getRangeProof and Get\[Versioned\]\[Range\]WithProof return nil proof/error if tree is empty. + ## 0.9.2 (July 3, 2018) IMPROVEMENTS diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 000000000..c1cdd5283 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,8 @@ +# Contributing + +Thank you for considering making contributions to IAVL+! +This repository follows the [contribution guidelines] of tendermint and the corresponding [coding repo]. +Please take a look if you are not already familiar with those. + +[contribution guidelines]: https://github.com/tendermint/tendermint/blob/master/CONTRIBUTING.md +[coding repo]: https://github.com/tendermint/coding diff --git a/Gopkg.lock b/Gopkg.lock index 4ebc929aa..1cfeff1ab 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -2,15 +2,15 @@ [[projects]] - digest = "1:a2c1d0e43bd3baaa071d1b9ed72c27d78169b2b269f71c105ac4ba34b1be4a39" + digest = "1:ffe9824d294da03b391f44e1ae8281281b4afc1bdaa9588c9097785e3af10cec" name = "github.com/davecgh/go-spew" packages = ["spew"] pruneopts = "UT" - revision = "346938d642f2ec3594ed81d874461961cd0faa76" - version = "v1.1.0" + revision = "8991bc29aa16c548c550c7ff78260e27b9ab7c73" + version = "v1.1.1" [[projects]] - digest = "1:d0309acc14b09c713e2fd1283be859c8e03f5f40c69e221410a0f652742b139c" + digest = "1:704b145137b5555c6bac6a7e52875e4df8a0a1b7ea74aaae864abcab61e672b0" name = "github.com/go-kit/kit" packages = [ "log", @@ -22,23 +22,23 @@ version = "v0.6.0" [[projects]] - digest = "1:31a18dae27a29aa074515e43a443abfd2ba6deb6d69309d8d7ce789c45f34659" + digest = "1:4062bc6de62d73e2be342243cf138cf499b34d558876db8d9430e2149388a4d8" name = "github.com/go-logfmt/logfmt" packages = ["."] pruneopts = "UT" - revision = "390ab7935ee28ec6b286364bba9b4dd6410cb3d5" - version = "v0.3.0" + revision = "07c9b44f60d7ffdfb7d8efe1ad539965737836dc" + version = "v0.4.0" [[projects]] - digest = "1:c4a2528ccbcabf90f9f3c464a5fc9e302d592861bbfd0b7135a7de8a943d0406" + digest = "1:586ea76dbd0374d6fb649a91d70d652b7fe0ccffb8910a77468e7702e7901f3d" name = "github.com/go-stack/stack" packages = ["."] pruneopts = "UT" - revision = "259ab82a6cad3992b4e21ff5cac294ccb06474bc" - version = "v1.7.0" + revision = "2fee6af1a9795aafbe0253a0cfbdf668e1fb8a9a" + version = "v1.8.0" [[projects]] - digest = "1:0cff52d4289e3d31494fb4e63d00c93be0f9d07ad1e3447d1dc300add4e32224" + digest = "1:bfc758d5a03d57d97226fac6934551c01bd76612adb119c177395b057a0a46db" name = "github.com/gogo/protobuf" packages = [ "gogoproto", @@ -98,7 +98,7 @@ version = "v1.0.0" [[projects]] - digest = "1:befd7181c2f92c9f05abf17cd7e15538919f19cf7871d794cacc45a4fb99c135" + digest = "1:c40d65817cdd41fac9aa7af8bed56927bb2d6d47e4fea566a74880f5c2b1c41e" name = "github.com/stretchr/testify" packages = [ "assert", @@ -110,7 +110,7 @@ [[projects]] branch = "master" - digest = "1:bd62f27525a36697564991b8e6071ff56afa99d3235261924a0212db5ce780bd" + digest = "1:9ff9e1808adfc43f788f1c1e9fd2660c285b522243da985a4c043ec6f2a2d736" name = "github.com/syndtr/goleveldb" packages = [ "leveldb", @@ -127,20 +127,21 @@ "leveldb/util", ] pruneopts = "UT" - revision = "0d5a0ceb10cf9ab89fdd744cc8c50a83134f6697" + revision = "f9080354173f192dfc8821931eacf9cfd6819253" [[projects]] - digest = "1:e9113641c839c21d8eaeb2c907c7276af1eddeed988df8322168c56b7e06e0e1" + digest = "1:ad9c4c1a4e7875330b1f62906f2830f043a23edb5db997e3a5ac5d3e6eadf80a" name = "github.com/tendermint/go-amino" packages = ["."] pruneopts = "UT" - revision = "2106ca61d91029c931fd54968c2bb02dc96b1412" - version = "0.10.1" + revision = "dc14acf9ef15f85828bfbc561ed9dd9d2a284885" + version = "v0.14.1" [[projects]] - digest = "1:3502c7cf4ada0e74d6a21e524fdd8ad8741b0a8da5219b9c38e3890d5ebbad1c" + digest = "1:a0fe2dfa98e218733ef751fcdfaceaac7192776020dd64c0cedc84daa07199d6" name = "github.com/tendermint/tendermint" packages = [ + "crypto/merkle", "crypto/tmhash", "libs/common", "libs/db", @@ -148,19 +149,19 @@ "libs/test", ] pruneopts = "UT" - revision = "5fdbcd70df57b71ffba71e1ff5f00d617852a9c0" - version = "v0.22.6" + revision = "44b769b1acd0b2aaa8c199b0885bc2811191fb2e" + version = "v0.27.0-dev1" [[projects]] branch = "master" - digest = "1:ac66d893cc5a3e78b27affceb96cdcc284318ae8f8aa86f9f6f51322d7d744f5" + digest = "1:0a733d91d2a819c502ba47ecddfd51a9938b3d61213d24d559947aed81230f70" name = "golang.org/x/crypto" packages = [ "ripemd160", "sha3", ] pruneopts = "UT" - revision = "a49355c7e3f8fe157a85be2f77e6e269a0f89602" + revision = "9eb0be3963eaeb646c9a9b6d11f3da2b249bb2ca" [solve-meta] analyzer-name = "dep" @@ -169,6 +170,7 @@ "github.com/stretchr/testify/assert", "github.com/stretchr/testify/require", "github.com/tendermint/go-amino", + "github.com/tendermint/tendermint/crypto/merkle", "github.com/tendermint/tendermint/crypto/tmhash", "github.com/tendermint/tendermint/libs/common", "github.com/tendermint/tendermint/libs/db", diff --git a/Gopkg.toml b/Gopkg.toml index 39510f840..246dc1587 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -3,12 +3,12 @@ version = "1.1.4" [[constraint]] - version = "=0.10.1" name = "github.com/tendermint/go-amino" + version = "v0.14.0" [[constraint]] - version = "^0.22.0" name = "github.com/tendermint/tendermint" + version = "v0.27.0-dev1" [prune] go-tests = true diff --git a/basic_test.go b/basic_test.go index c5ff62709..263a04a7a 100644 --- a/basic_test.go +++ b/basic_test.go @@ -423,10 +423,14 @@ func TestProof(t *testing.T) { func TestTreeProof(t *testing.T) { db := db.NewMemDB() tree := NewMutableTree(db, 100) + assert.Equal(t, tree.Hash(), []byte(nil)) // should get false for proof with nil root - _, _, err := tree.GetWithProof([]byte("foo")) - assert.Error(t, err) + value, proof, err := tree.GetWithProof([]byte("foo")) + assert.Nil(t, value) + assert.Nil(t, proof) + assert.Error(t, proof.Verify([]byte(nil))) + assert.NoError(t, err) // insert lots of info and store the bytes keys := make([][]byte, 200) @@ -436,9 +440,15 @@ func TestTreeProof(t *testing.T) { keys[i] = []byte(key) } + tree.SaveVersion() + // query random key fails - _, _, err = tree.GetWithProof([]byte("foo")) + value, proof, err = tree.GetWithProof([]byte("foo")) + assert.Nil(t, value) + assert.NotNil(t, proof) assert.NoError(t, err) + assert.NoError(t, proof.Verify(tree.Hash())) + assert.NoError(t, proof.VerifyAbsence([]byte("foo"))) // valid proof for real keys root := tree.WorkingHash() diff --git a/mutable_tree.go b/mutable_tree.go index 05b89cc32..a753502a8 100644 --- a/mutable_tree.go +++ b/mutable_tree.go @@ -263,6 +263,17 @@ func (tree *MutableTree) LoadVersion(targetVersion int64) (int64, error) { return latestVersion, nil } +// LoadVersionOverwrite returns the version number of targetVersion. +// Higher versions' data will be deleted. +func (tree *MutableTree) LoadVersionForOverwriting(targetVersion int64) (int64, error) { + latestVersion, err := tree.LoadVersion(targetVersion) + if err != nil { + return latestVersion, err + } + tree.deleteVersionsFrom(targetVersion+1) + return targetVersion, nil +} + // GetImmutable loads an ImmutableTree at a given version for querying func (tree *MutableTree) GetImmutable(version int64) (*ImmutableTree, error) { rootHash := tree.ndb.getRoot(version) @@ -365,7 +376,7 @@ func (tree *MutableTree) DeleteVersion(version int64) error { return cmn.ErrorWrap(ErrVersionDoesNotExist, "") } - tree.ndb.DeleteVersion(version) + tree.ndb.DeleteVersion(version, true) tree.ndb.Commit() delete(tree.versions, version) @@ -373,6 +384,29 @@ func (tree *MutableTree) DeleteVersion(version int64) error { return nil } +// deleteVersionsFrom deletes tree version from disk specified version to latest version. The version can then no +// longer be accessed. +func (tree *MutableTree) deleteVersionsFrom(version int64) error { + if version <= 0 { + return cmn.NewError("version must be greater than 0") + } + newLatestVersion := version - 1 + lastestVersion := tree.ndb.getLatestVersion() + for ; version <= lastestVersion; version++ { + if version == tree.version { + return cmn.NewError("cannot delete latest saved version (%d)", version) + } + if _, ok := tree.versions[version]; !ok { + return cmn.ErrorWrap(ErrVersionDoesNotExist, "") + } + tree.ndb.DeleteVersion(version, false) + delete(tree.versions, version) + } + tree.ndb.Commit() + tree.ndb.resetLatestVersion(newLatestVersion) + return nil +} + // Rotate right and return the new node and orphan. func (tree *MutableTree) rotateRight(node *Node) (*Node, *Node) { version := tree.version + 1 diff --git a/nodedb.go b/nodedb.go index 1844ded05..c41a1c01c 100644 --- a/nodedb.go +++ b/nodedb.go @@ -154,12 +154,12 @@ func (ndb *nodeDB) SaveBranch(node *Node) []byte { } // DeleteVersion deletes a tree version from disk. -func (ndb *nodeDB) DeleteVersion(version int64) { +func (ndb *nodeDB) DeleteVersion(version int64, checkLatestVersion bool) { ndb.mtx.Lock() defer ndb.mtx.Unlock() ndb.deleteOrphans(version) - ndb.deleteRoot(version) + ndb.deleteRoot(version, checkLatestVersion) } // Saves orphaned nodes to disk under a special prefix. @@ -244,10 +244,14 @@ func (ndb *nodeDB) updateLatestVersion(version int64) { } } +func (ndb *nodeDB) resetLatestVersion(version int64) { + ndb.latestVersion = version +} + func (ndb *nodeDB) getPreviousVersion(version int64) int64 { itr := ndb.db.ReverseIterator( - rootKeyFormat.Key(version-1), - rootKeyFormat.Key(0), + rootKeyFormat.Key(1), + rootKeyFormat.Key(version), ) defer itr.Close() @@ -262,8 +266,8 @@ func (ndb *nodeDB) getPreviousVersion(version int64) int64 { } // deleteRoot deletes the root entry from disk, but not the node it points to. -func (ndb *nodeDB) deleteRoot(version int64) { - if version == ndb.getLatestVersion() { +func (ndb *nodeDB) deleteRoot(version int64, checkLatestVersion bool) { + if checkLatestVersion && version == ndb.getLatestVersion() { panic("Tried to delete latest version") } diff --git a/proof.go b/proof.go index b68293ccc..e3c0d0c4d 100644 --- a/proof.go +++ b/proof.go @@ -18,9 +18,6 @@ var ( // ErrInvalidRoot is returned when the root passed in does not match the proof's. ErrInvalidRoot = fmt.Errorf("invalid root") - - // ErrNilRoot is returned when the root of the tree is nil. - ErrNilRoot = fmt.Errorf("tree root is nil") ) //---------------------------------------- diff --git a/proof_iavl_absence.go b/proof_iavl_absence.go new file mode 100644 index 000000000..88a6587ae --- /dev/null +++ b/proof_iavl_absence.go @@ -0,0 +1,87 @@ +package iavl + +import ( + "fmt" + + "github.com/tendermint/tendermint/crypto/merkle" + cmn "github.com/tendermint/tendermint/libs/common" +) + +const ProofOpIAVLAbsence = "iavl:a" + +// IAVLAbsenceOp takes a key as its only argument +// +// If the produced root hash matches the expected hash, the proof +// is good. +type IAVLAbsenceOp struct { + // Encoded in ProofOp.Key. + key []byte + + // To encode in ProofOp.Data. + // Proof is nil for an empty tree. + // The hash of an empty tree is nil. + Proof *RangeProof `json:"proof"` +} + +var _ merkle.ProofOperator = IAVLAbsenceOp{} + +func NewIAVLAbsenceOp(key []byte, proof *RangeProof) IAVLAbsenceOp { + return IAVLAbsenceOp{ + key: key, + Proof: proof, + } +} + +func IAVLAbsenceOpDecoder(pop merkle.ProofOp) (merkle.ProofOperator, error) { + if pop.Type != ProofOpIAVLAbsence { + return nil, cmn.NewError("unexpected ProofOp.Type; got %v, want %v", pop.Type, ProofOpIAVLAbsence) + } + var op IAVLAbsenceOp // a bit strange as we'll discard this, but it works. + err := cdc.UnmarshalBinaryLengthPrefixed(pop.Data, &op) + if err != nil { + return nil, cmn.ErrorWrap(err, "decoding ProofOp.Data into IAVLAbsenceOp") + } + return NewIAVLAbsenceOp(pop.Key, op.Proof), nil +} + +func (op IAVLAbsenceOp) ProofOp() merkle.ProofOp { + bz := cdc.MustMarshalBinaryLengthPrefixed(op) + return merkle.ProofOp{ + Type: ProofOpIAVLAbsence, + Key: op.key, + Data: bz, + } +} + +func (op IAVLAbsenceOp) String() string { + return fmt.Sprintf("IAVLAbsenceOp{%v}", op.GetKey()) +} + +func (op IAVLAbsenceOp) Run(args [][]byte) ([][]byte, error) { + if len(args) != 0 { + return nil, cmn.NewError("expected 0 args, got %v", len(args)) + } + // If the tree is nil, the proof is nil, and all keys are absent. + if op.Proof == nil { + return [][]byte{[]byte(nil)}, nil + } + // Compute the root hash and assume it is valid. + // The caller checks the ultimate root later. + root := op.Proof.ComputeRootHash() + err := op.Proof.Verify(root) + if err != nil { + return nil, cmn.ErrorWrap(err, "computing root hash") + } + // XXX What is the encoding for keys? + // We should decode the key depending on whether it's a string or hex, + // maybe based on quotes and 0x prefix? + err = op.Proof.VerifyAbsence([]byte(op.key)) + if err != nil { + return nil, cmn.ErrorWrap(err, "verifying absence") + } + return [][]byte{root}, nil +} + +func (op IAVLAbsenceOp) GetKey() []byte { + return op.key +} diff --git a/proof_iavl_value.go b/proof_iavl_value.go new file mode 100644 index 000000000..df3c905ef --- /dev/null +++ b/proof_iavl_value.go @@ -0,0 +1,86 @@ +package iavl + +import ( + "fmt" + + "github.com/tendermint/tendermint/crypto/merkle" + cmn "github.com/tendermint/tendermint/libs/common" +) + +const ProofOpIAVLValue = "iavl:v" + +// IAVLValueOp takes a key and a single value as argument and +// produces the root hash. +// +// If the produced root hash matches the expected hash, the proof +// is good. +type IAVLValueOp struct { + // Encoded in ProofOp.Key. + key []byte + + // To encode in ProofOp.Data. + // Proof is nil for an empty tree. + // The hash of an empty tree is nil. + Proof *RangeProof `json:"proof"` +} + +var _ merkle.ProofOperator = IAVLValueOp{} + +func NewIAVLValueOp(key []byte, proof *RangeProof) IAVLValueOp { + return IAVLValueOp{ + key: key, + Proof: proof, + } +} + +func IAVLValueOpDecoder(pop merkle.ProofOp) (merkle.ProofOperator, error) { + if pop.Type != ProofOpIAVLValue { + return nil, cmn.NewError("unexpected ProofOp.Type; got %v, want %v", pop.Type, ProofOpIAVLValue) + } + var op IAVLValueOp // a bit strange as we'll discard this, but it works. + err := cdc.UnmarshalBinaryLengthPrefixed(pop.Data, &op) + if err != nil { + return nil, cmn.ErrorWrap(err, "decoding ProofOp.Data into IAVLValueOp") + } + return NewIAVLValueOp(pop.Key, op.Proof), nil +} + +func (op IAVLValueOp) ProofOp() merkle.ProofOp { + bz := cdc.MustMarshalBinaryLengthPrefixed(op) + return merkle.ProofOp{ + Type: ProofOpIAVLValue, + Key: op.key, + Data: bz, + } +} + +func (op IAVLValueOp) String() string { + return fmt.Sprintf("IAVLValueOp{%v}", op.GetKey()) +} + +func (op IAVLValueOp) Run(args [][]byte) ([][]byte, error) { + if len(args) != 1 { + return nil, cmn.NewError("Value size is not 1") + } + value := args[0] + + // Compute the root hash and assume it is valid. + // The caller checks the ultimate root later. + root := op.Proof.ComputeRootHash() + err := op.Proof.Verify(root) + if err != nil { + return nil, cmn.ErrorWrap(err, "computing root hash") + } + // XXX What is the encoding for keys? + // We should decode the key depending on whether it's a string or hex, + // maybe based on quotes and 0x prefix? + err = op.Proof.VerifyItem([]byte(op.key), value) + if err != nil { + return nil, cmn.ErrorWrap(err, "verifying value") + } + return [][]byte{root}, nil +} + +func (op IAVLValueOp) GetKey() []byte { + return op.key +} diff --git a/proof_range.go b/proof_range.go index ec438515e..0f37506a3 100644 --- a/proof_range.go +++ b/proof_range.go @@ -126,8 +126,9 @@ func (proof *RangeProof) VerifyAbsence(key []byte) error { if cmp < 0 { if proof.LeftPath.isLeftmost() { return nil + } else { + return cmn.NewError("absence not proved by left path") } - return cmn.NewError("absence not proved by left path") } else if cmp == 0 { return cmn.NewError("absence disproved via first item #0") } @@ -149,7 +150,7 @@ func (proof *RangeProof) VerifyAbsence(key []byte) error { } else { if i == len(proof.Leaves)-1 { // If last item, check whether - // it's the last item in the tree. + // it's the last item in teh tree. } continue @@ -164,8 +165,9 @@ func (proof *RangeProof) VerifyAbsence(key []byte) error { // It's not a valid absence proof. if len(proof.Leaves) < 2 { return cmn.NewError("absence not proved by right leaf (need another leaf?)") + } else { + return cmn.NewError("absence not proved by right leaf") } - return cmn.NewError("absence not proved by right leaf") } // Verify that proof is valid. @@ -177,7 +179,7 @@ func (proof *RangeProof) Verify(root []byte) error { return err } -func (proof *RangeProof) verify(root []byte) error { +func (proof *RangeProof) verify(root []byte) (err error) { rootHash := proof.rootHash if rootHash == nil { derivedHash, err := proof.computeRootHash() @@ -188,8 +190,9 @@ func (proof *RangeProof) verify(root []byte) error { } if !bytes.Equal(rootHash, root) { return cmn.ErrorWrap(ErrInvalidRoot, "root hash doesn't match") + } else { + proof.rootVerified = true } - proof.rootVerified = true return nil } @@ -302,13 +305,12 @@ func (proof *RangeProof) _computeRootHash() (rootHash []byte, treeEnd bool, err /////////////////////////////////////////////////////////////////////////////// // keyStart is inclusive and keyEnd is exclusive. -// Returns the range-proof and the included keys and values. // 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 *ImmutableTree) getRangeProof(keyStart, keyEnd []byte, limit int) (*RangeProof, [][]byte, [][]byte, error) { +func (t *ImmutableTree) 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.") } @@ -316,7 +318,7 @@ func (t *ImmutableTree) getRangeProof(keyStart, keyEnd []byte, limit int) (*Rang panic("limit must be greater or equal to 0 -- 0 means no limit") } if t.root == nil { - return nil, nil, nil, cmn.ErrorWrap(ErrNilRoot, "") + return nil, nil, nil, nil } t.root.hashWithCount() // Ensure that all hashes are calculated. @@ -325,18 +327,17 @@ func (t *ImmutableTree) getRangeProof(keyStart, keyEnd []byte, limit int) (*Rang if err != nil { // Key doesn't exist, but instead we got the prev leaf (or the // first or last leaf), which provides proof of absence). - // err = nil isn't necessary as we do not use it in the returns below + err = nil } 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. - var keys, values [][]byte if startOK && endOK { keys = append(keys, left.key) // == keyStart values = append(values, left.value) } // Either way, add to proof leaves. - var leaves = []proofLeafNode{{ + var leaves = []proofLeafNode{proofLeafNode{ Key: left.key, ValueHash: tmhash.Sum(left.value), Version: left.version, @@ -362,7 +363,6 @@ func (t *ImmutableTree) getRangeProof(keyStart, keyEnd []byte, limit int) (*Rang // Traverse starting from afterLeft, until keyEnd or the next leaf // after keyEnd. - // nolint var innersq = []PathToLeaf(nil) var inners = PathToLeaf(nil) var leafCount = 1 // from left above. @@ -385,9 +385,9 @@ func (t *ImmutableTree) getRangeProof(keyStart, keyEnd []byte, limit int) (*Rang pn.Right != nil && !bytes.Equal(pn.Right, node.rightHash) { // We've diverged, so start appending to inners. - pathCount-- + pathCount = -1 } else { - pathCount++ + pathCount += 1 } } } @@ -403,7 +403,7 @@ func (t *ImmutableTree) getRangeProof(keyStart, keyEnd []byte, limit int) (*Rang ValueHash: tmhash.Sum(node.value), Version: node.version, }) - leafCount++ + leafCount += 1 // Maybe terminate because we found enough leaves. if limit > 0 && limit <= leafCount { return true @@ -451,16 +451,13 @@ func (t *ImmutableTree) getRangeProof(keyStart, keyEnd []byte, limit int) (*Rang // A proof of existence or absence is returned alongside the value. func (t *ImmutableTree) GetWithProof(key []byte) (value []byte, proof *RangeProof, err error) { proof, _, values, err := t.getRangeProof(key, cpIncr(key), 2) - if err == nil { - if len(values) > 0 { - if !bytes.Equal(proof.Leaves[0].Key, key) { - return nil, proof, nil - } - return values[0], proof, nil - } - return nil, proof, nil + if err != nil { + return nil, nil, cmn.ErrorWrap(err, "constructing range proof") + } + if len(values) > 0 && bytes.Equal(proof.Leaves[0].Key, key) { + return values[0], proof, nil } - return nil, nil, cmn.ErrorWrap(err, "could not construct any proof") + return nil, proof, nil } // GetRangeWithProof gets key/value pairs within the specified range and limit. @@ -477,6 +474,7 @@ func (tree *MutableTree) GetVersionedWithProof(key []byte, version int64) ([]byt if err != nil { return nil, nil, err } + return t.GetWithProof(key) } return nil, nil, cmn.ErrorWrap(ErrVersionDoesNotExist, "") diff --git a/proof_test.go b/proof_test.go index b9e82a2b2..185b8ea76 100644 --- a/proof_test.go +++ b/proof_test.go @@ -51,9 +51,12 @@ func TestTreeKeyExistsProof(t *testing.T) { root := tree.WorkingHash() // should get false for proof with nil root - proof, _, _, err := tree.getRangeProof([]byte("foo"), nil, 1) - assert.NotNil(t, err) - assert.NotNil(t, proof.Verify(root)) + proof, keys, values, err := tree.getRangeProof([]byte("foo"), nil, 1) + assert.Nil(t, proof) + assert.Error(t, proof.Verify(root)) + assert.Nil(t, keys) + assert.Nil(t, values) + assert.NoError(t, err) // insert lots of info and store the bytes allkeys := make([][]byte, 200) @@ -205,21 +208,21 @@ func verifyProof(t *testing.T, proof *RangeProof, root []byte) { // Write/Read then verify. cdc := amino.NewCodec() - proofBytes := cdc.MustMarshalBinary(proof) + proofBytes := cdc.MustMarshalBinaryLengthPrefixed(proof) var proof2 = new(RangeProof) - err := cdc.UnmarshalBinary(proofBytes, proof2) + err := cdc.UnmarshalBinaryLengthPrefixed(proofBytes, proof2) require.Nil(t, err, "Failed to read KeyExistsProof from bytes: %v", err) // Random mutations must not verify for i := 0; i < 1e4; i++ { badProofBytes := test.MutateByteSlice(proofBytes) var badProof = new(RangeProof) - err := cdc.UnmarshalBinary(badProofBytes, badProof) + err := cdc.UnmarshalBinaryLengthPrefixed(badProofBytes, badProof) if err != nil { continue // couldn't even decode. } // re-encode to make sure it's actually different. - badProofBytes2 := cdc.MustMarshalBinary(badProof) + badProofBytes2 := cdc.MustMarshalBinaryLengthPrefixed(badProof) if bytes.Equal(proofBytes, badProofBytes2) { continue // didn't mutate successfully. } diff --git a/tree_test.go b/tree_test.go index b55355b3e..5a1b6d141 100644 --- a/tree_test.go +++ b/tree_test.go @@ -5,6 +5,7 @@ import ( "flag" "os" "runtime" + "strconv" "testing" "github.com/stretchr/testify/require" @@ -1165,6 +1166,71 @@ func TestOverwrite(t *testing.T) { require.NoError(err, "SaveVersion should not fail, overwrite was idempotent") } +func TestLoadVersionForOverwriting(t *testing.T) { + require := require.New(t) + + mdb := db.NewMemDB() + tree := NewMutableTree(mdb, 0) + + maxLength := 100 + for count := 1; count <= maxLength; count++ { + countStr := strconv.Itoa(count) + // Set one kv pair and save version + tree.Set([]byte("key"+countStr), []byte("value"+countStr)) + _, _, err := tree.SaveVersion() + require.NoError(err, "SaveVersion should not fail") + } + + tree = NewMutableTree(mdb, 0) + targetVersion, err := tree.LoadVersionForOverwriting(int64(maxLength * 2)) + require.Equal(targetVersion, int64(maxLength), "targetVersion shouldn't larger than the actual tree latest version") + + tree = NewMutableTree(mdb, 0) + _, err = tree.LoadVersionForOverwriting(int64(maxLength / 2)) + require.NoError(err, "LoadVersion should not fail") + + for version := 1; version <= maxLength/2; version++ { + exist := tree.VersionExists(int64(version)) + require.True(exist, "versions no more than 50 should exist") + } + + for version := (maxLength / 2) + 1; version <= maxLength; version++ { + exist := tree.VersionExists(int64(version)) + require.False(exist, "versions more than 50 should have been deleted") + } + + tree.Set([]byte("key49"), []byte("value49 different")) + _, _, err = tree.SaveVersion() + require.NoError(err, "SaveVersion should not fail, overwrite was allowed") + + tree.Set([]byte("key50"), []byte("value50 different")) + _, _, err = tree.SaveVersion() + require.NoError(err, "SaveVersion should not fail, overwrite was allowed") + + // Reload tree at version 50, the latest tree version is 52 + tree = NewMutableTree(mdb, 0) + _, err = tree.LoadVersion(int64(maxLength / 2)) + require.NoError(err, "LoadVersion should not fail") + + tree.Set([]byte("key49"), []byte("value49 different")) + _, _, err = tree.SaveVersion() + require.NoError(err, "SaveVersion should not fail, write the same value") + + tree.Set([]byte("key50"), []byte("value50 different different")) + _, _, err = tree.SaveVersion() + require.Error(err, "SaveVersion should fail, overwrite was not allowed") + + tree.Set([]byte("key50"), []byte("value50 different")) + _, _, err = tree.SaveVersion() + require.NoError(err, "SaveVersion should not fail, write the same value") + + //The tree version now is 52 which is equal to latest version. + //Now any key value can be written into the tree + tree.Set([]byte("key any value"), []byte("value any value")) + _, _, err = tree.SaveVersion() + require.NoError(err, "SaveVersion should not fail.") +} + //////////////////////////// BENCHMARKS /////////////////////////////////////// func BenchmarkTreeLoadAndDelete(b *testing.B) { diff --git a/version.go b/version.go index aa993f09b..5b1eba83d 100644 --- a/version.go +++ b/version.go @@ -1,4 +1,4 @@ package iavl // Version of iavl. -const Version = "0.11.0" +const Version = "0.12.0"