diff --git a/CHANGELOG.md b/CHANGELOG.md index 77b4ffba89..1ca0189666 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,6 +28,8 @@ FEATURES: * New genesis account keys are automatically added to the client keybase (introduce `--client-home` flag) * Initialize with genesis txs using `--gen-txs` flag * Context now has access to the application-configured logger +* Add (non-proof) subspace query helper functions +* Add more staking query functions: candidates, delegator-bonds BUG FIXES * Gaia now uses stake, ported from github.com/cosmos/gaia diff --git a/client/context/helpers.go b/client/context/helpers.go index 233dbcc2b8..c3dc0a4abb 100644 --- a/client/context/helpers.go +++ b/client/context/helpers.go @@ -43,8 +43,23 @@ func (ctx CoreContext) BroadcastTx(tx []byte) (*ctypes.ResultBroadcastTxCommit, // Query from Tendermint with the provided key and storename func (ctx CoreContext) Query(key cmn.HexBytes, storeName string) (res []byte, err error) { + return ctx.query(key, storeName, "key") +} - path := fmt.Sprintf("/%s/key", storeName) +// Query from Tendermint with the provided storename and subspace +func (ctx CoreContext) QuerySubspace(cdc *wire.Codec, subspace []byte, storeName string) (res []sdk.KVPair, err error) { + resRaw, err := ctx.query(subspace, storeName, "subspace") + if err != nil { + return res, err + } + cdc.MustUnmarshalBinary(resRaw, &res) + return +} + +// Query from Tendermint with the provided storename and path +func (ctx CoreContext) query(key cmn.HexBytes, storeName, endPath string) (res []byte, err error) { + + path := fmt.Sprintf("/%s/%s", storeName, endPath) node, err := ctx.GetNode() if err != nil { return res, err diff --git a/cmd/gaia/cmd/gaiacli/main.go b/cmd/gaia/cmd/gaiacli/main.go index 93d6b57b91..8de2e3acc2 100644 --- a/cmd/gaia/cmd/gaiacli/main.go +++ b/cmd/gaia/cmd/gaiacli/main.go @@ -46,7 +46,7 @@ func main() { client.GetCommands( authcmd.GetAccountCmd("acc", cdc, authcmd.GetAccountDecoder(cdc)), stakecmd.GetCmdQueryCandidate("stake", cdc), - //stakecmd.GetCmdQueryCandidates("stake", cdc), + stakecmd.GetCmdQueryCandidates("stake", cdc), stakecmd.GetCmdQueryDelegatorBond("stake", cdc), //stakecmd.GetCmdQueryDelegatorBonds("stake", cdc), )...) diff --git a/store/iavlstore.go b/store/iavlstore.go index de32e27e1e..5399b3d5c4 100644 --- a/store/iavlstore.go +++ b/store/iavlstore.go @@ -176,7 +176,16 @@ func (st *iavlStore) Query(req abci.RequestQuery) (res abci.ResponseQuery) { } else { _, res.Value = tree.GetVersioned(key, height) } - + case "/subspace": + subspace := req.Data + res.Key = subspace + var KVs []KVPair + iterator := st.SubspaceIterator(subspace) + for ; iterator.Valid(); iterator.Next() { + KVs = append(KVs, KVPair{iterator.Key(), iterator.Value()}) + } + iterator.Close() + res.Value = cdc.MustMarshalBinary(KVs) default: msg := fmt.Sprintf("Unexpected Query path: %v", req.Path) return sdk.ErrUnknownRequest(msg).QueryResult() diff --git a/store/iavlstore_test.go b/store/iavlstore_test.go index 4557dea06f..32bc1ebe0b 100644 --- a/store/iavlstore_test.go +++ b/store/iavlstore_test.go @@ -263,19 +263,40 @@ func TestIAVLStoreQuery(t *testing.T) { tree := iavl.NewVersionedTree(db, cacheSize) iavlStore := newIAVLStore(tree, numHistory) - k, v := []byte("wind"), []byte("blows") - k2, v2 := []byte("water"), []byte("flows") - v3 := []byte("is cold") - // k3, v3 := []byte("earth"), []byte("soes") - // k4, v4 := []byte("fire"), []byte("woes") + k1, v1 := []byte("key1"), []byte("val1") + k2, v2 := []byte("key2"), []byte("val2") + v3 := []byte("val3") + + ksub := []byte("key") + KVs0 := []KVPair{} + KVs1 := []KVPair{ + {k1, v1}, + {k2, v2}, + } + KVs2 := []KVPair{ + {k1, v3}, + {k2, v2}, + } + valExpSubEmpty := cdc.MustMarshalBinary(KVs0) + valExpSub1 := cdc.MustMarshalBinary(KVs1) + valExpSub2 := cdc.MustMarshalBinary(KVs2) cid := iavlStore.Commit() ver := cid.Version - query := abci.RequestQuery{Path: "/key", Data: k, Height: ver} + query := abci.RequestQuery{Path: "/key", Data: k1, Height: ver} + querySub := abci.RequestQuery{Path: "/subspace", Data: ksub, Height: ver} + + // query subspace before anything set + qres := iavlStore.Query(querySub) + assert.Equal(t, uint32(sdk.CodeOK), qres.Code) + assert.Equal(t, valExpSubEmpty, qres.Value) + + // set data + iavlStore.Set(k1, v1) + iavlStore.Set(k2, v2) // set data without commit, doesn't show up - iavlStore.Set(k, v) - qres := iavlStore.Query(query) + qres = iavlStore.Query(query) assert.Equal(t, uint32(sdk.CodeOK), qres.Code) assert.Nil(t, qres.Value) @@ -289,17 +310,21 @@ func TestIAVLStoreQuery(t *testing.T) { query.Height = cid.Version qres = iavlStore.Query(query) assert.Equal(t, uint32(sdk.CodeOK), qres.Code) - assert.Equal(t, v, qres.Value) + assert.Equal(t, v1, qres.Value) + + // and for the subspace + qres = iavlStore.Query(querySub) + assert.Equal(t, uint32(sdk.CodeOK), qres.Code) + assert.Equal(t, valExpSub1, qres.Value) // modify - iavlStore.Set(k2, v2) - iavlStore.Set(k, v3) + iavlStore.Set(k1, v3) cid = iavlStore.Commit() // query will return old values, as height is fixed qres = iavlStore.Query(query) assert.Equal(t, uint32(sdk.CodeOK), qres.Code) - assert.Equal(t, v, qres.Value) + assert.Equal(t, v1, qres.Value) // update to latest in the query and we are happy query.Height = cid.Version @@ -310,10 +335,14 @@ func TestIAVLStoreQuery(t *testing.T) { qres = iavlStore.Query(query2) assert.Equal(t, uint32(sdk.CodeOK), qres.Code) assert.Equal(t, v2, qres.Value) + // and for the subspace + qres = iavlStore.Query(querySub) + assert.Equal(t, uint32(sdk.CodeOK), qres.Code) + assert.Equal(t, valExpSub2, qres.Value) // default (height 0) will show latest -1 - query0 := abci.RequestQuery{Path: "/store", Data: k} + query0 := abci.RequestQuery{Path: "/store", Data: k1} qres = iavlStore.Query(query0) assert.Equal(t, uint32(sdk.CodeOK), qres.Code) - assert.Equal(t, v, qres.Value) + assert.Equal(t, v1, qres.Value) } diff --git a/store/types.go b/store/types.go index ca43dab6bc..e232e6ec71 100644 --- a/store/types.go +++ b/store/types.go @@ -13,6 +13,7 @@ type MultiStore = types.MultiStore type CacheMultiStore = types.CacheMultiStore type CommitMultiStore = types.CommitMultiStore type KVStore = types.KVStore +type KVPair = types.KVPair type Iterator = types.Iterator type CacheKVStore = types.CacheKVStore type CommitKVStore = types.CommitKVStore diff --git a/types/store.go b/types/store.go index 7b73570cac..f8367a1260 100644 --- a/types/store.go +++ b/types/store.go @@ -4,6 +4,7 @@ import ( "fmt" abci "github.com/tendermint/abci/types" + cmn "github.com/tendermint/tmlibs/common" dbm "github.com/tendermint/tmlibs/db" ) @@ -256,3 +257,8 @@ func PrefixEndBytes(prefix []byte) []byte { } return end } + +//---------------------------------------- + +// key-value result for iterator queries +type KVPair cmn.KVPair diff --git a/x/stake/client/cli/query.go b/x/stake/client/cli/query.go index 145333e486..8a5a06a709 100644 --- a/x/stake/client/cli/query.go +++ b/x/stake/client/cli/query.go @@ -15,42 +15,6 @@ import ( "github.com/cosmos/cosmos-sdk/x/stake" ) -//// create command to query for all candidates -//func GetCmdQueryCandidates(storeName string, cdc *wire.Codec) *cobra.Command { -//cmd := &cobra.Command{ -//Use: "candidates", -//Short: "Query for the set of validator-candidates pubkeys", -//RunE: func(cmd *cobra.Command, args []string) error { - -//key := stake.CandidatesKey - -//ctx := context.NewCoreContextFromViper() -//res, err := ctx.Query(key, storeName) -//if err != nil { -//return err -//} - -//// parse out the candidates -//candidates := new(stake.Candidates) -//err = cdc.UnmarshalBinary(res, candidates) -//if err != nil { -//return err -//} -//output, err := wire.MarshalJSONIndent(cdc, candidates) -//if err != nil { -//return err -//} -//fmt.Println(string(output)) -//return nil - -//// TODO output with proofs / machine parseable etc. -//}, -//} - -//cmd.Flags().AddFlagSet(fsDelegator) -//return cmd -//} - // get the command to query a candidate func GetCmdQueryCandidate(storeName string, cdc *wire.Codec) *cobra.Command { cmd := &cobra.Command{ @@ -64,9 +28,7 @@ func GetCmdQueryCandidate(storeName string, cdc *wire.Codec) *cobra.Command { } key := stake.GetCandidateKey(addr) - ctx := context.NewCoreContextFromViper() - res, err := ctx.Query(key, storeName) if err != nil { return err @@ -74,10 +36,7 @@ func GetCmdQueryCandidate(storeName string, cdc *wire.Codec) *cobra.Command { // parse out the candidate candidate := new(stake.Candidate) - err = cdc.UnmarshalBinary(res, candidate) - if err != nil { - return err - } + cdc.MustUnmarshalBinary(res, candidate) output, err := wire.MarshalJSONIndent(cdc, candidate) if err != nil { return err @@ -93,6 +52,41 @@ func GetCmdQueryCandidate(storeName string, cdc *wire.Codec) *cobra.Command { return cmd } +// get the command to query a candidate +func GetCmdQueryCandidates(storeName string, cdc *wire.Codec) *cobra.Command { + cmd := &cobra.Command{ + Use: "candidates", + Short: "Query for all validator-candidate accounts", + RunE: func(cmd *cobra.Command, args []string) error { + + key := stake.CandidatesKey + ctx := context.NewCoreContextFromViper() + resKVs, err := ctx.QuerySubspace(cdc, key, storeName) + if err != nil { + return err + } + + // parse out the candidates + var candidates []stake.Candidate + for _, KV := range resKVs { + var candidate stake.Candidate + cdc.MustUnmarshalBinary(KV.Value, &candidate) + candidates = append(candidates, candidate) + } + + output, err := wire.MarshalJSONIndent(cdc, candidates) + if err != nil { + return err + } + fmt.Println(string(output)) + return nil + + // TODO output with proofs / machine parseable etc. + }, + } + return cmd +} + // get the command to query a single delegator bond func GetCmdQueryDelegatorBond(storeName string, cdc *wire.Codec) *cobra.Command { cmd := &cobra.Command{ @@ -112,9 +106,7 @@ func GetCmdQueryDelegatorBond(storeName string, cdc *wire.Codec) *cobra.Command delegator := crypto.Address(bz) key := stake.GetDelegatorBondKey(delegator, addr, cdc) - ctx := context.NewCoreContextFromViper() - res, err := ctx.Query(key, storeName) if err != nil { return err @@ -122,10 +114,7 @@ func GetCmdQueryDelegatorBond(storeName string, cdc *wire.Codec) *cobra.Command // parse out the bond bond := new(stake.DelegatorBond) - err = cdc.UnmarshalBinary(res, bond) - if err != nil { - return err - } + cdc.MustUnmarshalBinary(res, bond) output, err := wire.MarshalJSONIndent(cdc, bond) if err != nil { return err @@ -142,44 +131,42 @@ func GetCmdQueryDelegatorBond(storeName string, cdc *wire.Codec) *cobra.Command return cmd } -//// get the command to query all the candidates bonded to a delegator -//func GetCmdQueryDelegatorBonds(storeName string, cdc *wire.Codec) *cobra.Command { -//cmd := &cobra.Command{ -//Use: "delegator-candidates", -//Short: "Query all delegators bond's candidate-addresses based on delegator-address", -//RunE: func(cmd *cobra.Command, args []string) error { +// get the command to query all the candidates bonded to a delegator +func GetCmdQueryDelegatorBonds(storeName string, cdc *wire.Codec) *cobra.Command { + cmd := &cobra.Command{ + Use: "delegator-candidates", + Short: "Query all delegators bonds based on delegator-address", + RunE: func(cmd *cobra.Command, args []string) error { -//bz, err := hex.DecodeString(viper.GetString(FlagAddressDelegator)) -//if err != nil { -//return err -//} -//delegator := crypto.Address(bz) + delegatorAddr, err := sdk.GetAddress(viper.GetString(FlagAddressDelegator)) + if err != nil { + return err + } + key := stake.GetDelegatorBondsKey(delegatorAddr, cdc) + ctx := context.NewCoreContextFromViper() + resKVs, err := ctx.QuerySubspace(cdc, key, storeName) + if err != nil { + return err + } -//key := stake.GetDelegatorBondsKey(delegator, cdc) + // parse out the candidates + var delegators []stake.DelegatorBond + for _, KV := range resKVs { + var delegator stake.DelegatorBond + cdc.MustUnmarshalBinary(KV.Value, &delegator) + delegators = append(delegators, delegator) + } -//ctx := context.NewCoreContextFromViper() + output, err := wire.MarshalJSONIndent(cdc, delegators) + if err != nil { + return err + } + fmt.Println(string(output)) + return nil -//res, err := ctx.Query(key, storeName) -//if err != nil { -//return err -//} - -//// parse out the candidates list -//var candidates []crypto.PubKey -//err = cdc.UnmarshalBinary(res, candidates) -//if err != nil { -//return err -//} -//output, err := wire.MarshalJSONIndent(cdc, candidates) -//if err != nil { -//return err -//} -//fmt.Println(string(output)) -//return nil - -//// TODO output with proofs / machine parseable etc. -//}, -//} -//cmd.Flags().AddFlagSet(fsDelegator) -//return cmd -//} + // TODO output with proofs / machine parseable etc. + }, + } + cmd.Flags().AddFlagSet(fsDelegator) + return cmd +} diff --git a/x/stake/client/cli/tx.go b/x/stake/client/cli/tx.go index 091701a700..d4f97fd526 100644 --- a/x/stake/client/cli/tx.go +++ b/x/stake/client/cli/tx.go @@ -22,6 +22,8 @@ func GetCmdDeclareCandidacy(cdc *wire.Codec) *cobra.Command { Use: "declare-candidacy", Short: "create new validator-candidate account and delegate some coins to it", RunE: func(cmd *cobra.Command, args []string) error { + ctx := context.NewCoreContextFromViper().WithDecoder(authcmd.GetAccountDecoder(cdc)) + amount, err := sdk.ParseCoin(viper.GetString(FlagAmount)) if err != nil { return err @@ -56,8 +58,6 @@ func GetCmdDeclareCandidacy(cdc *wire.Codec) *cobra.Command { msg := stake.NewMsgDeclareCandidacy(candidateAddr, pk, amount, description) // build and sign the transaction, then broadcast to Tendermint - ctx := context.NewCoreContextFromViper().WithDecoder(authcmd.GetAccountDecoder(cdc)) - res, err := ctx.EnsureSignBuildBroadcast(ctx.FromAddressName, msg, cdc) if err != nil { return err diff --git a/x/stake/msg.go b/x/stake/msg.go index dd8d3714fd..4e322e6402 100644 --- a/x/stake/msg.go +++ b/x/stake/msg.go @@ -4,6 +4,7 @@ import ( "encoding/json" sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/wire" crypto "github.com/tendermint/go-crypto" ) @@ -19,6 +20,12 @@ const StakingToken = "steak" //Verify interface at compile time var _, _, _, _ sdk.Msg = &MsgDeclareCandidacy{}, &MsgEditCandidacy{}, &MsgDelegate{}, &MsgUnbond{} +var msgCdc = wire.NewCodec() + +func init() { + wire.RegisterCrypto(msgCdc) +} + //______________________________________________________________________ // MsgDeclareCandidacy - struct for unbonding transactions @@ -45,11 +52,7 @@ func (msg MsgDeclareCandidacy) GetSigners() []sdk.Address { return []sdk.Address // get the bytes for the message signer to sign on func (msg MsgDeclareCandidacy) GetSignBytes() []byte { - b, err := json.Marshal(msg) - if err != nil { - panic(err) - } - return b + return msgCdc.MustMarshalBinary(msg) } // quick validity check