Skip to content
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

Use simple merkle proof for commit info #6323

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
108 changes: 0 additions & 108 deletions store/rootmulti/proof.go
Original file line number Diff line number Diff line change
@@ -1,31 +1,10 @@
package rootmulti

import (
"bytes"
"errors"
"fmt"

"github.com/tendermint/iavl"
"github.com/tendermint/tendermint/crypto/merkle"
)

// MultiStoreProof defines a collection of store proofs in a multi-store
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note to review: This was for handling the serialized StoreInfos[] as a proof, which is now removed in favor of a merkle commitment (which scales much better for dozens or more of substores)

type MultiStoreProof struct {
StoreInfos []storeInfo
}

func NewMultiStoreProof(storeInfos []storeInfo) *MultiStoreProof {
return &MultiStoreProof{StoreInfos: storeInfos}
}

// ComputeRootHash returns the root hash for a given multi-store proof.
func (proof *MultiStoreProof) ComputeRootHash() []byte {
ci := commitInfo{
StoreInfos: proof.StoreInfos,
}
return ci.Hash()
}

// RequireProof returns whether proof is required for the subpath.
func RequireProof(subpath string) bool {
// XXX: create a better convention.
Expand All @@ -37,99 +16,12 @@ func RequireProof(subpath string) bool {

//-----------------------------------------------------------------------------

var _ merkle.ProofOperator = MultiStoreProofOp{}

// the multi-store proof operation constant value
const ProofOpMultiStore = "multistore"

// TODO: document
type MultiStoreProofOp struct {
// Encoded in ProofOp.Key
key []byte

// To encode in ProofOp.Data.
Proof *MultiStoreProof `json:"proof"`
}

func NewMultiStoreProofOp(key []byte, proof *MultiStoreProof) MultiStoreProofOp {
return MultiStoreProofOp{
key: key,
Proof: proof,
}
}

// MultiStoreProofOpDecoder returns a multi-store merkle proof operator from a
// given proof operation.
func MultiStoreProofOpDecoder(pop merkle.ProofOp) (merkle.ProofOperator, error) {
if pop.Type != ProofOpMultiStore {
return nil, fmt.Errorf("unexpected ProofOp.Type; got %v, want %v", pop.Type, ProofOpMultiStore)
}

// XXX: a bit strange as we'll discard this, but it works
var op MultiStoreProofOp

err := cdc.UnmarshalBinaryBare(pop.Data, &op)
if err != nil {
return nil, fmt.Errorf("decoding ProofOp.Data into MultiStoreProofOp: %w", err)
}

return NewMultiStoreProofOp(pop.Key, op.Proof), nil
}

// ProofOp return a merkle proof operation from a given multi-store proof
// operation.
func (op MultiStoreProofOp) ProofOp() merkle.ProofOp {
bz := cdc.MustMarshalBinaryBare(op)
return merkle.ProofOp{
Type: ProofOpMultiStore,
Key: op.key,
Data: bz,
}
}

// String implements the Stringer interface for a mult-store proof operation.
func (op MultiStoreProofOp) String() string {
return fmt.Sprintf("MultiStoreProofOp{%v}", op.GetKey())
}

// GetKey returns the key for a multi-store proof operation.
func (op MultiStoreProofOp) GetKey() []byte {
return op.key
}

// Run executes a multi-store proof operation for a given value. It returns
// the root hash if the value matches all the store's commitID's hash or an
// error otherwise.
func (op MultiStoreProofOp) Run(args [][]byte) ([][]byte, error) {
if len(args) != 1 {
return nil, errors.New("value size is not 1")
}

value := args[0]
root := op.Proof.ComputeRootHash()

for _, si := range op.Proof.StoreInfos {
if si.Name == string(op.key) {
if bytes.Equal(value, si.Core.CommitID.Hash) {
return [][]byte{root}, nil
}

return nil, fmt.Errorf("hash mismatch for substore %v: %X vs %X", si.Name, si.Core.CommitID.Hash, value)
}
}

return nil, fmt.Errorf("key %v not found in multistore proof", op.key)
}

//-----------------------------------------------------------------------------

// XXX: This should be managed by the rootMultiStore which may want to register
// more proof ops?
func DefaultProofRuntime() (prt *merkle.ProofRuntime) {
prt = merkle.NewProofRuntime()
prt.RegisterOpDecoder(merkle.ProofOpSimpleValue, merkle.SimpleValueOpDecoder)
prt.RegisterOpDecoder(iavl.ProofOpIAVLValue, iavl.ValueOpDecoder)
prt.RegisterOpDecoder(iavl.ProofOpIAVLAbsence, iavl.AbsenceOpDecoder)
prt.RegisterOpDecoder(ProofOpMultiStore, MultiStoreProofOpDecoder)
return
}
58 changes: 32 additions & 26 deletions store/rootmulti/store.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import (

"github.com/pkg/errors"
abci "github.com/tendermint/tendermint/abci/types"
"github.com/tendermint/tendermint/crypto/tmhash"
"github.com/tendermint/tendermint/crypto/merkle"
dbm "github.com/tendermint/tm-db"

"github.com/cosmos/cosmos-sdk/codec"
Expand Down Expand Up @@ -457,13 +457,8 @@ func (rs *Store) Query(req abci.RequestQuery) abci.ResponseQuery {
}

// Restore origin path and append proof op.
res.Proof.Ops = append(res.Proof.Ops, NewMultiStoreProofOp(
[]byte(storeName),
NewMultiStoreProof(commitInfo.StoreInfos),
).ProofOp())
res.Proof.Ops = append(res.Proof.Ops, commitInfo.ProofOp(storeName))

// TODO: handle in another TM v0.26 update PR
// res.Proof = buildMultiStoreProof(res.Proof, storeName, commitInfo.StoreInfos)
return res
}

Expand Down Expand Up @@ -561,15 +556,31 @@ type commitInfo struct {
StoreInfos []storeInfo
}

// Hash returns the simple merkle root hash of the stores sorted by name.
func (ci commitInfo) Hash() []byte {
// TODO: cache to ci.hash []byte
func (ci commitInfo) toMap() map[string][]byte {
m := make(map[string][]byte, len(ci.StoreInfos))
for _, storeInfo := range ci.StoreInfos {
m[storeInfo.Name] = storeInfo.Hash()
m[storeInfo.Name] = storeInfo.GetHash()
}
return m
}

// Hash returns the simple merkle root hash of the stores sorted by name.
func (ci commitInfo) Hash() []byte {
// we need a special case for empty set, as SimpleProofsFromMap requires at least one entry
if len(ci.StoreInfos) == 0 {
return nil
}
rootHash, _, _ := merkle.SimpleProofsFromMap(ci.toMap())
return rootHash
}

return SimpleHashFromMap(m)
func (ci commitInfo) ProofOp(storeName string) merkle.ProofOp {
_, proofs, _ := merkle.SimpleProofsFromMap(ci.toMap())
proof := proofs[storeName]
if proof == nil {
panic(fmt.Sprintf("ProofOp for %s but not registered store name", storeName))
}
return merkle.NewSimpleValueOp([]byte(storeName), proof).ProofOp()
}

func (ci commitInfo) CommitID() types.CommitID {
Expand All @@ -596,20 +607,15 @@ type storeCore struct {
// ... maybe add more state
}

// Implements merkle.Hasher.
func (si storeInfo) Hash() []byte {
// Doesn't write Name, since SimpleHashFromMap() will
// include them via the keys.
bz := si.Core.CommitID.Hash
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The storeInfo contains a name and a commitment (hash and version).
This method is not really used to "hash" it in a normal means, but rather pull out the hash that was already inside of it.

In effect, to make a chained proof op work, you would have to take the root hash from the iavlstore and then hash it before feeding it as a value to the merkle simple proof, which makes no sense. We want the simple proof to commit directly to the root hash of the sub stores (not a hash of a hash of a hash... - we are not bitcoin mining).

If anyone intends to use this as a real hashing op (which it never was used as), it must contain the name as well before feeding it to the hasher. In such a case we could rename this to eg. Commitment, which we would use to feed into commitInfo.Hash() and commitInfo.ProofOp().

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, believe this was why the hashing in TM had to be removed. This approach works better. Could you please put the cruz of this comment in the godoc. Namely that this does not actually hash the storeInfo, rather it returns the embedded store hash

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I can definitely add it to the GoDoc. I can also rename the function to make it more explicit that we are not trying to implement the Hasher interface but just access the substore hash.

Actually I can do both (rename an explain the purpose of the function). Any preferences for names?

hasher := tmhash.New()

_, err := hasher.Write(bz)
if err != nil {
// TODO: Handle with #870
panic(err)
}

return hasher.Sum(nil)
// GetHash returns the GetHash from the CommitID.
// This is used in CommitInfo.Hash()
//
// When we commit to this in a merkle proof, we create a map of storeInfo.Name -> storeInfo.GetHash()
// and build a merkle proof from that.
// This is then chained with the substore proof, so we prove the root hash from the substore before this
// and need to pass that (unmodified) as the leaf value of the multistore proof.
func (si storeInfo) GetHash() []byte {
return si.Core.CommitID.Hash
}

//----------------------------------------
Expand Down
2 changes: 1 addition & 1 deletion store/rootmulti/store_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -516,7 +516,7 @@ func hashStores(stores map[types.StoreKey]types.CommitKVStore) []byte {
CommitID: store.LastCommitID(),
// StoreType: store.GetStoreType(),
},
}.Hash()
}.GetHash()
}
return SimpleHashFromMap(m)
}
2 changes: 1 addition & 1 deletion x/evidence/types/params.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,6 @@ import (

// DONTCOVER

// The Double Sign Jail period ends at Max Time supported by Amino
// DoubleSignJailEndTime period ends at Max Time supported by Amino
// (Dec 31, 9999 - 23:59:59 GMT).
var DoubleSignJailEndTime = time.Unix(253402300799, 0)