IRISHUB-238: Add multiply store proof build and verification

This commit is contained in:
HaoyangLiu
2018-08-30 15:05:16 +08:00
parent fd8c1e5253
commit 703c643fc0
8 changed files with 401 additions and 0 deletions
+46
View File
@@ -0,0 +1,46 @@
package context
import (
"github.com/pkg/errors"
rpcclient "github.com/tendermint/tendermint/rpc/client"
"strings"
"sync"
)
// ClientManager is a manager of a set of rpc clients to full nodes.
// This manager can do load balancing upon these rpc clients.
type ClientManager struct {
clients []rpcclient.Client
currentIndex int
mutex sync.Mutex
}
// NewClientManager create a new ClientManager
func NewClientManager(nodeURIs string) (*ClientManager, error) {
if nodeURIs != "" {
nodeURLArray := strings.Split(nodeURIs, ",")
var clients []rpcclient.Client
for _, url := range nodeURLArray {
client := rpcclient.NewHTTP(url, "/websocket")
clients = append(clients, client)
}
mgr := &ClientManager{
currentIndex: 0,
clients: clients,
}
return mgr, nil
}
return nil, errors.New("missing node URIs")
}
func (mgr *ClientManager) getClient() rpcclient.Client {
mgr.mutex.Lock()
defer mgr.mutex.Unlock()
client := mgr.clients[mgr.currentIndex]
mgr.currentIndex++
if mgr.currentIndex >= len(mgr.clients) {
mgr.currentIndex = 0
}
return client
}
+16
View File
@@ -0,0 +1,16 @@
package context
import (
"github.com/stretchr/testify/assert"
"testing"
)
func TestClientManager(t *testing.T) {
nodeURIs := "10.10.10.10:26657,20.20.20.20:26657,30.30.30.30:26657"
clientMgr, err := NewClientManager(nodeURIs)
assert.Empty(t, err)
endpoint := clientMgr.getClient()
assert.NotEqual(t, endpoint, clientMgr.getClient())
clientMgr.getClient()
assert.Equal(t, endpoint, clientMgr.getClient())
}
+15
View File
@@ -10,6 +10,7 @@ import (
"github.com/spf13/viper"
rpcclient "github.com/tendermint/tendermint/rpc/client"
tendermintLite "github.com/tendermint/tendermint/lite"
)
const ctxAccStoreName = "acc"
@@ -32,6 +33,8 @@ type CLIContext struct {
Async bool
JSON bool
PrintResponse bool
Certifier tendermintLite.Certifier
ClientManager *ClientManager
}
// NewCLIContext returns a new initialized CLIContext with parameters from the
@@ -117,3 +120,15 @@ 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
}
// WithClientManager - return a copy of the context with an updated ClientManager
func (ctx CLIContext) WithClientManager(clientManager *ClientManager) CLIContext {
ctx.ClientManager = clientManager
return ctx
}
+79
View File
@@ -13,6 +13,11 @@ import (
cmn "github.com/tendermint/tendermint/libs/common"
rpcclient "github.com/tendermint/tendermint/rpc/client"
ctypes "github.com/tendermint/tendermint/rpc/core/types"
"github.com/cosmos/cosmos-sdk/store"
"github.com/cosmos/cosmos-sdk/wire"
"strings"
tendermintLiteProxy "github.com/tendermint/tendermint/lite/proxy"
abci "github.com/tendermint/tendermint/abci/types"
)
// 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
}
+14
View File
@@ -22,6 +22,9 @@ import (
cmn "github.com/tendermint/tendermint/libs/common"
"github.com/tendermint/tendermint/libs/log"
tmserver "github.com/tendermint/tendermint/rpc/lib/server"
tendermintLiteProxy "github.com/tendermint/tendermint/lite/proxy"
"github.com/tendermint/tendermint/libs/cli"
tendermintLite "github.com/tendermint/tendermint/lite"
)
// ServeCommand will generate a long-running rest server
@@ -80,6 +83,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")