Skip to content
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

Query old blocks for proposals in CLI #598

Merged
merged 9 commits into from
Jun 29, 2020
5 changes: 4 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,8 @@ Ref: https://keepachangelog.com/en/1.0.0/
[\#584](https://github.com/Kava-Labs/kava/pulls/584) Add REST client and CLI queries for `kavadist` module

[\#578](https://github.com/Kava-Labs/kava/pulls/578) Add v0.3 compatible REST client that supports
```

```plaintext
/v0_3/node_info
/v0_3/auth/accounts/<address>
/v0_3/<hash>
Expand All @@ -55,6 +56,8 @@ Ref: https://keepachangelog.com/en/1.0.0/
/v0_3/distribution/delegators/<address>/rewards
```

[\#598](https://github.com/Kava-Labs/kava/pulls/598) CLI and REST queries for committee proposals (ie `kvcli q committee proposal 1`) now query the historical state to return the proposal object before it was deleted from state

## [v0.8.1](https://github.com/Kava-Labs/kava/releases/tag/v0.8.1) kava-3 Patch Release

This version mitigates a memory leak in tendermint that was found prior to launching kava-3. It is fully compatible with v0.8.0 and is intended to replace that version as the canonical software version for upgrading the Kava mainnet from kava-2 to kava-3. Note that there are no breaking changes between the versions, but a safety check was added to this version to prevent starting the node with an unsafe configuration.
Expand Down
10 changes: 1 addition & 9 deletions x/committee/client/cli/query.go
Original file line number Diff line number Diff line change
Expand Up @@ -130,20 +130,12 @@ func GetCmdQueryProposal(queryRoute string, cdc *codec.Codec) *cobra.Command {
if err != nil {
return fmt.Errorf("proposal-id %s not a valid uint", args[0])
}
bz, err := cdc.MarshalJSON(types.NewQueryProposalParams(proposalID))
if err != nil {
return err
}

// Query
res, _, err := cliCtx.QueryWithData(fmt.Sprintf("custom/%s/%s", queryRoute, types.QueryProposal), bz)
proposal, _, err := common.QueryProposalByID(cliCtx, cdc, queryRoute, proposalID)
if err != nil {
return err
}

// Decode and print results
var proposal types.Proposal
cdc.MustUnmarshalJSON(res, &proposal)
return cliCtx.PrintOutput(proposal)
},
}
Expand Down
164 changes: 164 additions & 0 deletions x/committee/client/common/query.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
package common

import (
"fmt"
"strings"
"time"

"github.com/cosmos/cosmos-sdk/client/context"
"github.com/cosmos/cosmos-sdk/codec"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
"github.com/cosmos/cosmos-sdk/x/auth/client/utils"

"github.com/kava-labs/kava/x/committee/types"
)

// Note: QueryProposer is copied in from the gov module

const (
defaultPage = 1
defaultLimit = 30 // should be consistent with tendermint/tendermint/rpc/core/pipe.go:19
)

// Proposer contains metadata of a governance proposal used for querying a proposer.
type Proposer struct {
ProposalID uint64 `json:"proposal_id" yaml:"proposal_id"`
Proposer string `json:"proposer" yaml:"proposer"`
}

// NewProposer returns a new Proposer given id and proposer
func NewProposer(proposalID uint64, proposer string) Proposer {
return Proposer{proposalID, proposer}
}

func (p Proposer) String() string {
return fmt.Sprintf("Proposal with ID %d was proposed by %s", p.ProposalID, p.Proposer)
}

// QueryProposer will query for a proposer of a governance proposal by ID.
func QueryProposer(cliCtx context.CLIContext, proposalID uint64) (Proposer, error) {
events := []string{
fmt.Sprintf("%s.%s='%s'", sdk.EventTypeMessage, sdk.AttributeKeyAction, types.TypeMsgSubmitProposal),
fmt.Sprintf("%s.%s='%s'", types.EventTypeProposalSubmit, types.AttributeKeyProposalID, []byte(fmt.Sprintf("%d", proposalID))),
}

// NOTE: SearchTxs is used to facilitate the txs query which does not currently
// support configurable pagination.
searchResult, err := utils.QueryTxsByEvents(cliCtx, events, defaultPage, defaultLimit)
if err != nil {
return Proposer{}, err
}

for _, info := range searchResult.Txs {
for _, msg := range info.Tx.GetMsgs() {
// there should only be a single proposal under the given conditions
if msg.Type() == types.TypeMsgSubmitProposal {
subMsg := msg.(types.MsgSubmitProposal)
return NewProposer(proposalID, subMsg.Proposer.String()), nil
}
}
}

return Proposer{}, fmt.Errorf("failed to find the proposer for proposalID %d", proposalID)
}

// QueryProposalByID returns a proposal from state if present or fallbacks to searching old blocks
func QueryProposalByID(cliCtx context.CLIContext, cdc *codec.Codec, queryRoute string, proposalID uint64) (*types.Proposal, int64, error) {
bz, err := cdc.MarshalJSON(types.NewQueryProposalParams(proposalID))
if err != nil {
return nil, 0, err
}

res, height, err := cliCtx.QueryWithData(fmt.Sprintf("custom/%s/%s", queryRoute, types.QueryProposal), bz)

if err == nil {
var proposal *types.Proposal
cdc.MustUnmarshalJSON(res, &proposal)

return proposal, height, nil
}

// NOTE: !errors.Is(err, types.ErrUnknownProposal) does not work here
if err != nil && !strings.Contains(err.Error(), "proposal not found") {
return nil, 0, err
}

res, height, err = cliCtx.QueryWithData(fmt.Sprintf("custom/%s/%s", queryRoute, types.QueryNextProposalID), nil)
if err != nil {
return nil, 0, err
}

var nextProposalID uint64
cdc.MustUnmarshalJSON(res, &nextProposalID)

if proposalID >= nextProposalID {
return nil, 0, sdkerrors.Wrapf(types.ErrUnknownProposal, "%d", proposalID)
}

events := []string{
fmt.Sprintf("%s.%s='%s'", sdk.EventTypeMessage, sdk.AttributeKeyAction, types.TypeMsgSubmitProposal),
fmt.Sprintf("%s.%s='%s'", types.EventTypeProposalSubmit, types.AttributeKeyProposalID, []byte(fmt.Sprintf("%d", proposalID))),
}

searchResult, err := utils.QueryTxsByEvents(cliCtx, events, defaultPage, defaultLimit)
if err != nil {
return nil, 0, err
}

for _, info := range searchResult.Txs {
for _, msg := range info.Tx.GetMsgs() {
if msg.Type() == types.TypeMsgSubmitProposal {
subMsg := msg.(types.MsgSubmitProposal)

deadline, err := calculateDeadline(cliCtx, cdc, queryRoute, subMsg.CommitteeID, info.Height)
if err != nil {
return nil, 0, err
}

return &types.Proposal{
ID: proposalID,
CommitteeID: subMsg.CommitteeID,
PubProposal: subMsg.PubProposal,
Deadline: deadline,
}, height, nil
}
}
}

return nil, 0, sdkerrors.Wrapf(types.ErrUnknownProposal, "%d", proposalID)
}

// calculateDeadline returns the proposal deadline for a committee and block height
func calculateDeadline(cliCtx context.CLIContext, cdc *codec.Codec, queryRoute string, committeeID uint64, blockHeight int64) (time.Time, error) {
var deadline time.Time

bz, err := cdc.MarshalJSON(types.NewQueryCommitteeParams(committeeID))
if err != nil {
return deadline, err
}

res, _, err := cliCtx.QueryWithData(fmt.Sprintf("custom/%s/%s", queryRoute, types.QueryCommittee), bz)
if err != nil {
return deadline, err
}

var committee types.Committee
err = cdc.UnmarshalJSON(res, &committee)
if err != nil {
return deadline, err
}

node, err := cliCtx.GetNode()
if err != nil {
return deadline, err
}

resultBlock, err := node.Block(&blockHeight)
if err != nil {
return deadline, err
}

deadline = resultBlock.Block.Header.Time.Add(committee.ProposalDuration)
return deadline, nil
}
60 changes: 0 additions & 60 deletions x/committee/client/common/query_proposer.go

This file was deleted.

8 changes: 4 additions & 4 deletions x/committee/client/rest/query.go
Original file line number Diff line number Diff line change
Expand Up @@ -148,14 +148,14 @@ func queryProposalHandlerFn(cliCtx context.CLIContext) http.HandlerFunc {
if !ok {
return
}
bz, err := cliCtx.Codec.MarshalJSON(types.NewQueryProposalParams(proposalID))

proposal, height, err := common.QueryProposalByID(cliCtx, cliCtx.Codec, types.ModuleName, proposalID)
if err != nil {
rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error())
return
}

// Query
res, height, err := cliCtx.QueryWithData(fmt.Sprintf("custom/%s/%s", types.ModuleName, types.QueryProposal), bz)
res, err := cliCtx.Codec.MarshalJSON(proposal)
if err != nil {
rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error())
return
Expand Down
13 changes: 12 additions & 1 deletion x/committee/keeper/querier.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import (
"github.com/cosmos/cosmos-sdk/codec"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"

abci "github.com/tendermint/tendermint/abci/types"

"github.com/kava-labs/kava/x/committee/types"
Expand All @@ -29,6 +28,8 @@ func NewQuerier(keeper Keeper) sdk.Querier {
return queryVote(ctx, path[1:], req, keeper)
case types.QueryTally:
return queryTally(ctx, path[1:], req, keeper)
case types.QueryNextProposalID:
return queryNextProposalID(ctx, req, keeper)
case types.QueryRawParams:
return queryRawParams(ctx, path[1:], req, keeper)

Expand Down Expand Up @@ -111,6 +112,16 @@ func queryProposal(ctx sdk.Context, path []string, req abci.RequestQuery, keeper
return bz, nil
}

func queryNextProposalID(ctx sdk.Context, req abci.RequestQuery, keeper Keeper) ([]byte, error) {
nextProposalID, _ := keeper.GetNextProposalID(ctx)

bz, err := types.ModuleCdc.MarshalJSON(nextProposalID)
if err != nil {
return nil, sdkerrors.Wrap(sdkerrors.ErrJSONUnmarshal, err.Error())
}
return bz, nil
}

// ------------------------------------------
// Votes
// ------------------------------------------
Expand Down
12 changes: 12 additions & 0 deletions x/committee/keeper/querier_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,18 @@ func (suite *QuerierTestSuite) TestQueryProposal() {
suite.Equal(suite.testGenesis.Proposals[0], proposal)
}

func (suite *QuerierTestSuite) TestQueryNextProposalID() {
bz, err := suite.querier(suite.ctx, []string{types.QueryNextProposalID}, abci.RequestQuery{})
suite.Require().NoError(err)
suite.Require().NotNil(bz)

var nextProposalID uint64
suite.Require().NoError(suite.cdc.UnmarshalJSON(bz, &nextProposalID))

expectedID, _ := suite.keeper.GetNextProposalID(suite.ctx)
suite.Require().Equal(expectedID, nextProposalID)
}

func (suite *QuerierTestSuite) TestQueryVotes() {
ctx := suite.ctx.WithIsCheckTx(false)
// Set up request query
Expand Down
17 changes: 9 additions & 8 deletions x/committee/types/querier.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,15 @@ import (

// Query endpoints supported by the Querier
const (
QueryCommittees = "committees"
QueryCommittee = "committee"
QueryProposals = "proposals"
QueryProposal = "proposal"
QueryVotes = "votes"
QueryVote = "vote"
QueryTally = "tally"
QueryRawParams = "raw_params"
QueryCommittees = "committees"
QueryCommittee = "committee"
QueryProposals = "proposals"
QueryProposal = "proposal"
QueryNextProposalID = "next-proposal-id"
QueryVotes = "votes"
QueryVote = "vote"
QueryTally = "tally"
QueryRawParams = "raw_params"
)

type QueryCommitteeParams struct {
Expand Down