Skip to content

Commit b81e781

Browse files
authored
feat: added fix command (forbole#228)
## Description This PR implements the `fix` command that allows to fix BDJuno issues without having to re-index the whole database at once. Closes forbole#227 Adds a way to fix forbole#208 ## Checklist - [ ] Targeted PR against correct branch. - [ ] Linked to Github issue with discussion and accepted design OR link to spec that describes this work. - [ ] Wrote unit tests. - [ ] Re-reviewed `Files changed` in the Github PR explorer.
1 parent 42f4b7b commit b81e781

18 files changed

+443
-35
lines changed

Makefile

+2-2
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@ all: lint build test-unit
1313
### Build flags ###
1414
###############################################################################
1515

16-
LD_FLAGS = -X github.com/desmos-labs/juno/version.Version=$(VERSION) \
17-
-X github.com/desmos-labs/juno/version.Commit=$(COMMIT)
16+
LD_FLAGS = -X github.com/forbole/juno/version.Version=$(VERSION) \
17+
-X github.com/forbole/juno/version.Commit=$(COMMIT)
1818

1919
BUILD_FLAGS := -ldflags '$(LD_FLAGS)'
2020

README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
[![Go Report Card](https://goreportcard.com/badge/github.com/forbole/bdjuno)](https://goreportcard.com/report/github.com/forbole/bdjuno)
44
![Codecov branch](https://img.shields.io/codecov/c/github/forbole/bdjuno/cosmos/v0.40.x)
55

6-
BDJuno (shorthand for BigDipper Juno) is the [Juno](https://github.com/desmos-labs/juno) implementation
6+
BDJuno (shorthand for BigDipper Juno) is the [Juno](https://github.com/forbole/juno) implementation
77
for [BigDipper](https://github.com/forbole/big-dipper).
88

99
It extends the custom Juno behavior by adding different handlers and custom operations to make it easier for BigDipper

cmd/bdjuno/main.go

+3-1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
parsecmd "github.com/forbole/juno/v2/cmd/parse"
99
"github.com/forbole/juno/v2/modules/messages"
1010

11+
fixcmd "github.com/forbole/bdjuno/v2/cmd/fix"
1112
migratecmd "github.com/forbole/bdjuno/v2/cmd/migrate"
1213

1314
"github.com/forbole/bdjuno/v2/types/config"
@@ -32,7 +33,8 @@ func main() {
3233
cmd.VersionCmd(),
3334
initcmd.InitCmd(cfg.GetInitConfig()),
3435
parsecmd.ParseCmd(cfg.GetParseConfig()),
35-
migratecmd.MigrateCmd(),
36+
migratecmd.NewMigrateCmd(),
37+
fixcmd.NewFixCmd(cfg.GetParseConfig()),
3638
)
3739

3840
executor := cmd.PrepareRootCmd(cfg.GetName(), rootCmd)

cmd/fix/fix.go

+40
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package fix
2+
3+
import (
4+
"github.com/forbole/juno/v2/cmd/parse"
5+
"github.com/spf13/cobra"
6+
7+
fixgov "github.com/forbole/bdjuno/v2/cmd/fix/gov"
8+
fixstaking "github.com/forbole/bdjuno/v2/cmd/fix/staking"
9+
)
10+
11+
// NewFixCmd returns the Cobra command allowing to fix some BDJuno bugs without having to re-sync the whole database
12+
func NewFixCmd(parseCfg *parse.Config) *cobra.Command {
13+
cmd := &cobra.Command{
14+
Use: "fix",
15+
Short: "Apply some fixes without the need to re-syncing the whole database from scratch",
16+
PersistentPreRunE: runPersistentPreRuns(parse.ReadConfig(parseCfg)),
17+
}
18+
19+
cmd.AddCommand(
20+
fixgov.NewGovCmd(parseCfg),
21+
fixstaking.NewStakingCmd(parseCfg),
22+
)
23+
24+
return cmd
25+
}
26+
27+
func runPersistentPreRuns(preRun func(_ *cobra.Command, _ []string) error) func(_ *cobra.Command, _ []string) error {
28+
return func(cmd *cobra.Command, args []string) error {
29+
if root := cmd.Root(); root != nil {
30+
if root.PersistentPreRunE != nil {
31+
err := root.PersistentPreRunE(root, args)
32+
if err != nil {
33+
return err
34+
}
35+
}
36+
}
37+
38+
return preRun(cmd, args)
39+
}
40+
}

cmd/fix/gov/cmd.go

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package gov
2+
3+
import (
4+
"github.com/forbole/juno/v2/cmd/parse"
5+
"github.com/spf13/cobra"
6+
)
7+
8+
// NewGovCmd returns the Cobra command allowing to fix various things related to the x/gov module
9+
func NewGovCmd(parseConfig *parse.Config) *cobra.Command {
10+
cmd := &cobra.Command{
11+
Use: "gov",
12+
Short: "Fix things related to the x/gov module",
13+
}
14+
15+
cmd.AddCommand(
16+
proposalCmd(parseConfig),
17+
)
18+
19+
return cmd
20+
}

cmd/fix/gov/proposal.go

+152
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
package gov
2+
3+
import (
4+
"encoding/hex"
5+
"fmt"
6+
7+
govtypes "github.com/cosmos/cosmos-sdk/x/gov/types"
8+
"github.com/forbole/juno/v2/cmd/parse"
9+
"github.com/forbole/juno/v2/types/config"
10+
"github.com/spf13/cobra"
11+
12+
"github.com/forbole/bdjuno/v2/database"
13+
"github.com/forbole/bdjuno/v2/modules"
14+
"github.com/forbole/bdjuno/v2/modules/gov"
15+
"github.com/forbole/bdjuno/v2/utils"
16+
)
17+
18+
// proposalCmd returns the Cobra command allowing to fix all things related to a proposal
19+
func proposalCmd(parseConfig *parse.Config) *cobra.Command {
20+
return &cobra.Command{
21+
Use: "proposal [id]",
22+
Short: "Get the description, votes and everything related to a proposal given its id",
23+
RunE: func(cmd *cobra.Command, args []string) error {
24+
proposalID := args[0]
25+
26+
parseCtx, err := parse.GetParsingContext(parseConfig)
27+
if err != nil {
28+
return err
29+
}
30+
31+
sources, err := modules.BuildSources(config.Cfg.Node, parseCtx.EncodingConfig)
32+
if err != nil {
33+
return err
34+
}
35+
36+
// Get the database
37+
db := database.Cast(parseCtx.Database)
38+
39+
// Build the gov module
40+
govModule := gov.NewModule(parseCtx.EncodingConfig.Marshaler, sources.GovSource, nil, nil, nil, db)
41+
42+
err = refreshProposalDetails(parseCtx, proposalID, govModule)
43+
if err != nil {
44+
return err
45+
}
46+
47+
err = refreshProposalDeposits(parseCtx, proposalID, govModule)
48+
if err != nil {
49+
return err
50+
}
51+
52+
err = refreshProposalVotes(parseCtx, proposalID, govModule)
53+
if err != nil {
54+
return err
55+
}
56+
57+
return nil
58+
},
59+
}
60+
}
61+
62+
func refreshProposalDetails(parseCtx *parse.Context, proposalID string, govModule *gov.Module) error {
63+
// Get the tx that created the proposal
64+
txs, err := utils.QueryTxs(parseCtx.Node, fmt.Sprintf("submit_proposal.proposal_id=%s", proposalID))
65+
if err != nil {
66+
return err
67+
}
68+
69+
if len(txs) > 1 {
70+
return fmt.Errorf("expecting only one create proposal transaction, found %d", len(txs))
71+
}
72+
73+
// Get the tx details
74+
tx, err := parseCtx.Node.Tx(hex.EncodeToString(txs[0].Tx.Hash()))
75+
if err != nil {
76+
return err
77+
}
78+
79+
// Handle the MsgSubmitProposal messages
80+
for index, msg := range tx.GetMsgs() {
81+
if _, ok := msg.(*govtypes.MsgSubmitProposal); !ok {
82+
continue
83+
}
84+
85+
err = govModule.HandleMsg(index, msg, tx)
86+
if err != nil {
87+
return fmt.Errorf("error while handling MsgSubmitProposal: %s", err)
88+
}
89+
}
90+
91+
return nil
92+
}
93+
94+
func refreshProposalDeposits(parseCtx *parse.Context, proposalID string, govModule *gov.Module) error {
95+
// Get the tx that deposited to the proposal
96+
txs, err := utils.QueryTxs(parseCtx.Node, fmt.Sprintf("proposal_deposit.proposal_id=%s", proposalID))
97+
if err != nil {
98+
return err
99+
}
100+
101+
for _, tx := range txs {
102+
// Get the tx details
103+
junoTx, err := parseCtx.Node.Tx(hex.EncodeToString(tx.Tx.Hash()))
104+
if err != nil {
105+
return err
106+
}
107+
108+
// Handle the MsgDeposit messages
109+
for index, msg := range junoTx.GetMsgs() {
110+
if _, ok := msg.(*govtypes.MsgDeposit); !ok {
111+
continue
112+
}
113+
114+
err = govModule.HandleMsg(index, msg, junoTx)
115+
if err != nil {
116+
return fmt.Errorf("error while handling MsgDeposit: %s", err)
117+
}
118+
}
119+
}
120+
121+
return nil
122+
}
123+
124+
func refreshProposalVotes(parseCtx *parse.Context, proposalID string, govModule *gov.Module) error {
125+
// Get the tx that voted the proposal
126+
txs, err := utils.QueryTxs(parseCtx.Node, fmt.Sprintf("proposal_vote.proposal_id=%s", proposalID))
127+
if err != nil {
128+
return err
129+
}
130+
131+
for _, tx := range txs {
132+
// Get the tx details
133+
junoTx, err := parseCtx.Node.Tx(hex.EncodeToString(tx.Tx.Hash()))
134+
if err != nil {
135+
return err
136+
}
137+
138+
// Handle the MsgVote messages
139+
for index, msg := range junoTx.GetMsgs() {
140+
if _, ok := msg.(*govtypes.MsgVote); !ok {
141+
continue
142+
}
143+
144+
err = govModule.HandleMsg(index, msg, junoTx)
145+
if err != nil {
146+
return fmt.Errorf("error while handling MsgVote: %s", err)
147+
}
148+
}
149+
}
150+
151+
return nil
152+
}

cmd/fix/staking/cmd.go

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package staking
2+
3+
import (
4+
"github.com/forbole/juno/v2/cmd/parse"
5+
"github.com/spf13/cobra"
6+
)
7+
8+
// NewStakingCmd returns the Cobra command that allows to fix all the things related to the x/staking module
9+
func NewStakingCmd(parseConfig *parse.Config) *cobra.Command {
10+
cmd := &cobra.Command{
11+
Use: "staking",
12+
Short: "Fix things related to the x/staking module",
13+
}
14+
15+
cmd.AddCommand(
16+
validatorsCmd(parseConfig),
17+
slashesCmd(parseConfig),
18+
)
19+
20+
return cmd
21+
}

cmd/fix/staking/slashes.go

+69
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
package staking
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/forbole/juno/v2/cmd/parse"
7+
"github.com/forbole/juno/v2/types/config"
8+
"github.com/spf13/cobra"
9+
10+
"github.com/forbole/bdjuno/v2/database"
11+
"github.com/forbole/bdjuno/v2/modules"
12+
"github.com/forbole/bdjuno/v2/modules/staking"
13+
)
14+
15+
// slashesCmd returns a Cobra command that allows to fix the delegations for all the slashed validators.
16+
func slashesCmd(parseConfig *parse.Config) *cobra.Command {
17+
return &cobra.Command{
18+
Use: "slashes",
19+
Short: "Fix the delegations for all the slashed validators, taking their delegations from the latest known height",
20+
RunE: func(cmd *cobra.Command, args []string) error {
21+
parseCtx, err := parse.GetParsingContext(parseConfig)
22+
if err != nil {
23+
return err
24+
}
25+
26+
sources, err := modules.BuildSources(config.Cfg.Node, parseCtx.EncodingConfig)
27+
if err != nil {
28+
return err
29+
}
30+
31+
// Get the database
32+
db := database.Cast(parseCtx.Database)
33+
34+
// Get latest height
35+
height, err := parseCtx.Node.LatestHeight()
36+
if err != nil {
37+
return fmt.Errorf("error while getting latest block height: %s", err)
38+
}
39+
40+
// Get all validators
41+
validators, err := sources.StakingSource.GetValidatorsWithStatus(height, "")
42+
if err != nil {
43+
return fmt.Errorf("error while getting validators: %s", err)
44+
}
45+
46+
for _, validator := range validators {
47+
// Get the validator delegations
48+
delegations, err := sources.StakingSource.GetValidatorDelegations(height, validator.OperatorAddress)
49+
if err != nil {
50+
return fmt.Errorf("error while getting validator delegations: %s", err)
51+
}
52+
53+
// Delete the old delegations
54+
err = db.DeleteValidatorDelegations(validator.OperatorAddress)
55+
if err != nil {
56+
return fmt.Errorf("error while deleting validator delegations: %s", err)
57+
}
58+
59+
// Save the delegations
60+
err = db.SaveDelegations(staking.ConvertDelegationsResponses(height, delegations))
61+
if err != nil {
62+
return fmt.Errorf("error while saving delegations: %s", err)
63+
}
64+
}
65+
66+
return nil
67+
},
68+
}
69+
}

0 commit comments

Comments
 (0)