From 44ac700d8065e19a673460080e1553c130749844 Mon Sep 17 00:00:00 2001 From: Roman Akhtariev Date: Fri, 25 Mar 2022 17:24:36 +0000 Subject: [PATCH 1/3] implement store proof and commit hash query --- store/rootmulti/store.go | 31 ++++++++++++++++++++++++++----- store/rootmulti/store_test.go | 26 +++++++++++++++++++++++--- 2 files changed, 49 insertions(+), 8 deletions(-) diff --git a/store/rootmulti/store.go b/store/rootmulti/store.go index 5571199e54f0..7d00d5a39d63 100644 --- a/store/rootmulti/store.go +++ b/store/rootmulti/store.go @@ -29,6 +29,7 @@ import ( "github.com/pkg/errors" abci "github.com/tendermint/tendermint/abci/types" "github.com/tendermint/tendermint/libs/log" + "github.com/tendermint/tendermint/proto/tendermint/crypto" dbm "github.com/tendermint/tm-db" ) @@ -36,6 +37,8 @@ const ( latestVersionKey = "s/latest" commitInfoKeyFmt = "s/%d" // s/ + proofsPath = "proofs" + // Do not change chunk size without new snapshot format (must be uniform across nodes) snapshotChunkSize = uint64(10e6) snapshotBufferSize = int(snapshotChunkSize) @@ -567,19 +570,37 @@ func (rs *Store) getStoreByName(name string) types.Store { // TODO: add proof for `multistore -> substore`. func (rs *Store) Query(req abci.RequestQuery) abci.ResponseQuery { path := req.Path - storeName, subpath, err := parsePath(path) + firstPath, subpath, err := parsePath(path) if err != nil { return sdkerrors.QueryResult(err) } - store := rs.getStoreByName(storeName) + if firstPath == proofsPath { + commitInfo, err := getCommitInfo(rs.db, req.Height) + if err != nil { + return sdkerrors.QueryResult(err) + } + res := abci.ResponseQuery{ + Height: req.Height, + Key: []byte(proofsPath), + Value: commitInfo.CommitID().Hash, + ProofOps: &crypto.ProofOps{Ops: make([]crypto.ProofOp, 0, len(commitInfo.StoreInfos))}, + } + + for _, storeInfo := range commitInfo.StoreInfos { + res.ProofOps.Ops = append(res.ProofOps.Ops, commitInfo.ProofOp(storeInfo.Name)) + } + return res + } + + store := rs.getStoreByName(firstPath) if store == nil { - return sdkerrors.QueryResult(sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "no such store: %s", storeName)) + return sdkerrors.QueryResult(sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "no such store: %s", firstPath)) } queryable, ok := store.(types.Queryable) if !ok { - return sdkerrors.QueryResult(sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "store %s (type %T) doesn't support queries", storeName, store)) + return sdkerrors.QueryResult(sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "store %s (type %T) doesn't support queries", firstPath, store)) } // trim the path and make the query @@ -609,7 +630,7 @@ func (rs *Store) Query(req abci.RequestQuery) abci.ResponseQuery { } // Restore origin path and append proof op. - res.ProofOps.Ops = append(res.ProofOps.Ops, commitInfo.ProofOp(storeName)) + res.ProofOps.Ops = append(res.ProofOps.Ops, commitInfo.ProofOp(firstPath)) return res } diff --git a/store/rootmulti/store_test.go b/store/rootmulti/store_test.go index 8f838ee66ec5..adac8310d9e9 100644 --- a/store/rootmulti/store_test.go +++ b/store/rootmulti/store_test.go @@ -416,7 +416,7 @@ func TestMultiStoreQuery(t *testing.T) { k2, v2 := []byte("water"), []byte("flows") // v3 := []byte("is cold") - cid := multi.Commit() + cid1 := multi.Commit() // Make sure we can get by name. garbage := multi.getStoreByName("bad-name") @@ -431,8 +431,8 @@ func TestMultiStoreQuery(t *testing.T) { store2.Set(k2, v2) // Commit the multistore. - cid = multi.Commit() - ver := cid.Version + cid2 := multi.Commit() + ver := cid2.Version // Reload multistore from database multi = newMultiStoreWithMounts(db, pruningTypes.NewPruningOptions(pruningTypes.PruningNothing)) @@ -474,6 +474,26 @@ func TestMultiStoreQuery(t *testing.T) { qres = multi.Query(query) require.EqualValues(t, 0, qres.Code) require.Equal(t, v2, qres.Value) + + // Test proofs latest height + query.Path = fmt.Sprintf("/%s", proofsPath) + qres = multi.Query(query) + require.EqualValues(t, 0, qres.Code) + require.NotNil(t, qres.ProofOps) + require.Equal(t, []byte(proofsPath), qres.Key) + require.Equal(t, cid2.Hash, qres.Value) + require.Equal(t, cid2.Version, qres.Height) + require.Equal(t, 3, len(qres.ProofOps.Ops)) // 3 mounted stores + + // Test proofs second latest height + query.Height = query.Height - 1 + qres = multi.Query(query) + require.EqualValues(t, 0, qres.Code) + require.NotNil(t, qres.ProofOps) + require.Equal(t, []byte(proofsPath), qres.Key) + require.Equal(t, cid1.Hash, qres.Value) + require.Equal(t, cid1.Version, qres.Height) + require.Equal(t, 3, len(qres.ProofOps.Ops)) // 3 mounted stores } func TestMultiStore_Pruning(t *testing.T) { From 172ce5f42f776a0ca712acbe85711b0ac5f3dcb1 Mon Sep 17 00:00:00 2001 From: Roman Akhtariev Date: Fri, 25 Mar 2022 18:13:41 +0000 Subject: [PATCH 2/3] move proods query to a separate function --- store/rootmulti/store.go | 35 +++++++++++++++++++---------------- 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/store/rootmulti/store.go b/store/rootmulti/store.go index 7d00d5a39d63..fa942ddf536e 100644 --- a/store/rootmulti/store.go +++ b/store/rootmulti/store.go @@ -567,7 +567,6 @@ func (rs *Store) getStoreByName(name string) types.Store { // Query calls substore.Query with the same `req` where `req.Path` is // modified to remove the substore prefix. // Ie. `req.Path` here is `//`, and trimmed to `/` for the substore. -// TODO: add proof for `multistore -> substore`. func (rs *Store) Query(req abci.RequestQuery) abci.ResponseQuery { path := req.Path firstPath, subpath, err := parsePath(path) @@ -576,21 +575,7 @@ func (rs *Store) Query(req abci.RequestQuery) abci.ResponseQuery { } if firstPath == proofsPath { - commitInfo, err := getCommitInfo(rs.db, req.Height) - if err != nil { - return sdkerrors.QueryResult(err) - } - res := abci.ResponseQuery{ - Height: req.Height, - Key: []byte(proofsPath), - Value: commitInfo.CommitID().Hash, - ProofOps: &crypto.ProofOps{Ops: make([]crypto.ProofOp, 0, len(commitInfo.StoreInfos))}, - } - - for _, storeInfo := range commitInfo.StoreInfos { - res.ProofOps.Ops = append(res.ProofOps.Ops, commitInfo.ProofOp(storeInfo.Name)) - } - return res + return rs.doProofsQuery(req) } store := rs.getStoreByName(firstPath) @@ -1026,6 +1011,24 @@ type storeParams struct { initialVersion uint64 } +func (rs *Store) doProofsQuery(req abci.RequestQuery) abci.ResponseQuery { + commitInfo, err := getCommitInfo(rs.db, req.Height) + if err != nil { + return sdkerrors.QueryResult(err) + } + res := abci.ResponseQuery{ + Height: req.Height, + Key: []byte(proofsPath), + Value: commitInfo.CommitID().Hash, + ProofOps: &crypto.ProofOps{Ops: make([]crypto.ProofOp, 0, len(commitInfo.StoreInfos))}, + } + + for _, storeInfo := range commitInfo.StoreInfos { + res.ProofOps.Ops = append(res.ProofOps.Ops, commitInfo.ProofOp(storeInfo.Name)) + } + return res +} + func getLatestVersion(db dbm.DB) int64 { bz, err := db.Get([]byte(latestVersionKey)) if err != nil { From 6a728ed615f828da77ceb04552a754c4de320498 Mon Sep 17 00:00:00 2001 From: Roman Akhtariev Date: Fri, 25 Mar 2022 18:17:30 +0000 Subject: [PATCH 3/3] update multistore Query comment --- store/rootmulti/store.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/store/rootmulti/store.go b/store/rootmulti/store.go index fa942ddf536e..dd22e7432516 100644 --- a/store/rootmulti/store.go +++ b/store/rootmulti/store.go @@ -567,6 +567,9 @@ func (rs *Store) getStoreByName(name string) types.Store { // Query calls substore.Query with the same `req` where `req.Path` is // modified to remove the substore prefix. // Ie. `req.Path` here is `//`, and trimmed to `/` for the substore. +// Special case: if `req.Path` is `/proofs`, the commit hash is included +// as response value. In addition, proofs of every store are appended to the response for +// the requested height func (rs *Store) Query(req abci.RequestQuery) abci.ResponseQuery { path := req.Path firstPath, subpath, err := parsePath(path)