From 2779f4dba35c2cd631a894cf34d05205e349150c Mon Sep 17 00:00:00 2001 From: Jae Kwon Date: Wed, 7 Nov 2018 00:03:02 -0800 Subject: [PATCH] Add general merkle absence proof (also for empty substores) (#2685) --- PENDING.md | 1 + store/iavlstore.go | 17 +++++++-- store/multistoreproof_test.go | 66 +++++++++++++++++++++++++++++++++++ store/rootmultistore.go | 4 +++ 4 files changed, 86 insertions(+), 2 deletions(-) diff --git a/PENDING.md b/PENDING.md index 232d28f631..9796c00881 100644 --- a/PENDING.md +++ b/PENDING.md @@ -52,6 +52,7 @@ IMPROVEMENTS - \#2660 [x/mock/simulation] Staking transactions get tested far more frequently - \#2610 [x/stake] Block redelegation to and from the same validator - \#2652 [x/auth] Add benchmark for get and set account + - \#2685 [x/store] Add general merkle absence proof (also for empty substores) * Tendermint diff --git a/store/iavlstore.go b/store/iavlstore.go index bb588f029b..fccde38e27 100644 --- a/store/iavlstore.go +++ b/store/iavlstore.go @@ -225,8 +225,21 @@ func (st *iavlStore) Query(req abci.RequestQuery) (res abci.ResponseQuery) { res.Log = err.Error() break } - res.Value = value - res.Proof = &merkle.Proof{Ops: []merkle.ProofOp{iavl.NewIAVLValueOp(key, proof).ProofOp()}} + if proof == nil { + // Proof == nil implies that the store is empty. + if value != nil { + panic("unexpected value for an empty proof") + } + } + if value != nil { + // value was found + res.Value = value + res.Proof = &merkle.Proof{Ops: []merkle.ProofOp{iavl.NewIAVLValueOp(key, proof).ProofOp()}} + } else { + // value wasn't found + res.Value = nil + res.Proof = &merkle.Proof{Ops: []merkle.ProofOp{iavl.NewIAVLAbsenceOp(key, proof).ProofOp()}} + } } else { _, res.Value = tree.GetVersioned(key, res.Height) } diff --git a/store/multistoreproof_test.go b/store/multistoreproof_test.go index 19feef1600..db3b65cad5 100644 --- a/store/multistoreproof_test.go +++ b/store/multistoreproof_test.go @@ -106,3 +106,69 @@ func TestVerifyMultiStoreQueryProof(t *testing.T) { err = prt.VerifyValue(res.Proof, cid.Hash, "/iavlStoreKey/MYKEY", []byte(nil)) require.NotNil(t, err) } + +func TestVerifyMultiStoreQueryProofEmptyStore(t *testing.T) { + // Create main tree for testing. + db := dbm.NewMemDB() + store := NewCommitMultiStore(db) + iavlStoreKey := sdk.NewKVStoreKey("iavlStoreKey") + + store.MountStoreWithDB(iavlStoreKey, sdk.StoreTypeIAVL, nil) + store.LoadVersion(0) + cid := store.Commit() // Commit with empty iavl store. + + // Get Proof + res := store.Query(abci.RequestQuery{ + Path: "/iavlStoreKey/key", // required path to get key/value+proof + Data: []byte("MYKEY"), + Prove: true, + }) + require.NotNil(t, res.Proof) + + // Verify proof. + prt := DefaultProofRuntime() + err := prt.VerifyAbsence(res.Proof, cid.Hash, "/iavlStoreKey/MYKEY") + require.Nil(t, err) + + // Verify (bad) proof. + prt = DefaultProofRuntime() + err = prt.VerifyValue(res.Proof, cid.Hash, "/iavlStoreKey/MYKEY", []byte("MYVALUE")) + require.NotNil(t, err) +} + +func TestVerifyMultiStoreQueryProofAbsence(t *testing.T) { + // Create main tree for testing. + db := dbm.NewMemDB() + store := NewCommitMultiStore(db) + iavlStoreKey := sdk.NewKVStoreKey("iavlStoreKey") + + store.MountStoreWithDB(iavlStoreKey, sdk.StoreTypeIAVL, nil) + store.LoadVersion(0) + + iavlStore := store.GetCommitStore(iavlStoreKey).(*iavlStore) + iavlStore.Set([]byte("MYKEY"), []byte("MYVALUE")) + cid := store.Commit() // Commit with empty iavl store. + + // Get Proof + res := store.Query(abci.RequestQuery{ + Path: "/iavlStoreKey/key", // required path to get key/value+proof + Data: []byte("MYABSENTKEY"), + Prove: true, + }) + require.NotNil(t, res.Proof) + + // Verify proof. + prt := DefaultProofRuntime() + err := prt.VerifyAbsence(res.Proof, cid.Hash, "/iavlStoreKey/MYABSENTKEY") + require.Nil(t, err) + + // Verify (bad) proof. + prt = DefaultProofRuntime() + err = prt.VerifyAbsence(res.Proof, cid.Hash, "/MYABSENTKEY") + require.NotNil(t, err) + + // Verify (bad) proof. + prt = DefaultProofRuntime() + err = prt.VerifyValue(res.Proof, cid.Hash, "/iavlStoreKey/MYABSENTKEY", []byte("")) + require.NotNil(t, err) +} diff --git a/store/rootmultistore.go b/store/rootmultistore.go index 6c491bda13..cd2d0135f1 100644 --- a/store/rootmultistore.go +++ b/store/rootmultistore.go @@ -295,6 +295,10 @@ func (rs *rootMultiStore) Query(req abci.RequestQuery) abci.ResponseQuery { return res } + if res.Proof == nil || len(res.Proof.Ops) == 0 { + return sdk.ErrInternal("substore proof was nil/empty when it should never be").QueryResult() + } + commitInfo, errMsg := getCommitInfo(rs.db, res.Height) if errMsg != nil { return sdk.ErrInternal(errMsg.Error()).QueryResult()