Skip to content

Commit

Permalink
fix: re-add support for gov v1beta1 messages (#725)
Browse files Browse the repository at this point in the history
## Description

Closes: #XXXX
Currently, gov module is updated to gov v1, however, gov v1beta1 is
still alive to chains, Cosmos-SDK doesn't completely remove v1beta1
support. As a result, we should re-add gov v1beta1 support to make sure
our proposals data are also updated when gov v1beta1 messages are
performed.

Say, some of users on Cheqd is using gov.v1beta1.MsgVote to vote on the
proposal.

https://bigdipper.live/cheqd/transactions/19E4F068FBFBC46DA9BEF3E0F7CA908317D5582CAB2A17683138A5248ED34E3C

<!-- Add a description of the changes that this PR introduces and the
files that
are the most critical to review. -->

---

### Author Checklist

*All items are required. Please add a note to the item if the item is
not applicable and
please add links to any relevant follow up issues.*

I have...

- [ ] included the correct [type
prefix](https://github.com/commitizen/conventional-commit-types/blob/v3.0.0/index.json)
in the PR title
- [ ] added `!` to the type prefix if API or client breaking change
- [ ] targeted the correct branch
- [ ] provided a link to the relevant issue or specification
- [ ] added a changelog entry to `CHANGELOG.md`
- [ ] included comments for [documenting Go
code](https://blog.golang.org/godoc)
- [ ] updated the relevant documentation or specification
- [ ] reviewed "Files changed" and left comments if necessary
- [ ] confirmed all CI checks have passed

### Reviewers Checklist

*All items are required. Please add a note if the item is not applicable
and please add
your handle next to the items reviewed if you only reviewed selected
items.*

I have...

- [ ] confirmed the correct [type
prefix](https://github.com/commitizen/conventional-commit-types/blob/v3.0.0/index.json)
in the PR title
- [ ] confirmed `!` in the type prefix if API or client breaking change
- [ ] confirmed all author checklist items have been addressed
- [ ] reviewed API design and naming
- [ ] reviewed documentation is accurate
- [ ] reviewed tests and test coverage
- [ ] manually tested (if applicable)
  • Loading branch information
dadamu authored Apr 17, 2024
1 parent 631ae42 commit cf05236
Show file tree
Hide file tree
Showing 6 changed files with 264 additions and 90 deletions.
72 changes: 44 additions & 28 deletions cmd/parse/gov/proposal.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
modulestypes "github.com/forbole/callisto/v4/modules/types"

govtypesv1 "github.com/cosmos/cosmos-sdk/x/gov/types/v1"
govtypesv1beta1 "github.com/cosmos/cosmos-sdk/x/gov/types/v1beta1"
parsecmdtypes "github.com/forbole/juno/v5/cmd/parse/types"
"github.com/forbole/juno/v5/parser"
"github.com/forbole/juno/v5/types/config"
Expand Down Expand Up @@ -124,13 +125,13 @@ func refreshProposalDetails(parseCtx *parser.Context, proposalID uint64, govModu

// Handle the MsgSubmitProposal messages
for index, msg := range tx.GetMsgs() {
if _, ok := msg.(*govtypesv1.MsgSubmitProposal); !ok {
continue
}

err = govModule.HandleMsg(index, msg, tx)
if err != nil {
return fmt.Errorf("error while handling MsgSubmitProposal: %s", err)
switch msg.(type) {
case *govtypesv1.MsgSubmitProposal, *govtypesv1beta1.MsgSubmitProposal:
err = govModule.HandleMsg(index, msg, tx)
if err != nil {
return fmt.Errorf("error while handling MsgSubmitProposal: %s", err)
}
}
}

Expand All @@ -155,13 +156,12 @@ func refreshProposalDeposits(parseCtx *parser.Context, proposalID uint64, govMod

// Handle the MsgDeposit messages
for index, msg := range junoTx.GetMsgs() {
if _, ok := msg.(*govtypesv1.MsgDeposit); !ok {
continue
}

err = govModule.HandleMsg(index, msg, junoTx)
if err != nil {
return fmt.Errorf("error while handling MsgDeposit: %s", err)
switch msg.(type) {
case *govtypesv1.MsgDeposit, *govtypesv1beta1.MsgDeposit:
err = govModule.HandleMsg(index, msg, junoTx)
if err != nil {
return fmt.Errorf("error while handling MsgDeposit: %s", err)
}
}
}
}
Expand All @@ -187,23 +187,39 @@ func refreshProposalVotes(parseCtx *parser.Context, proposalID uint64, govModule

// Handle the MsgVote messages
for index, msg := range junoTx.GetMsgs() {
if msgVote, ok := msg.(*govtypesv1.MsgVote); !ok {
var msgProposalID uint64

switch cosmosMsg := msg.(type) {
case *govtypesv1.MsgVote:
msgProposalID = cosmosMsg.ProposalId

case *govtypesv1beta1.MsgVote:
msgProposalID = cosmosMsg.ProposalId

case *govtypesv1.MsgVoteWeighted:
msgProposalID = cosmosMsg.ProposalId

case *govtypesv1beta1.MsgVoteWeighted:
msgProposalID = cosmosMsg.ProposalId

// Skip if the message is not a vote message
default:
continue
} else {
// check if requested proposal ID is the same as proposal ID returned
// from the msg as some txs may contain multiple MsgVote msgs
// for different proposals which can cause error if one of the proposals
// info is not stored in database
if proposalID == msgVote.ProposalId {
err = govModule.HandleMsg(index, msg, junoTx)
if err != nil {
return fmt.Errorf("error while handling MsgVote: %s", err)
}
} else {
// skip votes for proposals with IDs
// different than requested in the query
continue
}

// check if requested proposal ID is the same as proposal ID returned
// from the msg as some txs may contain multiple MsgVote msgs
// for different proposals which can cause error if one of the proposals
// info is not stored in database
if proposalID == msgProposalID {
err = govModule.HandleMsg(index, msg, junoTx)
if err != nil {
return fmt.Errorf("error while handling MsgVote: %s", err)
}
} else {
// skip votes for proposals with IDs
// different than requested in the query
continue
}
}
}
Expand Down
111 changes: 53 additions & 58 deletions modules/gov/handle_msg.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,18 @@ package gov

import (
"fmt"
"strconv"
"strings"
"time"

"github.com/cosmos/cosmos-sdk/x/authz"

"github.com/forbole/callisto/v4/types"
"google.golang.org/grpc/codes"

sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/authz"
distrtypes "github.com/cosmos/cosmos-sdk/x/distribution/types"
govtypesv1 "github.com/cosmos/cosmos-sdk/x/gov/types/v1"
govtypesv1beta1 "github.com/cosmos/cosmos-sdk/x/gov/types/v1beta1"

gov "github.com/cosmos/cosmos-sdk/x/gov/types"
juno "github.com/forbole/juno/v5/types"
)

Expand All @@ -32,37 +30,35 @@ func (m *Module) HandleMsg(index int, msg sdk.Msg, tx *juno.Tx) error {

switch cosmosMsg := msg.(type) {
case *govtypesv1.MsgSubmitProposal:
return m.handleMsgSubmitProposal(tx, index, cosmosMsg)
return m.handleSubmitProposalEvent(tx, cosmosMsg.Proposer, tx.Logs[index].Events)
case *govtypesv1beta1.MsgSubmitProposal:
return m.handleSubmitProposalEvent(tx, cosmosMsg.Proposer, tx.Logs[index].Events)

case *govtypesv1.MsgDeposit:
return m.handleMsgDeposit(tx, cosmosMsg)
return m.handleDepositEvent(tx, cosmosMsg.Depositor, tx.Logs[index].Events)
case *govtypesv1beta1.MsgDeposit:
return m.handleDepositEvent(tx, cosmosMsg.Depositor, tx.Logs[index].Events)

case *govtypesv1.MsgVote:
return m.handleMsgVote(tx, cosmosMsg)
return m.handleVoteEvent(tx, cosmosMsg.Voter, tx.Logs[index].Events)
case *govtypesv1beta1.MsgVote:
return m.handleVoteEvent(tx, cosmosMsg.Voter, tx.Logs[index].Events)

case *govtypesv1.MsgVoteWeighted:
return m.handleMsgVoteWeighted(tx, cosmosMsg)
return m.handleVoteEvent(tx, cosmosMsg.Voter, tx.Logs[index].Events)
case *govtypesv1beta1.MsgVoteWeighted:
return m.handleVoteEvent(tx, cosmosMsg.Voter, tx.Logs[index].Events)
}

return nil
}

// handleMsgSubmitProposal allows to properly handle a MsgSubmitProposal
func (m *Module) handleMsgSubmitProposal(tx *juno.Tx, index int, msg *govtypesv1.MsgSubmitProposal) error {
// handleSubmitProposalEvent allows to properly handle a handleSubmitProposalEvent
func (m *Module) handleSubmitProposalEvent(tx *juno.Tx, proposer string, events sdk.StringEvents) error {
// Get the proposal id
event, err := tx.FindEventByType(index, gov.EventTypeSubmitProposal)
if err != nil {
return fmt.Errorf("error while searching for EventTypeSubmitProposal: %s", err)
}

id, err := tx.FindAttributeByKey(event, gov.AttributeKeyProposalID)
if err != nil {
return fmt.Errorf("error while searching for AttributeKeyProposalID: %s", err)
}

proposalID, err := strconv.ParseUint(id, 10, 64)
proposalID, err := ProposalIDFromEvents(events)
if err != nil {
return fmt.Errorf("error while parsing proposal id: %s", err)
return fmt.Errorf("error while getting proposal id: %s", err)
}

// Get the proposal
Expand Down Expand Up @@ -108,39 +104,45 @@ func (m *Module) handleMsgSubmitProposal(tx *juno.Tx, index int, msg *govtypesv1
return fmt.Errorf("error while storing proposal recipient: %s", err)
}

// Unpack the proposal interfaces
err = proposal.UnpackInterfaces(m.cdc)
if err != nil {
return fmt.Errorf("error while unpacking proposal interfaces: %s", err)
}

// Store the proposal
proposalObj := types.NewProposal(
proposal.Id,
proposal.Title,
proposal.Summary,
proposal.Metadata,
msg.Messages,
proposal.Messages,
proposal.Status.String(),
*proposal.SubmitTime,
*proposal.DepositEndTime,
proposal.VotingStartTime,
proposal.VotingEndTime,
msg.Proposer,
proposer,
)

err = m.db.SaveProposals([]types.Proposal{proposalObj})
if err != nil {
return err
return fmt.Errorf("error while saving proposal: %s", err)
}

txTimestamp, err := time.Parse(time.RFC3339, tx.Timestamp)
// Submit proposal must have a deposit event with depositor equal to the proposer
return m.handleDepositEvent(tx, proposer, events)
}

// handleDepositEvent allows to properly handle a handleDepositEvent
func (m *Module) handleDepositEvent(tx *juno.Tx, depositor string, events sdk.StringEvents) error {
// Get the proposal id
proposalID, err := ProposalIDFromEvents(events)
if err != nil {
return fmt.Errorf("error while parsing time: %s", err)
return fmt.Errorf("error while getting proposal id: %s", err)
}

// Store the deposit
deposit := types.NewDeposit(proposal.Id, msg.Proposer, msg.InitialDeposit, txTimestamp, tx.TxHash, tx.Height)
return m.db.SaveDeposits([]types.Deposit{deposit})
}

// handleMsgDeposit allows to properly handle a MsgDeposit
func (m *Module) handleMsgDeposit(tx *juno.Tx, msg *govtypesv1.MsgDeposit) error {
deposit, err := m.source.ProposalDeposit(tx.Height, msg.ProposalId, msg.Depositor)
deposit, err := m.source.ProposalDeposit(tx.Height, proposalID, depositor)
if err != nil {
return fmt.Errorf("error while getting proposal deposit: %s", err)
}
Expand All @@ -150,43 +152,36 @@ func (m *Module) handleMsgDeposit(tx *juno.Tx, msg *govtypesv1.MsgDeposit) error
}

return m.db.SaveDeposits([]types.Deposit{
types.NewDeposit(msg.ProposalId, msg.Depositor, deposit.Amount, txTimestamp, tx.TxHash, tx.Height),
types.NewDeposit(proposalID, depositor, deposit.Amount, txTimestamp, tx.TxHash, tx.Height),
})
}

// handleMsgVote allows to properly handle a MsgVote
func (m *Module) handleMsgVote(tx *juno.Tx, msg *govtypesv1.MsgVote) error {
// handleVoteEvent allows to properly handle a handleVoteEvent
func (m *Module) handleVoteEvent(tx *juno.Tx, voter string, events sdk.StringEvents) error {
// Get the proposal id
proposalID, err := ProposalIDFromEvents(events)
if err != nil {
return fmt.Errorf("error while getting proposal id: %s", err)
}

txTimestamp, err := time.Parse(time.RFC3339, tx.Timestamp)
if err != nil {
return fmt.Errorf("error while parsing time: %s", err)
}

vote := types.NewVote(msg.ProposalId, msg.Voter, msg.Option, "1.0", txTimestamp, tx.Height)

err = m.db.SaveVote(vote)
// Get the vote option
weightVoteOption, err := WeightVoteOptionFromEvents(events)
if err != nil {
return fmt.Errorf("error while saving vote: %s", err)
return fmt.Errorf("error while getting vote option: %s", err)
}

// update tally result for given proposal
return m.UpdateProposalTallyResult(msg.ProposalId, tx.Height)
}
vote := types.NewVote(proposalID, voter, weightVoteOption.Option, weightVoteOption.Weight, txTimestamp, tx.Height)

// handleMsgVoteWeighted allows to properly handle a MsgVoteWeighted
func (m *Module) handleMsgVoteWeighted(tx *juno.Tx, msg *govtypesv1.MsgVoteWeighted) error {
txTimestamp, err := time.Parse(time.RFC3339, tx.Timestamp)
err = m.db.SaveVote(vote)
if err != nil {
return fmt.Errorf("error while parsing time: %s", err)
}

for _, option := range msg.Options {
vote := types.NewVote(msg.ProposalId, msg.Voter, option.Option, option.Weight, txTimestamp, tx.Height)
err = m.db.SaveVote(vote)
if err != nil {
return fmt.Errorf("error while saving weighted vote for address %s: %s", msg.Voter, err)
}
return fmt.Errorf("error while saving vote: %s", err)
}

// update tally result for given proposal
return m.UpdateProposalTallyResult(msg.ProposalId, tx.Height)
return m.UpdateProposalTallyResult(proposalID, tx.Height)
}
9 changes: 5 additions & 4 deletions modules/gov/module.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,11 @@ import (
)

var (
_ modules.Module = &Module{}
_ modules.GenesisModule = &Module{}
_ modules.BlockModule = &Module{}
_ modules.MessageModule = &Module{}
_ modules.Module = &Module{}
_ modules.GenesisModule = &Module{}
_ modules.BlockModule = &Module{}
_ modules.MessageModule = &Module{}
_ modules.AuthzMessageModule = &Module{}
)

// Module represent x/gov module
Expand Down
66 changes: 66 additions & 0 deletions modules/gov/utils_events.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package gov

import (
"encoding/json"
"fmt"
"strconv"
"strings"

sdk "github.com/cosmos/cosmos-sdk/types"
govtypes "github.com/cosmos/cosmos-sdk/x/gov/types"
govtypesv1 "github.com/cosmos/cosmos-sdk/x/gov/types/v1"
eventsutil "github.com/forbole/callisto/v4/utils/events"
)

// ProposalIDFromEvent returns the proposal id from the given events
func ProposalIDFromEvents(events sdk.StringEvents) (uint64, error) {
for _, event := range events {
attribute, ok := eventsutil.FindAttributeByKey(event, govtypes.AttributeKeyProposalID)
if ok {
return strconv.ParseUint(attribute.Value, 10, 64)
}
}

return 0, fmt.Errorf("no proposal id found")
}

// WeightVoteOptionFromEvents returns the vote option from the given events
func WeightVoteOptionFromEvents(events sdk.StringEvents) (govtypesv1.WeightedVoteOption, error) {
for _, event := range events {
attribute, ok := eventsutil.FindAttributeByKey(event, govtypes.AttributeKeyOption)
if ok {
return parseWeightVoteOption(attribute.Value)
}
}

return govtypesv1.WeightedVoteOption{}, fmt.Errorf("no vote option found")
}

// parseWeightVoteOption returns the vote option from the given string
// option value in string has 2 cases, for example:
// 1. "{\"option\":1,\"weight\":\"1.000000000000000000\"}"
// 2. "option:VOTE_OPTION_NO weight:\"1.000000000000000000\""
func parseWeightVoteOption(optionValue string) (govtypesv1.WeightedVoteOption, error) {
// try parse json option value
var weightedVoteOption govtypesv1.WeightedVoteOption
err := json.Unmarshal([]byte(optionValue), &weightedVoteOption)
if err == nil {
return weightedVoteOption, nil
}

// try parse string option value
// option:VOTE_OPTION_NO weight:"1.000000000000000000"
voteOptionParsed := strings.Split(optionValue, " ")
if len(voteOptionParsed) != 2 {
return govtypesv1.WeightedVoteOption{}, fmt.Errorf("failed to parse vote option %s", optionValue)
}

voteOption, err := govtypesv1.VoteOptionFromString(strings.ReplaceAll(voteOptionParsed[0], "option:", ""))
if err != nil {
return govtypesv1.WeightedVoteOption{}, fmt.Errorf("failed to parse vote option %s: %s", optionValue, err)
}
weight := strings.ReplaceAll(voteOptionParsed[1], "weight:", "")
weight = strings.ReplaceAll(weight, "\"", "")

return govtypesv1.WeightedVoteOption{Option: voteOption, Weight: weight}, nil
}
Loading

0 comments on commit cf05236

Please sign in to comment.