-
Notifications
You must be signed in to change notification settings - Fork 268
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
General Merkle Proof #105
General Merkle Proof #105
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
package iavl | ||
|
||
import ( | ||
"fmt" | ||
|
||
"github.com/tendermint/tendermint/crypto/merkle" | ||
cmn "github.com/tendermint/tmlibs/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.UnmarshalBinary(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.MustMarshalBinary(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 | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
package iavl | ||
|
||
import ( | ||
"fmt" | ||
|
||
"github.com/tendermint/tendermint/crypto/merkle" | ||
cmn "github.com/tendermint/tmlibs/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.UnmarshalBinary(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.MustMarshalBinary(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 | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,7 +7,7 @@ import ( | |
"strings" | ||
|
||
"github.com/tendermint/tendermint/crypto/tmhash" | ||
cmn "github.com/tendermint/tendermint/libs/common" | ||
cmn "github.com/tendermint/tmlibs/common" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is there a reason this doesn't use There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. fixed on another PR i'll make. |
||
) | ||
|
||
type RangeProof struct { | ||
|
@@ -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,21 +305,20 @@ 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.") | ||
} | ||
if limit < 0 { | ||
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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It is weird that a change like this doesn't need a change in a test, too. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @mossid can you add a test-case for this? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Made issue #115 |
||
} 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, "") | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If IAVL is going to remain its thing/own repo (which I think it should), it would really be nice to not take on more dependencies that couple it tightly to Tendermint at a particular version. Ultimately it would be good to drop dependencies on tendermint altogether if we can spin tmlibs/db out into its own thing see #46.
For
ProofOperator
you can just replicate the interface (or even better the subset of the interface you actually use) here - which makes it clearer what this package. ForProofOp
could you just define the type you need here and do a bit of mapping elsewhere when you want to use the protofbuf type?A little bit of duplication could make this library more lean and loosely coupled.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree, but to do after game of stakes.