-
Notifications
You must be signed in to change notification settings - Fork 3.8k
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
WIP: Fix ICS23-Proofs #6178
WIP: Fix ICS23-Proofs #6178
Changes from all commits
73462b2
59d5fd1
567fc51
2da7b0d
4c38f3e
dba44d8
d26a2d8
f605880
6c66834
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 |
---|---|---|
|
@@ -6,7 +6,6 @@ import ( | |
"io" | ||
|
||
"github.com/tendermint/tendermint/crypto/merkle" | ||
"github.com/tendermint/tendermint/crypto/tmhash" | ||
"github.com/tendermint/tendermint/libs/kv" | ||
) | ||
|
||
|
@@ -30,13 +29,9 @@ func newMerkleMap() *merkleMap { | |
func (sm *merkleMap) set(key string, value []byte) { | ||
sm.sorted = false | ||
|
||
// The value is hashed, so you can check for equality with a cached value (say) | ||
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. Okay, this is the same change as in tendermint. Again, why? (Also this breaks ics23-tendermint, which needs a PR to update the format and regenerate the proofs) 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. https://github.com/confio/ics23-tendermint/pulls if you really want to make this. But there should be no real reason to mix it in this PR. 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. Why do we have this code in two places in the first place? |
||
// and make a determination to fetch or not. | ||
vhash := tmhash.Sum(value) | ||
|
||
sm.kvs = append(sm.kvs, kv.Pair{ | ||
Key: []byte(key), | ||
Value: vhash, | ||
Value: value, | ||
}) | ||
} | ||
|
||
|
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 | ||
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. | ||
|
@@ -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) | ||
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. If you can drop this one (:+1:) you an also add another proof op that returns ics23 format, no? |
||
return | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,6 +7,7 @@ import ( | |
|
||
"github.com/pkg/errors" | ||
abci "github.com/tendermint/tendermint/abci/types" | ||
"github.com/tendermint/tendermint/crypto/merkle" | ||
"github.com/tendermint/tendermint/crypto/tmhash" | ||
dbm "github.com/tendermint/tm-db" | ||
|
||
|
@@ -456,11 +457,11 @@ func (rs *Store) Query(req abci.RequestQuery) abci.ResponseQuery { | |
} | ||
} | ||
|
||
// Get proofs for all store keys from commitInfo | ||
popMap := commitInfo.StoreProofs() | ||
|
||
// 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, popMap[storeName].ProofOp()) | ||
|
||
// TODO: handle in another TM v0.26 update PR | ||
// res.Proof = buildMultiStoreProof(res.Proof, storeName, commitInfo.StoreInfos) | ||
|
@@ -572,6 +573,17 @@ func (ci commitInfo) Hash() []byte { | |
return SimpleHashFromMap(m) | ||
} | ||
|
||
// StoreProofs returns the simpleproofs for each store key | ||
func (ci commitInfo) StoreProofs() map[string]merkle.SimpleValueOp { | ||
// TODO: cache to ci.hash []byte | ||
m := make(map[string][]byte, len(ci.StoreInfos)) | ||
for _, storeInfo := range ci.StoreInfos { | ||
m[storeInfo.Name] = storeInfo.Hash() | ||
} | ||
|
||
return SimpleProofOpsFromMap(m) | ||
} | ||
|
||
func (ci commitInfo) CommitID() types.CommitID { | ||
return types.CommitID{ | ||
Version: ci.Version, | ||
|
@@ -609,7 +621,8 @@ func (si storeInfo) Hash() []byte { | |
panic(err) | ||
} | ||
|
||
return hasher.Sum(nil) | ||
hash := hasher.Sum(nil) | ||
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. Why this change? 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. Missed reverting this change during debugging |
||
return hash | ||
} | ||
|
||
//---------------------------------------- | ||
|
@@ -714,3 +727,13 @@ func SimpleHashFromMap(m map[string][]byte) []byte { | |
|
||
return mm.hash() | ||
} | ||
|
||
func SimpleProofOpsFromMap(m map[string][]byte) map[string]merkle.SimpleValueOp { | ||
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. No need to convert all the Unless you actually use the multi-return value elsewhere (where this code would make sense), why not just: // in real code handle the missing case better
func SimpleProofOpFromMap(m map[string][]byte, key string) merkle.SimpleValueOp {
_, proofs, _ := merkle.SimpleProofsFromMap(m)
return merkle.NewSimpleValueOp([]byte(key), proofs[key])
} |
||
_, proofs, _ := merkle.SimpleProofsFromMap(m) | ||
popMap := make(map[string]merkle.SimpleValueOp, len(proofs)) | ||
|
||
for k, p := range proofs { | ||
popMap[k] = merkle.NewSimpleValueOp([]byte(k), p) | ||
} | ||
return popMap | ||
} |
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 just looked at this. Essentially changing the hashing algorithm for SimpleMerkleProof (which breaks any external tooling that uses it). Why was that done? And is there any goal to move that into master??
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.
AdityaSripal/tendermint@b8be4a0
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 would remove this replace. Step by step. No need to require merging a breaking change into tendermint core to get this working. If you think it is a nice optimiziation, it may be, and can be another PR process there, but it feels odd to pull it into this PR.
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.
This isn't an optimization. The way simple proofs were being verified didn't line up with the way the stores were getting hashed. There was an extra rounds of hashing happening for leaf values in proof verification that wasn't happening with the simple hash. Thus the proof did not commit to the multistore hash, when just using
SimpleHash
andSimpleProof
without any changes.After tracking the issue down, I found that removing these extra layers of hashing made the proof verification line up with the multistore commit. Haven't found alternative solutions that also work, so I left this for now and moved on with rest of the code. Will look into this further and try to understand issue fully, because I too do not want to change tendermint if I don't have to.
If it's possible to have an SDK only solution, I will use it. If not, this may be necessary. I haven't figured out if it is necessary yet.
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.
Are we still hashing somewhere? Maybe this was accidental, or maybe there was a reason for it - can we ask the Tendermint team (are they using this code anywhere)?
If they're not using this code anywhere, maybe we should move it out of Tendermint altogether.