Merge pull request #91 from HaoyangLiu/bianjie/lcd-proof-build-verfication
Split LCD implementation PR, part one
This commit is contained in:
commit
5a632afd2c
|
@ -9,6 +9,7 @@ import (
|
|||
|
||||
"github.com/spf13/viper"
|
||||
|
||||
tendermintLite "github.com/tendermint/tendermint/lite"
|
||||
rpcclient "github.com/tendermint/tendermint/rpc/client"
|
||||
)
|
||||
|
||||
|
@ -32,6 +33,7 @@ type CLIContext struct {
|
|||
Async bool
|
||||
JSON bool
|
||||
PrintResponse bool
|
||||
Certifier tendermintLite.Certifier
|
||||
}
|
||||
|
||||
// NewCLIContext returns a new initialized CLIContext with parameters from the
|
||||
|
@ -117,3 +119,9 @@ func (ctx CLIContext) WithUseLedger(useLedger bool) CLIContext {
|
|||
ctx.UseLedger = useLedger
|
||||
return ctx
|
||||
}
|
||||
|
||||
// WithCertifier - return a copy of the context with an updated Certifier
|
||||
func (ctx CLIContext) WithCertifier(certifier tendermintLite.Certifier) CLIContext {
|
||||
ctx.Certifier = certifier
|
||||
return ctx
|
||||
}
|
||||
|
|
|
@ -10,9 +10,14 @@ import (
|
|||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/store"
|
||||
"github.com/cosmos/cosmos-sdk/wire"
|
||||
abci "github.com/tendermint/tendermint/abci/types"
|
||||
cmn "github.com/tendermint/tendermint/libs/common"
|
||||
tendermintLiteProxy "github.com/tendermint/tendermint/lite/proxy"
|
||||
rpcclient "github.com/tendermint/tendermint/rpc/client"
|
||||
ctypes "github.com/tendermint/tendermint/rpc/core/types"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// GetNode returns an RPC client. If the context's client is not defined, an
|
||||
|
@ -304,12 +309,86 @@ func (ctx CLIContext) query(path string, key cmn.HexBytes) (res []byte, err erro
|
|||
return res, errors.Errorf("query failed: (%d) %s", resp.Code, resp.Log)
|
||||
}
|
||||
|
||||
// Data from trusted node or subspace query doesn't need verification
|
||||
if ctx.TrustNode || !isQueryStoreWithProof(path) {
|
||||
return resp.Value, nil
|
||||
}
|
||||
|
||||
err = ctx.verifyProof(path, resp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return resp.Value, nil
|
||||
}
|
||||
|
||||
// verifyProof perform response proof verification
|
||||
func (ctx CLIContext) verifyProof(path string, resp abci.ResponseQuery) error {
|
||||
|
||||
// TODO: Later we consider to return error for missing valid certifier to verify data from untrusted node
|
||||
if ctx.Certifier == nil {
|
||||
if ctx.Logger != nil {
|
||||
io.WriteString(ctx.Logger, fmt.Sprintf("Missing valid certifier to verify data from untrusted node\n"))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
node, err := ctx.GetNode()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// TODO: need improvement
|
||||
// If the the node http client connect to a full node which can't produce or receive new blocks,
|
||||
// then here the code will wait for a while and return error if time is out.
|
||||
// AppHash for height H is in header H+1
|
||||
commit, err := tendermintLiteProxy.GetCertifiedCommit(resp.Height+1, node, ctx.Certifier)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var multiStoreProof store.MultiStoreProof
|
||||
cdc := wire.NewCodec()
|
||||
err = cdc.UnmarshalBinary(resp.Proof, &multiStoreProof)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to unmarshalBinary rangeProof")
|
||||
}
|
||||
|
||||
// Validate the substore commit hash against trusted appHash
|
||||
substoreCommitHash, err := store.VerifyMultiStoreCommitInfo(multiStoreProof.StoreName,
|
||||
multiStoreProof.CommitIDList, commit.Header.AppHash)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed in verifying the proof against appHash")
|
||||
}
|
||||
err = store.VerifyRangeProof(resp.Key, resp.Value, substoreCommitHash, &multiStoreProof.RangeProof)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed in the range proof verification")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// queryStore performs a query from a Tendermint node with the provided a store
|
||||
// name and path.
|
||||
func (ctx CLIContext) queryStore(key cmn.HexBytes, storeName, endPath string) ([]byte, error) {
|
||||
path := fmt.Sprintf("/store/%s/%s", storeName, endPath)
|
||||
return ctx.query(path, key)
|
||||
}
|
||||
|
||||
// isQueryStoreWithProof expects a format like /<queryType>/<storeName>/<subpath>
|
||||
// queryType can be app or store
|
||||
// if subpath equals to "/store" or "/key", then return true
|
||||
func isQueryStoreWithProof(path string) bool {
|
||||
if !strings.HasPrefix(path, "/") {
|
||||
return false
|
||||
}
|
||||
paths := strings.SplitN(path[1:], "/", 3)
|
||||
if len(paths) != 3 {
|
||||
return false
|
||||
}
|
||||
// Currently, only when query subpath is "/store" or "/key", will proof be included in response.
|
||||
// If there are some changes about proof building in iavlstore.go, we must change code here to keep consistency with iavlstore.go
|
||||
if paths[2] == "store" || paths[2] == "key" {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
|
|
@ -19,8 +19,11 @@ import (
|
|||
"github.com/gorilla/mux"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
"github.com/tendermint/tendermint/libs/cli"
|
||||
cmn "github.com/tendermint/tendermint/libs/common"
|
||||
"github.com/tendermint/tendermint/libs/log"
|
||||
tendermintLite "github.com/tendermint/tendermint/lite"
|
||||
tendermintLiteProxy "github.com/tendermint/tendermint/lite/proxy"
|
||||
tmserver "github.com/tendermint/tendermint/rpc/lib/server"
|
||||
)
|
||||
|
||||
|
@ -66,6 +69,7 @@ func ServeCommand(cdc *wire.Codec) *cobra.Command {
|
|||
cmd.Flags().String(client.FlagChainID, "", "The chain ID to connect to")
|
||||
cmd.Flags().String(client.FlagNode, "tcp://localhost:26657", "Address of the node to connect to")
|
||||
cmd.Flags().Int(flagMaxOpenConnections, 1000, "The number of maximum open connections")
|
||||
cmd.Flags().Bool(client.FlagTrustNode, false, "Whether trust connected full node")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
@ -80,6 +84,17 @@ func createHandler(cdc *wire.Codec) http.Handler {
|
|||
|
||||
cliCtx := context.NewCLIContext().WithCodec(cdc).WithLogger(os.Stdout)
|
||||
|
||||
chainID := viper.GetString(client.FlagChainID)
|
||||
home := viper.GetString(cli.HomeFlag)
|
||||
nodeURI := viper.GetString(client.FlagNode)
|
||||
var certifier tendermintLite.Certifier
|
||||
if chainID != "" && home != "" && nodeURI != "" {
|
||||
certifier, err = tendermintLiteProxy.GetCertifier(chainID, home, nodeURI)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
cliCtx = cliCtx.WithCertifier(certifier)
|
||||
}
|
||||
// TODO: make more functional? aka r = keys.RegisterRoutes(r)
|
||||
r.HandleFunc("/version", CLIVersionRequestHandler).Methods("GET")
|
||||
r.HandleFunc("/node_version", NodeVersionRequestHandler(cliCtx)).Methods("GET")
|
||||
|
|
|
@ -37,6 +37,7 @@ import (
|
|||
"github.com/tendermint/tendermint/proxy"
|
||||
tmrpc "github.com/tendermint/tendermint/rpc/lib/server"
|
||||
tmtypes "github.com/tendermint/tendermint/types"
|
||||
"time"
|
||||
)
|
||||
|
||||
// makePathname creates a unique pathname for each test. It will panic if it
|
||||
|
@ -190,6 +191,7 @@ func InitializeTestLCD(t *testing.T, nValidators int, initAddrs []sdk.AccAddress
|
|||
node, err := startTM(config, logger, genDoc, privVal, app)
|
||||
require.NoError(t, err)
|
||||
|
||||
time.Sleep(3 * time.Second)
|
||||
lcd, err := startLCD(logger, listenAddr, cdc)
|
||||
require.NoError(t, err)
|
||||
|
||||
|
|
|
@ -1,15 +1,15 @@
|
|||
package server
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/cosmos/cosmos-sdk/wire"
|
||||
"github.com/tendermint/tendermint/libs/log"
|
||||
tcmd "github.com/tendermint/tendermint/cmd/tendermint/commands"
|
||||
"os"
|
||||
"bytes"
|
||||
"io"
|
||||
"github.com/cosmos/cosmos-sdk/server/mock"
|
||||
"github.com/cosmos/cosmos-sdk/wire"
|
||||
"github.com/stretchr/testify/require"
|
||||
tcmd "github.com/tendermint/tendermint/cmd/tendermint/commands"
|
||||
"github.com/tendermint/tendermint/libs/log"
|
||||
"io"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestEmptyState(t *testing.T) {
|
||||
|
|
|
@ -0,0 +1,116 @@
|
|||
package store
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/tendermint/iavl"
|
||||
cmn "github.com/tendermint/tendermint/libs/common"
|
||||
)
|
||||
|
||||
// commitID of substores, such as acc store, gov store
|
||||
type SubstoreCommitID struct {
|
||||
Name string `json:"name"`
|
||||
Version int64 `json:"version"`
|
||||
CommitHash cmn.HexBytes `json:"commit_hash"`
|
||||
}
|
||||
|
||||
// proof of store which have multi substores
|
||||
type MultiStoreProof struct {
|
||||
CommitIDList []SubstoreCommitID `json:"commit_id_list"`
|
||||
StoreName string `json:"store_name"`
|
||||
RangeProof iavl.RangeProof `json:"range_proof"`
|
||||
}
|
||||
|
||||
// build MultiStoreProof based on iavl proof and storeInfos
|
||||
func BuildMultiStoreProof(iavlProof []byte, storeName string, storeInfos []storeInfo) ([]byte, error) {
|
||||
var rangeProof iavl.RangeProof
|
||||
err := cdc.UnmarshalBinary(iavlProof, &rangeProof)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var multiStoreProof MultiStoreProof
|
||||
for _, storeInfo := range storeInfos {
|
||||
|
||||
commitID := SubstoreCommitID{
|
||||
Name: storeInfo.Name,
|
||||
Version: storeInfo.Core.CommitID.Version,
|
||||
CommitHash: storeInfo.Core.CommitID.Hash,
|
||||
}
|
||||
multiStoreProof.CommitIDList = append(multiStoreProof.CommitIDList, commitID)
|
||||
}
|
||||
multiStoreProof.StoreName = storeName
|
||||
multiStoreProof.RangeProof = rangeProof
|
||||
|
||||
proof, err := cdc.MarshalBinary(multiStoreProof)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return proof, nil
|
||||
}
|
||||
|
||||
// verify multiStoreCommitInfo against appHash
|
||||
func VerifyMultiStoreCommitInfo(storeName string, multiStoreCommitInfo []SubstoreCommitID, appHash []byte) ([]byte, error) {
|
||||
var substoreCommitHash []byte
|
||||
var storeInfos []storeInfo
|
||||
var height int64
|
||||
for _, multiStoreCommitID := range multiStoreCommitInfo {
|
||||
|
||||
if multiStoreCommitID.Name == storeName {
|
||||
substoreCommitHash = multiStoreCommitID.CommitHash
|
||||
height = multiStoreCommitID.Version
|
||||
}
|
||||
storeInfo := storeInfo{
|
||||
Name: multiStoreCommitID.Name,
|
||||
Core: storeCore{
|
||||
CommitID: sdk.CommitID{
|
||||
Version: multiStoreCommitID.Version,
|
||||
Hash: multiStoreCommitID.CommitHash,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
storeInfos = append(storeInfos, storeInfo)
|
||||
}
|
||||
if len(substoreCommitHash) == 0 {
|
||||
return nil, cmn.NewError("failed to get substore root commit hash by store name")
|
||||
}
|
||||
|
||||
ci := commitInfo{
|
||||
Version: height,
|
||||
StoreInfos: storeInfos,
|
||||
}
|
||||
|
||||
if !bytes.Equal(appHash, ci.Hash()) {
|
||||
return nil, cmn.NewError("the merkle root of multiStoreCommitInfo doesn't equal to appHash")
|
||||
}
|
||||
return substoreCommitHash, nil
|
||||
}
|
||||
|
||||
// verify iavl proof
|
||||
func VerifyRangeProof(key, value []byte, substoreCommitHash []byte, rangeProof *iavl.RangeProof) error {
|
||||
|
||||
// Validate the proof to ensure data integrity.
|
||||
err := rangeProof.Verify(substoreCommitHash)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "proof root hash doesn't equal to substore commit root hash")
|
||||
}
|
||||
|
||||
if len(value) != 0 {
|
||||
// Validate existence proof
|
||||
err = rangeProof.VerifyItem(key, value)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed in existence verification")
|
||||
}
|
||||
} else {
|
||||
// Validate absence proof
|
||||
err = rangeProof.VerifyAbsence(key)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed in absence verification")
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,96 @@
|
|||
package store
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/tendermint/iavl"
|
||||
cmn "github.com/tendermint/tendermint/libs/common"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestVerifyMultiStoreCommitInfo(t *testing.T) {
|
||||
appHash, _ := hex.DecodeString("ebf3c1fb724d3458023c8fefef7b33add2fc1e84")
|
||||
|
||||
substoreRootHash, _ := hex.DecodeString("ea5d468431015c2cd6295e9a0bb1fc0e49033828")
|
||||
storeName := "acc"
|
||||
|
||||
var multiStoreCommitInfo []SubstoreCommitID
|
||||
|
||||
gocRootHash, _ := hex.DecodeString("62c171bb022e47d1f745608ff749e676dbd25f78")
|
||||
multiStoreCommitInfo = append(multiStoreCommitInfo, SubstoreCommitID{
|
||||
Name: "gov",
|
||||
Version: 689,
|
||||
CommitHash: gocRootHash,
|
||||
})
|
||||
|
||||
multiStoreCommitInfo = append(multiStoreCommitInfo, SubstoreCommitID{
|
||||
Name: "main",
|
||||
Version: 689,
|
||||
CommitHash: nil,
|
||||
})
|
||||
|
||||
accRootHash, _ := hex.DecodeString("ea5d468431015c2cd6295e9a0bb1fc0e49033828")
|
||||
multiStoreCommitInfo = append(multiStoreCommitInfo, SubstoreCommitID{
|
||||
Name: "acc",
|
||||
Version: 689,
|
||||
CommitHash: accRootHash,
|
||||
})
|
||||
|
||||
multiStoreCommitInfo = append(multiStoreCommitInfo, SubstoreCommitID{
|
||||
Name: "ibc",
|
||||
Version: 689,
|
||||
CommitHash: nil,
|
||||
})
|
||||
|
||||
stakeRootHash, _ := hex.DecodeString("987d1d27b8771d93aa3691262f661d2c85af7ca4")
|
||||
multiStoreCommitInfo = append(multiStoreCommitInfo, SubstoreCommitID{
|
||||
Name: "stake",
|
||||
Version: 689,
|
||||
CommitHash: stakeRootHash,
|
||||
})
|
||||
|
||||
slashingRootHash, _ := hex.DecodeString("388ee6e5b11f367069beb1eefd553491afe9d73e")
|
||||
multiStoreCommitInfo = append(multiStoreCommitInfo, SubstoreCommitID{
|
||||
Name: "slashing",
|
||||
Version: 689,
|
||||
CommitHash: slashingRootHash,
|
||||
})
|
||||
|
||||
commitHash, err := VerifyMultiStoreCommitInfo(storeName, multiStoreCommitInfo, appHash)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, commitHash, substoreRootHash)
|
||||
|
||||
appHash, _ = hex.DecodeString("29de216bf5e2531c688de36caaf024cd3bb09ee3")
|
||||
|
||||
_, err = VerifyMultiStoreCommitInfo(storeName, multiStoreCommitInfo, appHash)
|
||||
assert.Error(t, err, "appHash doesn't match to the merkle root of multiStoreCommitInfo")
|
||||
}
|
||||
|
||||
func TestVerifyRangeProof(t *testing.T) {
|
||||
tree := iavl.NewTree(nil, 0)
|
||||
|
||||
rand := cmn.NewRand()
|
||||
rand.Seed(0) // for determinism
|
||||
for _, ikey := range []byte{0x11, 0x32, 0x50, 0x72, 0x99} {
|
||||
key := []byte{ikey}
|
||||
tree.Set(key, []byte(rand.Str(8)))
|
||||
}
|
||||
|
||||
root := tree.Hash()
|
||||
|
||||
key := []byte{0x32}
|
||||
val, proof, err := tree.GetWithProof(key)
|
||||
assert.Nil(t, err)
|
||||
assert.NotEmpty(t, val)
|
||||
assert.NotEmpty(t, proof)
|
||||
err = VerifyRangeProof(key, val, root, proof)
|
||||
assert.Nil(t, err)
|
||||
|
||||
key = []byte{0x40}
|
||||
val, proof, err = tree.GetWithProof(key)
|
||||
assert.Nil(t, err)
|
||||
assert.Empty(t, val)
|
||||
assert.NotEmpty(t, proof)
|
||||
err = VerifyRangeProof(key, val, root, proof)
|
||||
assert.Nil(t, err)
|
||||
}
|
|
@ -291,6 +291,24 @@ func (rs *rootMultiStore) Query(req abci.RequestQuery) abci.ResponseQuery {
|
|||
// trim the path and make the query
|
||||
req.Path = subpath
|
||||
res := queryable.Query(req)
|
||||
|
||||
// Currently, only when query subpath is "/store" or "/key", will proof be included in response.
|
||||
// If there are some changes about proof building in iavlstore.go, we must change code here to keep consistency with iavlstore.go
|
||||
if !req.Prove || subpath != "/store" && subpath != "/key" {
|
||||
return res
|
||||
}
|
||||
|
||||
//Load commit info from db
|
||||
commitInfo, errMsg := getCommitInfo(rs.db, res.Height)
|
||||
if errMsg != nil {
|
||||
return sdk.ErrInternal(errMsg.Error()).QueryResult()
|
||||
}
|
||||
|
||||
res.Proof, errMsg = BuildMultiStoreProof(res.Proof, storeName, commitInfo.StoreInfos)
|
||||
if errMsg != nil {
|
||||
return sdk.ErrInternal(errMsg.Error()).QueryResult()
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue