-
Notifications
You must be signed in to change notification settings - Fork 3.6k
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
Bianjie/lcd implementation #2118
Changes from 21 commits
6aa8309
21e36b5
22161af
4e61859
170daf3
34548db
eccb889
2047673
85f47d2
8e0ca8d
60acd5c
187dda4
22495e9
26af936
4a0d1c5
4607ca9
7528534
473fe21
95e367a
9d944b0
fc40419
f266472
6cdf21d
79ec713
9409e73
9ff57af
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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" | ||
|
@@ -30,6 +31,8 @@ type CLIContext struct { | |
Async bool | ||
JSON bool | ||
PrintResponse bool | ||
Cert tendermintLite.Certifier | ||
ClientMgr *ClientManager | ||
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.
|
||
} | ||
|
||
// NewCLIContext returns a new initialized CLIContext with parameters from the | ||
|
@@ -113,3 +116,15 @@ func (ctx CLIContext) WithUseLedger(useLedger bool) CLIContext { | |
ctx.UseLedger = useLedger | ||
return ctx | ||
} | ||
|
||
// WithCert - return a copy of the context with an updated Cert | ||
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. Use Tendermint Lint to avoid verbose comments. You won't need to add the function name at the beginning of the comment with it |
||
func (ctx CLIContext) WithCert(cert tendermintLite.Certifier) CLIContext { | ||
ctx.Cert = cert | ||
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. change |
||
return ctx | ||
} | ||
|
||
// WithClientMgr - return a copy of the context with an updated ClientMgr | ||
func (ctx CLIContext) WithClientMgr(clientMgr *ClientManager) CLIContext { | ||
ctx.ClientMgr = clientMgr | ||
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. change |
||
return ctx | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
package context | ||
|
||
import ( | ||
rpcclient "github.com/tendermint/tendermint/rpc/client" | ||
"strings" | ||
"sync" | ||
"github.com/pkg/errors" | ||
) | ||
|
||
// 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.RWMutex | ||
} | ||
|
||
// 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 { | ||
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.
|
||
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 | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
package context | ||
|
||
import ( | ||
"testing" | ||
"github.com/stretchr/testify/assert" | ||
) | ||
|
||
func TestLoadBalancing(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()) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -14,11 +14,18 @@ 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" | ||
) | ||
|
||
// GetNode returns an RPC client. If the context's client is not defined, an | ||
// error is returned. | ||
func (ctx CLIContext) GetNode() (rpcclient.Client, error) { | ||
if ctx.ClientMgr != nil { | ||
return ctx.ClientMgr.getClient(), nil | ||
} | ||
if ctx.Client == nil { | ||
return nil, errors.New("no RPC client defined") | ||
} | ||
|
@@ -277,6 +284,7 @@ func (ctx CLIContext) ensureBroadcastTx(txBytes []byte) error { | |
return nil | ||
} | ||
|
||
// nolint: gocyclo | ||
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. refactor the function and split it into smaller ones instead |
||
// query performs a query from a Tendermint node with the provided store name | ||
// and path. | ||
func (ctx CLIContext) query(path string, key common.HexBytes) (res []byte, err error) { | ||
|
@@ -296,10 +304,46 @@ func (ctx CLIContext) query(path string, key common.HexBytes) (res []byte, err e | |
} | ||
|
||
resp := result.Response | ||
if !resp.IsOK() { | ||
if resp.Code != uint32(0) { | ||
return res, errors.Errorf("query failed: (%d) %s", resp.Code, resp.Log) | ||
} | ||
|
||
// Data from trusted node or subspace doesn't need verification | ||
if ctx.TrustNode || !isQueryStoreWithProof(path) { | ||
return resp.Value,nil | ||
} | ||
|
||
// TODO: Later we consider to return error for missing valid certifier to verify data from untrusted node | ||
if ctx.Cert == nil { | ||
if ctx.Logger != nil { | ||
io.WriteString(ctx.Logger, fmt.Sprintf("Missing valid certifier to verify data from untrusted node\n")) | ||
} | ||
return resp.Value, nil | ||
} | ||
|
||
// AppHash for height H is in header H+1 | ||
commit, err := tendermintLiteProxy.GetCertifiedCommit(resp.Height+1, node, ctx.Cert) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
var multiStoreProof store.MultiStoreProof | ||
cdc := wire.NewCodec() | ||
err = cdc.UnmarshalBinary(resp.Proof, &multiStoreProof) | ||
if err != nil { | ||
return res, 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 nil, errors.Wrap(err, "failed in verifying the proof against appHash") | ||
} | ||
err = store.VerifyRangeProof(resp.Key, resp.Value, substoreCommitHash, &multiStoreProof.RangeProof) | ||
if err != nil { | ||
return nil, errors.Wrap(err, "failed in the range proof verification") | ||
} | ||
|
||
return resp.Value, nil | ||
} | ||
|
||
|
@@ -309,3 +353,21 @@ func (ctx CLIContext) queryStore(key cmn.HexBytes, storeName, endPath string) ([ | |
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 | ||
} | ||
// WARNING This should be consistent with query method in iavlstore.go | ||
if paths[2] == "store" || paths[2] == "key" { | ||
return true | ||
} | ||
return false | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
package httputils | ||
|
||
import ( | ||
"github.com/gin-gonic/gin" | ||
"net/http" | ||
) | ||
|
||
// NewError create error http response | ||
func NewError(ctx *gin.Context, errCode int, err error) { | ||
errorResponse := HTTPError{ | ||
API: "2.0", | ||
Code: errCode, | ||
} | ||
if err != nil { | ||
errorResponse.ErrMsg = err.Error() | ||
} | ||
|
||
ctx.JSON(errCode, errorResponse) | ||
} | ||
|
||
// NormalResponse create normal http response | ||
func NormalResponse(ctx *gin.Context, data []byte) { | ||
ctx.Status(http.StatusOK) | ||
ctx.Writer.Write(data) | ||
} | ||
|
||
// HTTPError is http response with error | ||
type HTTPError struct { | ||
API string `json:"rest api" example:"2.0"` | ||
Code int `json:"code" example:"500"` | ||
ErrMsg string `json:"error message"` | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -16,6 +16,9 @@ import ( | |
"github.com/cosmos/cosmos-sdk/crypto/keys" | ||
|
||
"github.com/tendermint/tendermint/libs/cli" | ||
"github.com/gin-gonic/gin" | ||
"github.com/cosmos/cosmos-sdk/client/httputils" | ||
"regexp/syntax" | ||
) | ||
|
||
const ( | ||
|
@@ -236,6 +239,85 @@ func AddNewKeyRequestHandler(w http.ResponseWriter, r *http.Request) { | |
w.Write(bz) | ||
} | ||
|
||
// AddNewKeyRequest is the handler of adding new key in swagger rest server | ||
// nolint: gocyclo | ||
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. same here. Refactor and split |
||
func AddNewKeyRequest(gtx *gin.Context) { | ||
var m NewKeyBody | ||
body, err := ioutil.ReadAll(gtx.Request.Body) | ||
if err != nil { | ||
httputils.NewError(gtx, http.StatusBadRequest, err) | ||
return | ||
} | ||
err = json.Unmarshal(body, &m) | ||
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. Use |
||
if err != nil { | ||
httputils.NewError(gtx, http.StatusBadRequest, err) | ||
return | ||
} | ||
|
||
if len(m.Name) < 1 || len(m.Name) > 16 { | ||
httputils.NewError(gtx, http.StatusBadRequest, fmt.Errorf("account name length should not be longer than 16")) | ||
return | ||
} | ||
for _, char := range []rune(m.Name) { | ||
if !syntax.IsWordChar(char) { | ||
httputils.NewError(gtx, http.StatusBadRequest, fmt.Errorf("account name should not contains any char beyond [_0-9A-Za-z]")) | ||
return | ||
} | ||
} | ||
if len(m.Password) < 8 || len(m.Password) > 16 { | ||
httputils.NewError(gtx, http.StatusBadRequest, fmt.Errorf("account password length should be no less than 8 and no greater than 16")) | ||
return | ||
} | ||
|
||
kb, err := GetKeyBase() | ||
if err != nil { | ||
httputils.NewError(gtx, http.StatusInternalServerError, err) | ||
return | ||
} | ||
|
||
// check if already exists | ||
infos, err := kb.List() | ||
if err != nil { | ||
httputils.NewError(gtx, http.StatusInternalServerError, err) | ||
return | ||
} | ||
|
||
for _, i := range infos { | ||
if i.GetName() == m.Name { | ||
httputils.NewError(gtx, http.StatusConflict, fmt.Errorf("account with name %s already exists", m.Name)) | ||
return | ||
} | ||
} | ||
|
||
// create account | ||
seed := m.Seed | ||
if seed == "" { | ||
seed = getSeed(keys.Secp256k1) | ||
} | ||
info, err := kb.CreateKey(m.Name, seed, m.Password) | ||
if err != nil { | ||
httputils.NewError(gtx, http.StatusInternalServerError, err) | ||
return | ||
} | ||
|
||
keyOutput, err := Bech32KeyOutput(info) | ||
if err != nil { | ||
httputils.NewError(gtx, http.StatusInternalServerError, err) | ||
return | ||
} | ||
|
||
keyOutput.Seed = seed | ||
|
||
bz, err := json.Marshal(keyOutput) | ||
if err != nil { | ||
httputils.NewError(gtx, http.StatusInternalServerError, err) | ||
return | ||
} | ||
|
||
httputils.NormalResponse(gtx, bz) | ||
|
||
} | ||
|
||
// function to just a new seed to display in the UI before actually persisting it in the keybase | ||
func getSeed(algo keys.SigningAlgo) string { | ||
kb := client.MockKeyBase() | ||
|
@@ -258,3 +340,13 @@ func SeedRequestHandler(w http.ResponseWriter, r *http.Request) { | |
seed := getSeed(algo) | ||
w.Write([]byte(seed)) | ||
} | ||
|
||
// SeedRequest is the handler of creating seed in swagger rest server | ||
func SeedRequest(gtx *gin.Context) { | ||
|
||
algo := keys.SigningAlgo("secp256k1") | ||
|
||
seed := getSeed(algo) | ||
|
||
httputils.NormalResponse(gtx, []byte(seed)) | ||
} |
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.
Certifier