From 7b4ec26ce5ee720c861373135c8e9c4ec570893d Mon Sep 17 00:00:00 2001 From: Magic Cat <37407870+MonikaCat@users.noreply.github.com> Date: Tue, 3 Oct 2023 14:37:04 +0700 Subject: [PATCH] feat: update `x/gov` module parsing (#652) ## Description Closes: #XXXX [BDU-11](https://forbole.atlassian.net/browse/BDU-11) [BDU-771](https://forbole.atlassian.net/browse/BDU-771) [BDU-1077](https://forbole.atlassian.net/browse/BDU-1077) Changes: `gov` module: - Added handler on every block to check and update proposal status if `active_proposal` event has been emitted inside the EndBlockEvents - Added periodic operations to update `proposal staking pool snapshots` every 5 mins - Added periodic operations to update `proposal tally results` every 5 mins - Added handler to update `tally results` for given proposal on every `MsgVote` and `MsgVoteWeighted` msgs - Added handler for rpc error `rpc error returning code = NotFound desc = proposal x doesn't exist` returned from node sometimes when processing `MsgSubmitProposal` msg and querying the node for proposal details - Added amount to `unique_deposit` constraint to allow to store different deposits from the same address for the same proposal - Added `weight` column to `proposal_vote` table and added `option` to `unique_vote` constraint - Added handler for `MsgVoteWeighted` msgs to correctly store votes in db - Removed `auth` module from expected modules `staking` module: - Added periodic operations to update validators statuses, voting power and proposal validators status snapshots every 5 mins - Added handler to update validators statuses, voting powers and proposals validators status snapshots when there is a VP change on `MsgDelegate`, `MsgBeginRedelegate` and `MsgUndelegate` msgs --- ### 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... - [x] 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 - [x] targeted the correct branch - [x] provided a link to the relevant issue or specification - [x] added a changelog entry to `CHANGELOG.md` - [x] included comments for [documenting Go code](https://blog.golang.org/godoc) - [x] updated the relevant documentation or specification - [x] reviewed "Files changed" and left comments if necessary - [x] 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) [BDU-11]: https://forbole.atlassian.net/browse/BDU-11?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ [BDU-771]: https://forbole.atlassian.net/browse/BDU-771?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ [BDU-1077]: https://forbole.atlassian.net/browse/BDU-1077?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ --- CHANGELOG.md | 2 +- cmd/parse/gov/proposal.go | 37 ++- database/auth.go | 16 +- database/consensus.go | 18 +- database/gov.go | 74 +++--- database/gov_test.go | 111 +++++---- database/schema/08-gov.sql | 18 +- database/staking_validators.go | 42 ++-- database/staking_validators_test.go | 7 +- database/types/consensus.go | 8 + database/types/gov.go | 28 ++- .../bdjuno/tables/public_proposal_vote.yaml | 1 + modules/bank/handle_periodic_operations.go | 6 +- .../handle_periodic_operations.go | 4 +- modules/gov/expected_modules.go | 11 - modules/gov/handle_block.go | 50 ++-- modules/gov/handle_genesis.go | 1 + modules/gov/handle_msg.go | 84 ++++++- modules/gov/handle_periodic_operations.go | 32 +++ modules/gov/module.go | 3 - modules/gov/utils_proposal.go | 217 ++++++------------ modules/mint/handle_periodic_operations.go | 6 +- modules/registrar.go | 2 +- modules/staking/handle_block.go | 69 +----- modules/staking/handle_msg.go | 13 ++ modules/staking/handle_periodic_operations.go | 13 +- modules/staking/utils_validators.go | 120 ++++++++-- types/gov.go | 31 ++- 28 files changed, 610 insertions(+), 414 deletions(-) create mode 100644 modules/gov/handle_periodic_operations.go diff --git a/CHANGELOG.md b/CHANGELOG.md index f98328452..4acb453e2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ ## Unreleased - ([\#610](https://github.com/forbole/bdjuno/pull/610)) Add support for gov `v1` proposals - +- ([\#652](https://github.com/forbole/bdjuno/pull/652)) Update gov module parsing ## Version v4.0.0 ## Notes diff --git a/cmd/parse/gov/proposal.go b/cmd/parse/gov/proposal.go index a53f13411..eb8ab8768 100644 --- a/cmd/parse/gov/proposal.go +++ b/cmd/parse/gov/proposal.go @@ -4,7 +4,6 @@ import ( "encoding/hex" "fmt" "strconv" - "time" "github.com/rs/zerolog/log" @@ -56,7 +55,7 @@ func proposalCmd(parseConfig *parsecmdtypes.Config) *cobra.Command { stakingModule := staking.NewModule(sources.StakingSource, parseCtx.EncodingConfig.Codec, db) // Build the gov module - govModule := gov.NewModule(sources.GovSource, nil, distrModule, mintModule, slashingModule, stakingModule, parseCtx.EncodingConfig.Codec, db) + govModule := gov.NewModule(sources.GovSource, distrModule, mintModule, slashingModule, stakingModule, parseCtx.EncodingConfig.Codec, db) err = refreshProposalDetails(parseCtx, proposalID, govModule) if err != nil { @@ -79,7 +78,17 @@ func proposalCmd(parseConfig *parsecmdtypes.Config) *cobra.Command { return fmt.Errorf("error while getting chain latest block height: %s", err) } - err = govModule.UpdateProposal(height, time.Now(), proposalID) + err = govModule.UpdateProposalStatus(height, proposalID) + if err != nil { + return err + } + + err = govModule.UpdateProposalTallyResult(proposalID, height) + if err != nil { + return err + } + + err = govModule.UpdateProposalStakingPoolSnapshot(height, proposalID) if err != nil { return err } @@ -178,13 +187,23 @@ func refreshProposalVotes(parseCtx *parser.Context, proposalID uint64, govModule // Handle the MsgVote messages for index, msg := range junoTx.GetMsgs() { - if _, ok := msg.(*govtypesv1.MsgVote); !ok { + if msgVote, ok := msg.(*govtypesv1.MsgVote); !ok { continue - } - - err = govModule.HandleMsg(index, msg, junoTx) - if err != nil { - return fmt.Errorf("error while handling MsgVote: %s", err) + } 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 + } } } } diff --git a/database/auth.go b/database/auth.go index 24c3fa23f..4b406ddd9 100644 --- a/database/auth.go +++ b/database/auth.go @@ -98,8 +98,14 @@ func (db *Db) storeVestingAccount(account exported.VestingAccount) (int, error) start_time = excluded.start_time RETURNING id ` + // Store the vesting account + err := db.SaveAccounts([]types.Account{types.NewAccount(account.GetAddress().String())}) + if err != nil { + return 0, fmt.Errorf("error while storing vesting account address: %s", err) + } + var vestingAccountRowID int - err := db.SQL.QueryRow(stmt, + err = db.SQL.QueryRow(stmt, proto.MessageName(account), account.GetAddress().String(), pq.Array(dbtypes.NewDbCoins(account.GetOriginalVesting())), @@ -124,7 +130,13 @@ func (db *Db) StoreBaseVestingAccountFromMsg(bva *vestingtypes.BaseVestingAccoun start_time = excluded.start_time, end_time = excluded.end_time` - _, err := db.SQL.Exec(stmt, + // Store the vesting account + err := db.SaveAccounts([]types.Account{types.NewAccount(bva.GetAddress().String())}) + if err != nil { + return fmt.Errorf("error while storing vesting account address: %s", err) + } + + _, err = db.SQL.Exec(stmt, proto.MessageName(bva), bva.GetAddress().String(), pq.Array(dbtypes.NewDbCoins(bva.OriginalVesting)), diff --git a/database/consensus.go b/database/consensus.go index 556c8c8a1..681cd4d48 100644 --- a/database/consensus.go +++ b/database/consensus.go @@ -25,20 +25,20 @@ func (db *Db) GetLastBlock() (*dbtypes.BlockRow, error) { return &blocks[0], nil } -// GetLastBlockHeight returns the last block height stored inside the database -func (db *Db) GetLastBlockHeight() (int64, error) { - stmt := `SELECT height FROM block ORDER BY height DESC LIMIT 1` +// GetLastBlockHeight returns the last block height and timestamp stored inside the database +func (db *Db) GetLastBlockHeightAndTimestamp() (dbtypes.BlockHeightAndTimestamp, error) { + stmt := `SELECT height, timestamp FROM block ORDER BY height DESC LIMIT 1` - var heights []int64 - if err := db.Sqlx.Select(&heights, stmt); err != nil { - return 0, err + var blockHeightAndTimestamp []dbtypes.BlockHeightAndTimestamp + if err := db.Sqlx.Select(&blockHeightAndTimestamp, stmt); err != nil { + return dbtypes.BlockHeightAndTimestamp{}, fmt.Errorf("cannot get last block height and timestamp from db: %s", err) } - if len(heights) == 0 { - return 0, nil + if len(blockHeightAndTimestamp) == 0 { + return dbtypes.BlockHeightAndTimestamp{}, nil } - return heights[0], nil + return blockHeightAndTimestamp[0], nil } // ------------------------------------------------------------------------------------------------------------------- diff --git a/database/gov.go b/database/gov.go index 673a53abe..96af2ac87 100644 --- a/database/gov.go +++ b/database/gov.go @@ -112,7 +112,7 @@ INSERT INTO proposal( ) } - // Store the accounts + // Store the proposers accounts err := db.SaveAccounts(accounts) if err != nil { return fmt.Errorf("error while storing proposers accounts: %s", err) @@ -120,7 +120,17 @@ INSERT INTO proposal( // Store the proposals proposalsQuery = proposalsQuery[:len(proposalsQuery)-1] // Remove trailing "," - proposalsQuery += " ON CONFLICT DO NOTHING" + proposalsQuery += ` +ON CONFLICT (id) DO UPDATE + SET title = excluded.title, + description = excluded.description, + content = excluded.content, + proposer_address = excluded.proposer_address, + status = excluded.status, + submit_time = excluded.submit_time, + deposit_end_time = excluded.deposit_end_time, + voting_start_time = excluded.voting_start_time, + voting_end_time = excluded.voting_end_time` _, err = db.SQL.Exec(proposalsQuery, proposalsParams...) if err != nil { return fmt.Errorf("error while storing proposals: %s", err) @@ -134,7 +144,7 @@ func (db *Db) GetProposal(id uint64) (types.Proposal, error) { var rows []*dbtypes.ProposalRow err := db.SQL.Select(&rows, `SELECT * FROM proposal WHERE id = $1`, id) if err != nil { - return types.Proposal{}, err + return types.Proposal{}, fmt.Errorf("error while getting proposal %d: %s", id, err) } if len(rows) == 0 { @@ -203,7 +213,7 @@ func (db *Db) UpdateProposal(update types.ProposalUpdate) error { update.ProposalID, ) if err != nil { - return fmt.Errorf("error while updating proposal: %s", err) + return fmt.Errorf("error while updating proposal %d: %s", update.ProposalID, err) } return nil @@ -215,25 +225,29 @@ func (db *Db) SaveDeposits(deposits []types.Deposit) error { return nil } - query := `INSERT INTO proposal_deposit (proposal_id, depositor_address, amount, timestamp, height) VALUES ` + query := `INSERT INTO proposal_deposit (proposal_id, depositor_address, + amount, timestamp, transaction_hash, height) VALUES ` var param []interface{} var accounts []types.Account for i, deposit := range deposits { - vi := i * 5 - query += fmt.Sprintf("($%d,$%d,$%d,$%d,$%d),", vi+1, vi+2, vi+3, vi+4, vi+5) + vi := i * 6 + + accounts = append(accounts, types.NewAccount(deposit.Depositor)) + + query += fmt.Sprintf("($%d,$%d,$%d,$%d,$%d,$%d),", vi+1, vi+2, vi+3, vi+4, vi+5, vi+6) param = append(param, deposit.ProposalID, deposit.Depositor, pq.Array(dbtypes.NewDbCoins(deposit.Amount)), deposit.Timestamp, + deposit.TransactionHash, deposit.Height, ) - accounts = append(accounts, types.NewAccount(deposit.Depositor)) } - // Store the depositor account + // Store depositors accounts err := db.SaveAccounts(accounts) if err != nil { - return fmt.Errorf("error while storing depositor account: %s", err) + return fmt.Errorf("error while storing depositors accounts: %s", err) } query = query[:len(query)-1] // Remove trailing "," @@ -256,10 +270,10 @@ WHERE proposal_deposit.height <= excluded.height` // SaveVote allows to save for the given height and the message vote func (db *Db) SaveVote(vote types.Vote) error { query := ` -INSERT INTO proposal_vote (proposal_id, voter_address, option, timestamp, height) -VALUES ($1, $2, $3, $4, $5) +INSERT INTO proposal_vote (proposal_id, voter_address, option, weight, timestamp, height) +VALUES ($1, $2, $3, $4, $5, $6) ON CONFLICT ON CONSTRAINT unique_vote DO UPDATE - SET option = excluded.option, + SET weight = excluded.weight, timestamp = excluded.timestamp, height = excluded.height WHERE proposal_vote.height <= excluded.height` @@ -270,9 +284,9 @@ WHERE proposal_vote.height <= excluded.height` return fmt.Errorf("error while storing voter account: %s", err) } - _, err = db.SQL.Exec(query, vote.ProposalID, vote.Voter, vote.Option.String(), vote.Timestamp, vote.Height) + _, err = db.SQL.Exec(query, vote.ProposalID, vote.Voter, vote.Option.String(), vote.Weight, vote.Timestamp, vote.Height) if err != nil { - return fmt.Errorf("error while storing vote: %s", err) + return fmt.Errorf("error while storing vote for proposal %d: %s", vote.ProposalID, err) } return nil @@ -323,17 +337,19 @@ func (db *Db) SaveProposalStakingPoolSnapshot(snapshot types.ProposalStakingPool stmt := ` INSERT INTO proposal_staking_pool_snapshot (proposal_id, bonded_tokens, not_bonded_tokens, height) VALUES ($1, $2, $3, $4) -ON CONFLICT ON CONSTRAINT unique_staking_pool_snapshot DO UPDATE SET - proposal_id = excluded.proposal_id, - bonded_tokens = excluded.bonded_tokens, - not_bonded_tokens = excluded.not_bonded_tokens, +ON CONFLICT ON CONSTRAINT unique_staking_pool_snapshot DO UPDATE + SET bonded_tokens = excluded.bonded_tokens, + not_bonded_tokens = excluded.not_bonded_tokens, height = excluded.height WHERE proposal_staking_pool_snapshot.height <= excluded.height` _, err := db.SQL.Exec(stmt, - snapshot.ProposalID, snapshot.Pool.BondedTokens.String(), snapshot.Pool.NotBondedTokens.String(), snapshot.Pool.Height) + snapshot.ProposalID, snapshot.Pool.BondedTokens.String(), + snapshot.Pool.NotBondedTokens.String(), snapshot.Pool.Height) + if err != nil { - return fmt.Errorf("error while storing proposal staking pool snapshot: %s", err) + return fmt.Errorf("error while storing proposal staking pool snapshot for proposal %d: %s", + snapshot.ProposalID, err) } return nil @@ -346,7 +362,7 @@ func (db *Db) SaveProposalValidatorsStatusesSnapshots(snapshots []types.Proposal } stmt := ` -INSERT INTO proposal_validator_status_snapshot(proposal_id, validator_address, voting_power, status, jailed, height) +INSERT INTO proposal_validator_status_snapshot (proposal_id, validator_address, voting_power, status, jailed, height) VALUES ` var args []interface{} @@ -361,17 +377,15 @@ VALUES ` stmt = stmt[:len(stmt)-1] stmt += ` -ON CONFLICT ON CONSTRAINT unique_validator_status_snapshot DO UPDATE - SET proposal_id = excluded.proposal_id, - validator_address = excluded.validator_address, - voting_power = excluded.voting_power, - status = excluded.status, +ON CONFLICT ON CONSTRAINT unique_validator_status_snapshot DO UPDATE + SET voting_power = excluded.voting_power, + status = excluded.status, jailed = excluded.jailed, height = excluded.height WHERE proposal_validator_status_snapshot.height <= excluded.height` _, err := db.SQL.Exec(stmt, args...) if err != nil { - return fmt.Errorf("error while storing proposal validator statuses snapshot: %s", err) + return fmt.Errorf("error while storing proposal validator statuses snapshots: %s", err) } return nil @@ -393,7 +407,7 @@ WHERE software_upgrade_plan.height <= excluded.height` _, err := db.SQL.Exec(stmt, proposalID, plan.Name, plan.Height, plan.Info, height) if err != nil { - return fmt.Errorf("error while storing software upgrade plan: %s", err) + return fmt.Errorf("error while storing software upgrade plan for proposal %d: %s", proposalID, err) } return nil @@ -405,7 +419,7 @@ func (db *Db) DeleteSoftwareUpgradePlan(proposalID uint64) error { _, err := db.SQL.Exec(stmt, proposalID) if err != nil { - return fmt.Errorf("error while deleting software upgrade plan: %s", err) + return fmt.Errorf("error while deleting software upgrade plan for proposal %d: %s", proposalID, err) } return nil diff --git a/database/gov_test.go b/database/gov_test.go index 907f7f5f8..f5e4aa6ab 100644 --- a/database/gov_test.go +++ b/database/gov_test.go @@ -386,30 +386,33 @@ func (suite *DbTestSuite) TestBigDipperDb_SaveDeposits() { depositor := suite.getAccount("cosmos1z4hfrxvlgl4s8u4n5ngjcw8kdqrcv43599amxs") amount := sdk.NewCoins(sdk.NewCoin("desmos", sdk.NewInt(10000))) + txHash := "D40FE0C386FA85677FFB9B3C4CECD54CF2CD7ABECE4EF15FAEF328FCCBF4C3A8" depositor2 := suite.getAccount("cosmos184ma3twcfjqef6k95ne8w2hk80x2kah7vcwy4a") amount2 := sdk.NewCoins(sdk.NewCoin("desmos", sdk.NewInt(30000))) + txHash2 := "40A9812A137256E88593E19428E006C01D87DB35F60F8D14739B4A46AC3C67A5" depositor3 := suite.getAccount("cosmos1gyds87lg3m52hex9yqta2mtwzw89pfukx3jl7g") amount3 := sdk.NewCoins(sdk.NewCoin("desmos", sdk.NewInt(50000))) + txHash3 := "086CFE10741EF3800DB7F72B1666DE298DD40913BBB84C5530C87AF5EDE8027A" timestamp1 := time.Date(2020, 1, 1, 15, 00, 00, 000, time.UTC) timestamp2 := time.Date(2020, 1, 1, 16, 00, 00, 000, time.UTC) timestamp3 := time.Date(2020, 1, 1, 17, 00, 00, 000, time.UTC) deposit := []types.Deposit{ - types.NewDeposit(proposal.ID, depositor.String(), amount, timestamp1, 10), - types.NewDeposit(proposal.ID, depositor2.String(), amount2, timestamp2, 10), - types.NewDeposit(proposal.ID, depositor3.String(), amount3, timestamp3, 10), + types.NewDeposit(proposal.ID, depositor.String(), amount, timestamp1, txHash, 10), + types.NewDeposit(proposal.ID, depositor2.String(), amount2, timestamp2, txHash2, 10), + types.NewDeposit(proposal.ID, depositor3.String(), amount3, timestamp3, txHash3, 10), } err := suite.database.SaveDeposits(deposit) suite.Require().NoError(err) expected := []dbtypes.DepositRow{ - dbtypes.NewDepositRow(1, depositor.String(), dbtypes.NewDbCoins(amount), timestamp1, 10), - dbtypes.NewDepositRow(1, depositor2.String(), dbtypes.NewDbCoins(amount2), timestamp2, 10), - dbtypes.NewDepositRow(1, depositor3.String(), dbtypes.NewDbCoins(amount3), timestamp3, 10), + dbtypes.NewDepositRow(1, depositor.String(), dbtypes.NewDbCoins(amount), timestamp1, txHash, 10), + dbtypes.NewDepositRow(1, depositor2.String(), dbtypes.NewDbCoins(amount2), timestamp2, txHash2, 10), + dbtypes.NewDepositRow(1, depositor3.String(), dbtypes.NewDbCoins(amount3), timestamp3, txHash3, 10), } var result []dbtypes.DepositRow err = suite.database.Sqlx.Select(&result, `SELECT * FROM proposal_deposit`) @@ -421,24 +424,20 @@ func (suite *DbTestSuite) TestBigDipperDb_SaveDeposits() { // ---------------------------------------------------------------------------------------------------------------- // Update values - amount = sdk.NewCoins(sdk.NewCoin("desmos", sdk.NewInt(10))) - amount2 = sdk.NewCoins(sdk.NewCoin("desmos", sdk.NewInt(20))) - amount3 = sdk.NewCoins(sdk.NewCoin("desmos", sdk.NewInt(30))) - deposit = []types.Deposit{ - types.NewDeposit(proposal.ID, depositor.String(), amount, timestamp1, 9), - types.NewDeposit(proposal.ID, depositor2.String(), amount2, timestamp2, 10), - types.NewDeposit(proposal.ID, depositor3.String(), amount3, timestamp3, 11), + types.NewDeposit(proposal.ID, depositor.String(), amount, timestamp1, "8E6EA32C656A6EED84132425533E897D458F1B877080DF842B068C4AS92WP01A", 9), + types.NewDeposit(proposal.ID, depositor2.String(), amount2, timestamp2, txHash2, 11), + types.NewDeposit(proposal.ID, depositor3.String(), sdk.NewCoins(sdk.NewCoin("desmos", sdk.NewInt(30))), timestamp3, txHash3, 11), } err = suite.database.SaveDeposits(deposit) suite.Require().NoError(err) expected = []dbtypes.DepositRow{ - dbtypes.NewDepositRow(1, depositor.String(), dbtypes.NewDbCoins( - sdk.NewCoins(sdk.NewCoin("desmos", sdk.NewInt(10000)))), timestamp1, 10), - dbtypes.NewDepositRow(1, depositor2.String(), dbtypes.NewDbCoins(amount2), timestamp2, 10), - dbtypes.NewDepositRow(1, depositor3.String(), dbtypes.NewDbCoins(amount3), timestamp3, 11), + dbtypes.NewDepositRow(1, depositor.String(), dbtypes.NewDbCoins(amount), timestamp1, txHash, 10), + dbtypes.NewDepositRow(1, depositor.String(), dbtypes.NewDbCoins(amount), timestamp1, "8E6EA32C656A6EED84132425533E897D458F1B877080DF842B068C4AS92WP01A", 9), + dbtypes.NewDepositRow(1, depositor2.String(), dbtypes.NewDbCoins(amount2), timestamp2, txHash2, 11), + dbtypes.NewDepositRow(1, depositor3.String(), dbtypes.NewDbCoins(sdk.NewCoins(sdk.NewCoin("desmos", sdk.NewInt(30)))), timestamp3, txHash3, 11), } result = []dbtypes.DepositRow{} @@ -461,54 +460,86 @@ func (suite *DbTestSuite) TestBigDipperDb_SaveVote() { timestamp := time.Date(2020, 1, 1, 15, 00, 00, 000, time.UTC) - vote := types.NewVote(1, voter.String(), govtypesv1.OptionYes, timestamp, 1) + vote := types.NewVote(1, voter.String(), govtypesv1.OptionYes, "0.5", timestamp, 1) err := suite.database.SaveVote(vote) suite.Require().NoError(err) - expected := dbtypes.NewVoteRow(int64(proposal.ID), voter.String(), govtypesv1.OptionYes.String(), timestamp, 1) + vote2 := types.NewVote(1, voter.String(), govtypesv1.OptionNo, "0.5", timestamp, 1) + err = suite.database.SaveVote(vote2) + suite.Require().NoError(err) + + expected := []dbtypes.VoteRow{ + dbtypes.NewVoteRow(int64(proposal.ID), voter.String(), govtypesv1.OptionYes.String(), "0.5", timestamp, 1), + dbtypes.NewVoteRow(int64(proposal.ID), voter.String(), govtypesv1.OptionNo.String(), "0.5", timestamp, 1), + } var result []dbtypes.VoteRow err = suite.database.SQL.Select(&result, `SELECT * FROM proposal_vote`) suite.Require().NoError(err) - suite.Require().Len(result, 1) - suite.Require().True(expected.Equals(result[0])) + suite.Require().Len(result, 2) + for i, r := range result { + suite.Require().True(expected[i].Equals(r)) + } // Update with lower height should not change option - vote = types.NewVote(1, voter.String(), govtypesv1.OptionNo, timestamp, 0) + vote = types.NewVote(1, voter.String(), govtypesv1.OptionYes, "0.7", timestamp, 0) err = suite.database.SaveVote(vote) suite.Require().NoError(err) + vote2 = types.NewVote(1, voter.String(), govtypesv1.OptionNo, "0.3", timestamp, 0) + err = suite.database.SaveVote(vote2) + suite.Require().NoError(err) + result = []dbtypes.VoteRow{} err = suite.database.SQL.Select(&result, `SELECT * FROM proposal_vote`) suite.Require().NoError(err) - suite.Require().Len(result, 1) - suite.Require().True(expected.Equals(result[0])) + suite.Require().Len(result, 2) + for i, r := range result { + suite.Require().True(expected[i].Equals(r)) + } // Update with same height should change option - vote = types.NewVote(1, voter.String(), govtypesv1.OptionAbstain, timestamp, 1) + vote = types.NewVote(1, voter.String(), govtypesv1.OptionYes, "0.6", timestamp, 1) err = suite.database.SaveVote(vote) suite.Require().NoError(err) - expected = dbtypes.NewVoteRow(int64(proposal.ID), voter.String(), govtypesv1.OptionAbstain.String(), timestamp, 1) + vote2 = types.NewVote(1, voter.String(), govtypesv1.OptionNo, "0.4", timestamp, 1) + err = suite.database.SaveVote(vote2) + suite.Require().NoError(err) + + expected = []dbtypes.VoteRow{ + dbtypes.NewVoteRow(int64(proposal.ID), voter.String(), govtypesv1.OptionYes.String(), "0.6", timestamp, 1), + dbtypes.NewVoteRow(int64(proposal.ID), voter.String(), govtypesv1.OptionNo.String(), "0.4", timestamp, 1), + } result = []dbtypes.VoteRow{} err = suite.database.Sqlx.Select(&result, `SELECT * FROM proposal_vote`) suite.Require().NoError(err) - suite.Require().Len(result, 1) - suite.Require().True(expected.Equals(result[0])) + suite.Require().Len(result, 2) + for i, r := range result { + suite.Require().True(expected[i].Equals(r)) + } // Update with higher height should change option - vote = types.NewVote(1, voter.String(), govtypesv1.OptionNoWithVeto, timestamp, 2) + vote = types.NewVote(1, voter.String(), govtypesv1.OptionYes, "0.6", timestamp, 3) err = suite.database.SaveVote(vote) suite.Require().NoError(err) - expected = dbtypes.NewVoteRow(int64(proposal.ID), voter.String(), govtypesv1.OptionNoWithVeto.String(), timestamp, 2) + vote2 = types.NewVote(1, voter.String(), govtypesv1.OptionNo, "0.4", timestamp, 3) + err = suite.database.SaveVote(vote2) + suite.Require().NoError(err) + expected = []dbtypes.VoteRow{ + dbtypes.NewVoteRow(int64(proposal.ID), voter.String(), govtypesv1.OptionYes.String(), "0.6", timestamp, 3), + dbtypes.NewVoteRow(int64(proposal.ID), voter.String(), govtypesv1.OptionNo.String(), "0.4", timestamp, 3), + } result = []dbtypes.VoteRow{} err = suite.database.Sqlx.Select(&result, `SELECT * FROM proposal_vote`) suite.Require().NoError(err) - suite.Require().Len(result, 1) - suite.Require().True(expected.Equals(result[0])) + suite.Require().Len(result, 2) + for i, r := range result { + suite.Require().True(expected[i].Equals(r)) + } } func (suite *DbTestSuite) TestBigDipperDb_SaveTallyResults() { @@ -681,7 +712,7 @@ func (suite *DbTestSuite) TestBigDipperDb_SaveProposalValidatorsStatusesSnapshot 1, validator1.GetConsAddr(), 100, - int(stakingtypes.Bonded), + stakingtypes.Bonded, false, 10, ), @@ -689,7 +720,7 @@ func (suite *DbTestSuite) TestBigDipperDb_SaveProposalValidatorsStatusesSnapshot 1, validator2.GetConsAddr(), 100, - int(stakingtypes.Unbonding), + stakingtypes.Unbonding, true, 10, ), @@ -730,7 +761,7 @@ func (suite *DbTestSuite) TestBigDipperDb_SaveProposalValidatorsStatusesSnapshot 1, validator1.GetConsAddr(), 10, - int(stakingtypes.Bonded), + stakingtypes.Bonded, true, 9, ), @@ -738,7 +769,7 @@ func (suite *DbTestSuite) TestBigDipperDb_SaveProposalValidatorsStatusesSnapshot 1, validator2.GetConsAddr(), 700, - int(stakingtypes.Unbonding), + stakingtypes.Unbonding, true, 9, ), @@ -779,7 +810,7 @@ func (suite *DbTestSuite) TestBigDipperDb_SaveProposalValidatorsStatusesSnapshot 1, validator1.GetConsAddr(), 10, - int(stakingtypes.Bonded), + stakingtypes.Bonded, true, 10, ), @@ -787,7 +818,7 @@ func (suite *DbTestSuite) TestBigDipperDb_SaveProposalValidatorsStatusesSnapshot 1, validator2.GetConsAddr(), 700, - int(stakingtypes.Unbonding), + stakingtypes.Unbonding, true, 10, ), @@ -828,7 +859,7 @@ func (suite *DbTestSuite) TestBigDipperDb_SaveProposalValidatorsStatusesSnapshot 1, validator1.GetConsAddr(), 100000, - int(stakingtypes.Unspecified), + stakingtypes.Unspecified, false, 11, ), @@ -836,7 +867,7 @@ func (suite *DbTestSuite) TestBigDipperDb_SaveProposalValidatorsStatusesSnapshot 1, validator2.GetConsAddr(), 700000, - int(stakingtypes.Unbonded), + stakingtypes.Unbonded, false, 11, ), diff --git a/database/schema/08-gov.sql b/database/schema/08-gov.sql index 350ab5b4c..9fd83ba1f 100644 --- a/database/schema/08-gov.sql +++ b/database/schema/08-gov.sql @@ -28,8 +28,9 @@ CREATE TABLE proposal_deposit depositor_address TEXT REFERENCES account (address), amount COIN[], timestamp TIMESTAMP, + transaction_hash TEXT NOT NULL, height BIGINT NOT NULL, - CONSTRAINT unique_deposit UNIQUE (proposal_id, depositor_address) + CONSTRAINT unique_deposit UNIQUE (proposal_id, depositor_address, transaction_hash) ); CREATE INDEX proposal_deposit_proposal_id_index ON proposal_deposit (proposal_id); CREATE INDEX proposal_deposit_depositor_address_index ON proposal_deposit (depositor_address); @@ -40,9 +41,10 @@ CREATE TABLE proposal_vote proposal_id INTEGER NOT NULL REFERENCES proposal (id), voter_address TEXT NOT NULL REFERENCES account (address), option TEXT NOT NULL, + weight TEXT NOT NULL, timestamp TIMESTAMP, height BIGINT NOT NULL, - CONSTRAINT unique_vote UNIQUE (proposal_id, voter_address) + CONSTRAINT unique_vote UNIQUE (proposal_id, voter_address, option) ); CREATE INDEX proposal_vote_proposal_id_index ON proposal_vote (proposal_id); CREATE INDEX proposal_vote_voter_address_index ON proposal_vote (voter_address); @@ -73,13 +75,13 @@ CREATE INDEX proposal_staking_pool_snapshot_proposal_id_index ON proposal_stakin CREATE TABLE proposal_validator_status_snapshot ( - id SERIAL PRIMARY KEY NOT NULL, + id SERIAL PRIMARY KEY NOT NULL, proposal_id INTEGER REFERENCES proposal (id), - validator_address TEXT NOT NULL REFERENCES validator (consensus_address), - voting_power BIGINT NOT NULL, - status INT NOT NULL, - jailed BOOLEAN NOT NULL, - height BIGINT NOT NULL, + validator_address TEXT NOT NULL REFERENCES validator (consensus_address), + voting_power BIGINT NOT NULL, + status INT NOT NULL, + jailed BOOLEAN NOT NULL, + height BIGINT NOT NULL, CONSTRAINT unique_validator_status_snapshot UNIQUE (proposal_id, validator_address) ); CREATE INDEX proposal_validator_status_snapshot_proposal_id_index ON proposal_validator_status_snapshot (proposal_id); diff --git a/database/staking_validators.go b/database/staking_validators.go index 04043cf1a..54d7d551a 100644 --- a/database/staking_validators.go +++ b/database/staking_validators.go @@ -456,24 +456,40 @@ VALUES ($1, $2, $3, $4, $5, $6, $7) ON CONFLICT DO NOTHING RETURNING id` return id, err } -// SaveDoubleSignEvidence saves the given double sign evidence inside the proper tables -func (db *Db) SaveDoubleSignEvidence(evidence types.DoubleSignEvidence) error { - voteA, err := db.saveDoubleSignVote(evidence.VoteA) - if err != nil { - return fmt.Errorf("error while storing double sign vote: %s", err) - } - - voteB, err := db.saveDoubleSignVote(evidence.VoteB) - if err != nil { - return fmt.Errorf("error while storing double sign vote: %s", err) +// SaveDoubleSignEvidences saves the given double sign evidences inside the database +func (db *Db) SaveDoubleSignEvidences(evidence []types.DoubleSignEvidence) error { + if len(evidence) == 0 { + return nil } stmt := ` INSERT INTO double_sign_evidence (height, vote_a_id, vote_b_id) -VALUES ($1, $2, $3) ON CONFLICT DO NOTHING` - _, err = db.SQL.Exec(stmt, evidence.Height, voteA, voteB) +VALUES ` + + var doubleSignEvidence []interface{} + + for i, ev := range evidence { + voteA, err := db.saveDoubleSignVote(ev.VoteA) + if err != nil { + return fmt.Errorf("error while storing double sign vote: %s", err) + } + + voteB, err := db.saveDoubleSignVote(ev.VoteB) + if err != nil { + return fmt.Errorf("error while storing double sign vote: %s", err) + } + + si := i * 3 + stmt += fmt.Sprintf("($%d,$%d,$%d),", si+1, si+2, si+3) + doubleSignEvidence = append(doubleSignEvidence, ev.Height, voteA, voteB) + + } + + stmt = stmt[:len(stmt)-1] // remove tailing "," + stmt += " ON CONFLICT DO NOTHING" + _, err := db.SQL.Exec(stmt, doubleSignEvidence...) if err != nil { - return fmt.Errorf("error while storing double sign evidence: %s", err) + return fmt.Errorf("error while storing double sign evidences: %s", err) } return nil diff --git a/database/staking_validators_test.go b/database/staking_validators_test.go index 3fce81e5e..8c91c7b42 100644 --- a/database/staking_validators_test.go +++ b/database/staking_validators_test.go @@ -722,9 +722,9 @@ func (suite *DbTestSuite) TestSaveDoubleVoteEvidence() { "cosmosvaloper1rcp29q3hpd246n6qak7jluqep4v006cdsc2kkl", "cosmosvalconspub1zcjduepq7mft6gfls57a0a42d7uhx656cckhfvtrlmw744jv4q0mvlv0dypskehfk8", ) - + var evidences []types.DoubleSignEvidence // Insert data - evidence := types.NewDoubleSignEvidence( + evidences = append(evidences, types.NewDoubleSignEvidence( 10, types.NewDoubleSignVote( int(tmtypes.PrevoteType), @@ -744,8 +744,9 @@ func (suite *DbTestSuite) TestSaveDoubleVoteEvidence() { 1, "A5m7SVuvZ8YNXcUfBKLgkeV+Vy5ea+7rPfzlbkEvHOPPce6B7A2CwOIbCmPSVMKUarUdta+HiyTV+IELaOYyDA==", ), + ), ) - err := suite.database.SaveDoubleSignEvidence(evidence) + err := suite.database.SaveDoubleSignEvidences(evidences) suite.Require().NoError(err) // Verify insertion diff --git a/database/types/consensus.go b/database/types/consensus.go index f16356a0a..4852979d0 100644 --- a/database/types/consensus.go +++ b/database/types/consensus.go @@ -87,3 +87,11 @@ type BlockRow struct { PreCommitsNum int64 `db:"pre_commits"` Timestamp time.Time `db:"timestamp"` } + +// ------------------------------------------------------------------------------------------------------------------- + +// BlockHeightAndTimestamp represents last block height and timestamp stored inside the database +type BlockHeightAndTimestamp struct { + Height int64 `db:"height"` + BlockTimestamp time.Time `db:"timestamp"` +} diff --git a/database/types/gov.go b/database/types/gov.go index 0d48bd6f8..004d42bcb 100644 --- a/database/types/gov.go +++ b/database/types/gov.go @@ -117,6 +117,7 @@ type VoteRow struct { ProposalID int64 `db:"proposal_id"` Voter string `db:"voter_address"` Option string `db:"option"` + Weight string `db:"weight"` Timestamp time.Time `db:"timestamp"` Height int64 `db:"height"` } @@ -126,6 +127,7 @@ func NewVoteRow( proposalID int64, voter string, option string, + weight string, timestamp time.Time, height int64, ) VoteRow { @@ -133,6 +135,7 @@ func NewVoteRow( ProposalID: proposalID, Voter: voter, Option: option, + Weight: weight, Timestamp: timestamp, Height: height, } @@ -143,17 +146,19 @@ func (w VoteRow) Equals(v VoteRow) bool { return w.ProposalID == v.ProposalID && w.Voter == v.Voter && w.Option == v.Option && + w.Weight == v.Weight && w.Timestamp.Equal(v.Timestamp) && w.Height == v.Height } // DepositRow represents a single row inside the deposit table type DepositRow struct { - ProposalID int64 `db:"proposal_id"` - Depositor string `db:"depositor_address"` - Amount DbCoins `db:"amount"` - Timestamp time.Time `db:"timestamp"` - Height int64 `db:"height"` + ProposalID int64 `db:"proposal_id"` + Depositor string `db:"depositor_address"` + Amount DbCoins `db:"amount"` + Timestamp time.Time `db:"timestamp"` + TransactionHash string `db:"transaction_hash"` + Height int64 `db:"height"` } // NewDepositRow allows to easily create a new NewDepositRow @@ -162,14 +167,16 @@ func NewDepositRow( depositor string, amount DbCoins, timestamp time.Time, + transactionHash string, height int64, ) DepositRow { return DepositRow{ - ProposalID: proposalID, - Depositor: depositor, - Amount: amount, - Timestamp: timestamp, - Height: height, + ProposalID: proposalID, + Depositor: depositor, + Amount: amount, + Timestamp: timestamp, + TransactionHash: transactionHash, + Height: height, } } @@ -179,6 +186,7 @@ func (w DepositRow) Equals(v DepositRow) bool { w.Depositor == v.Depositor && w.Amount.Equal(&v.Amount) && w.Timestamp.Equal(v.Timestamp) && + w.TransactionHash == v.TransactionHash && w.Height == v.Height } diff --git a/hasura/metadata/databases/bdjuno/tables/public_proposal_vote.yaml b/hasura/metadata/databases/bdjuno/tables/public_proposal_vote.yaml index 60f3694be..7b889106d 100644 --- a/hasura/metadata/databases/bdjuno/tables/public_proposal_vote.yaml +++ b/hasura/metadata/databases/bdjuno/tables/public_proposal_vote.yaml @@ -24,6 +24,7 @@ select_permissions: - proposal_id - voter_address - option + - weight - timestamp - height filter: {} diff --git a/modules/bank/handle_periodic_operations.go b/modules/bank/handle_periodic_operations.go index 856b3bb17..1435aab3d 100644 --- a/modules/bank/handle_periodic_operations.go +++ b/modules/bank/handle_periodic_operations.go @@ -27,15 +27,15 @@ func (m *Module) UpdateSupply() error { log.Trace().Str("module", "bank").Str("operation", "total supply"). Msg("updating total supply") - height, err := m.db.GetLastBlockHeight() + block, err := m.db.GetLastBlockHeightAndTimestamp() if err != nil { return fmt.Errorf("error while getting latest block height: %s", err) } - supply, err := m.keeper.GetSupply(height) + supply, err := m.keeper.GetSupply(block.Height) if err != nil { return err } - return m.db.SaveSupply(supply, height) + return m.db.SaveSupply(supply, block.Height) } diff --git a/modules/distribution/handle_periodic_operations.go b/modules/distribution/handle_periodic_operations.go index 38ff5e50e..3866d7444 100644 --- a/modules/distribution/handle_periodic_operations.go +++ b/modules/distribution/handle_periodic_operations.go @@ -25,10 +25,10 @@ func (m *Module) RegisterPeriodicOperations(scheduler *gocron.Scheduler) error { // GetLatestCommunityPool gets the latest community pool from the chain and stores inside the database func (m *Module) GetLatestCommunityPool() error { - height, err := m.db.GetLastBlockHeight() + block, err := m.db.GetLastBlockHeightAndTimestamp() if err != nil { return fmt.Errorf("error while getting latest block height: %s", err) } - return m.updateCommunityPool(height) + return m.updateCommunityPool(block.Height) } diff --git a/modules/gov/expected_modules.go b/modules/gov/expected_modules.go index 04e7c8ab5..876ab3dc8 100644 --- a/modules/gov/expected_modules.go +++ b/modules/gov/expected_modules.go @@ -1,16 +1,9 @@ package gov import ( - tmctypes "github.com/cometbft/cometbft/rpc/core/types" - stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" - "github.com/forbole/bdjuno/v4/types" ) -type AuthModule interface { - RefreshAccounts(height int64, addresses []string) error -} - type DistrModule interface { UpdateParams(height int64) error } @@ -25,10 +18,6 @@ type SlashingModule interface { } type StakingModule interface { - GetStakingPool(height int64) (*types.Pool, error) GetStakingPoolSnapshot(height int64) (*types.PoolSnapshot, error) - GetValidatorsWithStatus(height int64, status string) ([]stakingtypes.Validator, []types.Validator, error) - GetValidatorsVotingPowers(height int64, vals *tmctypes.ResultValidators) ([]types.ValidatorVotingPower, error) - GetValidatorsStatuses(height int64, validators []stakingtypes.Validator) ([]types.ValidatorStatus, error) UpdateParams(height int64) error } diff --git a/modules/gov/handle_block.go b/modules/gov/handle_block.go index 31fec7ad7..e16e34164 100644 --- a/modules/gov/handle_block.go +++ b/modules/gov/handle_block.go @@ -2,49 +2,69 @@ package gov import ( "fmt" - "time" + "strconv" juno "github.com/forbole/juno/v5/types" tmctypes "github.com/cometbft/cometbft/rpc/core/types" + abci "github.com/cometbft/cometbft/abci/types" + govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" + "github.com/rs/zerolog/log" ) // HandleBlock implements modules.BlockModule func (m *Module) HandleBlock( - b *tmctypes.ResultBlock, _ *tmctypes.ResultBlockResults, _ []*juno.Tx, vals *tmctypes.ResultValidators, + b *tmctypes.ResultBlock, blockResults *tmctypes.ResultBlockResults, _ []*juno.Tx, _ *tmctypes.ResultValidators, ) error { - err := m.updateProposals(b.Block.Height, b.Block.Time, vals) + + err := m.updateProposalsStatus(b.Block.Height, blockResults.EndBlockEvents) if err != nil { log.Error().Str("module", "gov").Int64("height", b.Block.Height). Err(err).Msg("error while updating proposals") } + return nil } -// updateProposals updates the proposals -func (m *Module) updateProposals(height int64, blockTime time.Time, blockVals *tmctypes.ResultValidators) error { - ids, err := m.db.GetOpenProposalsIds(blockTime) - if err != nil { - log.Error().Err(err).Str("module", "gov").Msg("error while getting open ids") +// updateProposalsStatus updates the status of proposals if they have been included in the EndBlockEvents +func (m *Module) updateProposalsStatus(height int64, events []abci.Event) error { + if len(events) == 0 { + return nil } - for _, id := range ids { - err = m.UpdateProposal(height, blockTime, id) + var ids []uint64 + // check if EndBlockEvents contains active_proposal event + eventsList := juno.FindEventsByType(events, govtypes.EventTypeActiveProposal) + if len(eventsList) == 0 { + return nil + } + + for _, event := range eventsList { + // find proposal ID + proposalID, err := juno.FindAttributeByKey(event, govtypes.AttributeKeyProposalID) if err != nil { - return fmt.Errorf("error while updating proposal: %s", err) + return fmt.Errorf("error while getting proposal ID from block events: %s", err) } - err = m.UpdateProposalValidatorStatusesSnapshot(height, blockVals, id) + // parse proposal ID from []byte to unit64 + id, err := strconv.ParseUint(proposalID.Value, 10, 64) if err != nil { - return fmt.Errorf("error while updating proposal validator statuses snapshots: %s", err) + return fmt.Errorf("error while parsing proposal id: %s", err) } - err = m.UpdateProposalStakingPoolSnapshot(height, blockVals, id) + // add proposal ID to ids array + ids = append(ids, id) + } + + // update status for proposals IDs stored in ids array + for _, id := range ids { + err := m.UpdateProposalStatus(height, id) if err != nil { - return fmt.Errorf("error while updating proposal validator statuses snapshots: %s", err) + return fmt.Errorf("error while updating proposal %d status: %s", id, err) } } + return nil } diff --git a/modules/gov/handle_genesis.go b/modules/gov/handle_genesis.go index 02e986d78..44a1c8d2d 100644 --- a/modules/gov/handle_genesis.go +++ b/modules/gov/handle_genesis.go @@ -75,6 +75,7 @@ func (m *Module) saveGenesisProposals(slice govtypesv1.Proposals, genDoc *tmtype "", proposal.TotalDeposit, genDoc.GenesisTime, + "", genDoc.InitialHeight, ) } diff --git a/modules/gov/handle_msg.go b/modules/gov/handle_msg.go index db7995eaf..1232c8082 100644 --- a/modules/gov/handle_msg.go +++ b/modules/gov/handle_msg.go @@ -3,13 +3,16 @@ package gov import ( "fmt" "strconv" + "strings" "time" "github.com/cosmos/cosmos-sdk/x/authz" "github.com/forbole/bdjuno/v4/types" + "google.golang.org/grpc/codes" sdk "github.com/cosmos/cosmos-sdk/types" + distrtypes "github.com/cosmos/cosmos-sdk/x/distribution/types" govtypesv1 "github.com/cosmos/cosmos-sdk/x/gov/types/v1" gov "github.com/cosmos/cosmos-sdk/x/gov/types" @@ -36,12 +39,15 @@ func (m *Module) HandleMsg(index int, msg sdk.Msg, tx *juno.Tx) error { case *govtypesv1.MsgVote: return m.handleMsgVote(tx, cosmosMsg) + + case *govtypesv1.MsgVoteWeighted: + return m.handleMsgVoteWeighted(tx, cosmosMsg) } return nil } -// handleMsgSubmitProposal allows to properly handle a handleMsgSubmitProposal +// handleMsgSubmitProposal allows to properly handle a MsgSubmitProposal func (m *Module) handleMsgSubmitProposal(tx *juno.Tx, index int, msg *govtypesv1.MsgSubmitProposal) error { // Get the proposal id event, err := tx.FindEventByType(index, gov.EventTypeSubmitProposal) @@ -62,7 +68,44 @@ func (m *Module) handleMsgSubmitProposal(tx *juno.Tx, index int, msg *govtypesv1 // Get the proposal proposal, err := m.source.Proposal(tx.Height, proposalID) if err != nil { - return fmt.Errorf("error while getting proposal: %s", err) + if strings.Contains(err.Error(), codes.NotFound.String()) { + // query the proposal details using the latest height stored in db + // to fix the rpc error returning code = NotFound desc = proposal x doesn't exist + block, err := m.db.GetLastBlockHeightAndTimestamp() + if err != nil { + return fmt.Errorf("error while getting latest block height: %s", err) + } + proposal, err = m.source.Proposal(block.Height, proposalID) + if err != nil { + return fmt.Errorf("error while getting proposal: %s", err) + } + } else { + return fmt.Errorf("error while getting proposal: %s", err) + } + } + + var addresses []types.Account + for _, msg := range proposal.Messages { + var sdkMsg sdk.Msg + err := m.cdc.UnpackAny(msg, &sdkMsg) + if err != nil { + return fmt.Errorf("error while unpacking proposal message: %s", err) + } + + switch msg := sdkMsg.(type) { + case *distrtypes.MsgCommunityPoolSpend: + addresses = append(addresses, types.NewAccount(msg.Recipient)) + case *govtypesv1.MsgExecLegacyContent: + content, ok := msg.Content.GetCachedValue().(*distrtypes.CommunityPoolSpendProposal) + if ok { + addresses = append(addresses, types.NewAccount(content.Recipient)) + } + } + } + + err = m.db.SaveAccounts(addresses) + if err != nil { + return fmt.Errorf("error while storing proposal recipient: %s", err) } // Store the proposal @@ -91,11 +134,11 @@ func (m *Module) handleMsgSubmitProposal(tx *juno.Tx, index int, msg *govtypesv1 } // Store the deposit - deposit := types.NewDeposit(proposal.Id, msg.Proposer, msg.InitialDeposit, txTimestamp, tx.Height) + 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 handleMsgDeposit +// 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) if err != nil { @@ -107,18 +150,43 @@ 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.Height), + types.NewDeposit(msg.ProposalId, msg.Depositor, deposit.Amount, txTimestamp, tx.TxHash, tx.Height), }) } -// handleMsgVote allows to properly handle a handleMsgVote +// handleMsgVote allows to properly handle a MsgVote func (m *Module) handleMsgVote(tx *juno.Tx, msg *govtypesv1.MsgVote) error { 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, txTimestamp, tx.Height) + vote := types.NewVote(msg.ProposalId, msg.Voter, msg.Option, "1.0", txTimestamp, tx.Height) + + err = m.db.SaveVote(vote) + if err != nil { + return fmt.Errorf("error while saving vote: %s", err) + } + + // update tally result for given proposal + return m.UpdateProposalTallyResult(msg.ProposalId, 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) + 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 m.db.SaveVote(vote) + // update tally result for given proposal + return m.UpdateProposalTallyResult(msg.ProposalId, tx.Height) } diff --git a/modules/gov/handle_periodic_operations.go b/modules/gov/handle_periodic_operations.go new file mode 100644 index 000000000..874d3ba42 --- /dev/null +++ b/modules/gov/handle_periodic_operations.go @@ -0,0 +1,32 @@ +package gov + +import ( + "fmt" + + "github.com/go-co-op/gocron" + "github.com/rs/zerolog/log" + + "github.com/forbole/bdjuno/v4/modules/utils" +) + +// RegisterPeriodicOperations implements modules.PeriodicOperationsModule +func (m *Module) RegisterPeriodicOperations(scheduler *gocron.Scheduler) error { + log.Debug().Str("module", "gov").Msg("setting up periodic tasks") + + // refresh proposal staking pool snapshots every 5 mins + // (set the same interval as staking pool periodic ops) + if _, err := scheduler.Every(5).Minutes().Do(func() { + utils.WatchMethod(m.UpdateProposalsStakingPoolSnapshot) + }); err != nil { + return fmt.Errorf("error while setting up gov period operations: %s", err) + } + + // refresh proposal tally results every 5 mins + if _, err := scheduler.Every(5).Minutes().Do(func() { + utils.WatchMethod(m.UpdateProposalsTallyResults) + }); err != nil { + return fmt.Errorf("error while setting up gov period operations: %s", err) + } + + return nil +} diff --git a/modules/gov/module.go b/modules/gov/module.go index 7c51496b0..01e3875ca 100644 --- a/modules/gov/module.go +++ b/modules/gov/module.go @@ -22,7 +22,6 @@ type Module struct { cdc codec.Codec db *database.Db source govsource.Source - authModule AuthModule distrModule DistrModule mintModule MintModule slashingModule SlashingModule @@ -32,7 +31,6 @@ type Module struct { // NewModule returns a new Module instance func NewModule( source govsource.Source, - authModule AuthModule, distrModule DistrModule, mintModule MintModule, slashingModule SlashingModule, @@ -43,7 +41,6 @@ func NewModule( return &Module{ cdc: cdc, source: source, - authModule: authModule, distrModule: distrModule, mintModule: mintModule, slashingModule: slashingModule, diff --git a/modules/gov/utils_proposal.go b/modules/gov/utils_proposal.go index e5c84e621..79f437f67 100644 --- a/modules/gov/utils_proposal.go +++ b/modules/gov/utils_proposal.go @@ -3,29 +3,29 @@ package gov import ( "fmt" "strings" - "time" "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" govtypesv1 "github.com/cosmos/cosmos-sdk/x/gov/types/v1" govtypesv1beta1 "github.com/cosmos/cosmos-sdk/x/gov/types/v1beta1" minttypes "github.com/cosmos/cosmos-sdk/x/mint/types" + "github.com/rs/zerolog/log" + + distrtypes "github.com/cosmos/cosmos-sdk/x/distribution/types" proposaltypes "github.com/cosmos/cosmos-sdk/x/params/types/proposal" slashingtypes "github.com/cosmos/cosmos-sdk/x/slashing/types" stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" upgradetypes "github.com/cosmos/cosmos-sdk/x/upgrade/types" "google.golang.org/grpc/codes" - tmctypes "github.com/cometbft/cometbft/rpc/core/types" - "github.com/forbole/bdjuno/v4/types" - distrtypes "github.com/cosmos/cosmos-sdk/x/distribution/types" - gov "github.com/cosmos/cosmos-sdk/x/gov/types" ) -func (m *Module) UpdateProposal(height int64, blockTime time.Time, id uint64) error { +// UpdateProposalStatus queries the latest details of given proposal ID, updates it's status +// in database and handles changes if the proposal has been passed. +func (m *Module) UpdateProposalStatus(height int64, id uint64) error { // Get the proposal proposal, err := m.source.Proposal(height, id) if err != nil { @@ -43,16 +43,6 @@ func (m *Module) UpdateProposal(height int64, blockTime time.Time, id uint64) er return fmt.Errorf("error while updating proposal status: %s", err) } - err = m.updateProposalTallyResult(proposal) - if err != nil { - return fmt.Errorf("error while updating proposal tally result: %s", err) - } - - err = m.updateAccounts(proposal) - if err != nil { - return fmt.Errorf("error while updating account: %s", err) - } - err = m.handlePassedProposal(proposal, height) if err != nil { return fmt.Errorf("error while handling passed proposals: %s", err) @@ -61,22 +51,53 @@ func (m *Module) UpdateProposal(height int64, blockTime time.Time, id uint64) er return nil } -func (m *Module) UpdateProposalValidatorStatusesSnapshot(height int64, blockVals *tmctypes.ResultValidators, id uint64) error { - err := m.updateProposalValidatorStatusesSnapshot(height, id, blockVals) +// updateProposalStatus updates given proposal status +func (m *Module) updateProposalStatus(proposal *govtypesv1.Proposal) error { + return m.db.UpdateProposal( + types.NewProposalUpdate( + proposal.Id, + proposal.Status.String(), + proposal.VotingStartTime, + proposal.VotingEndTime, + ), + ) +} + +// UpdateProposalsStakingPoolSnapshot updates +// staking pool snapshots for active proposals +func (m *Module) UpdateProposalsStakingPoolSnapshot() error { + log.Debug().Str("module", "gov").Msg("refreshing proposal staking pool snapshots") + block, err := m.db.GetLastBlockHeightAndTimestamp() if err != nil { - return fmt.Errorf("error while updating proposal validator statuses snapshot: %s", err) + return err + } + + ids, err := m.db.GetOpenProposalsIds(block.BlockTimestamp) + if err != nil { + log.Error().Err(err).Str("module", "gov").Msg("error while getting open proposals ids") + } + + for _, proposalID := range ids { + err = m.UpdateProposalStakingPoolSnapshot(block.Height, proposalID) + if err != nil { + return fmt.Errorf("error while updating proposal %d staking pool snapshots: %s", proposalID, err) + } } return nil } -func (m *Module) UpdateProposalStakingPoolSnapshot(height int64, blockVals *tmctypes.ResultValidators, id uint64) error { - err := m.updateProposalStakingPoolSnapshot(height, id) +// UpdateProposalStakingPoolSnapshot updates the staking pool snapshot associated with the gov +// proposal having the provided id +func (m *Module) UpdateProposalStakingPoolSnapshot(height int64, proposalID uint64) error { + pool, err := m.stakingModule.GetStakingPoolSnapshot(height) if err != nil { - return fmt.Errorf("error while updating proposal staking pool snapshot: %s", err) + return fmt.Errorf("error while getting staking pool: %s", err) } - return nil + return m.db.SaveProposalStakingPoolSnapshot( + types.NewProposalStakingPoolSnapshot(proposalID, pool), + ) } // updateDeletedProposalStatus updates the proposal having the given id by setting its status @@ -136,154 +157,48 @@ func (m *Module) handleParamChangeProposal(height int64, moduleName string) (err return nil } -// updateProposalStatus updates the given proposal status -func (m *Module) updateProposalStatus(proposal *govtypesv1.Proposal) error { - return m.db.UpdateProposal( - types.NewProposalUpdate( - proposal.Id, - proposal.Status.String(), - proposal.VotingStartTime, - proposal.VotingEndTime, - ), - ) -} - -// updateProposalTallyResult updates the tally result associated with the given proposal -func (m *Module) updateProposalTallyResult(proposal *govtypesv1.Proposal) error { - height, err := m.db.GetLastBlockHeight() +// UpdateProposalsTallyResults updates the tally for active proposals +func (m *Module) UpdateProposalsTallyResults() error { + log.Debug().Str("module", "gov").Msg("refreshing proposal tally results") + block, err := m.db.GetLastBlockHeightAndTimestamp() if err != nil { return err } - result, err := m.source.TallyResult(height, proposal.Id) + ids, err := m.db.GetOpenProposalsIds(block.BlockTimestamp) if err != nil { - return fmt.Errorf("error while getting tally result: %s", err) + log.Error().Err(err).Str("module", "gov").Msg("error while getting open proposals ids") } - return m.db.SaveTallyResults([]types.TallyResult{ - types.NewTallyResult( - proposal.Id, - result.YesCount, - result.AbstainCount, - result.NoCount, - result.NoWithVetoCount, - height, - ), - }) -} - -// updateAccounts updates any account that might be involved in the proposal (eg. fund community recipient) -func (m *Module) updateAccounts(proposal *govtypesv1.Proposal) error { - var addresses []string - for _, msg := range proposal.Messages { - var sdkMsg sdk.Msg - err := m.cdc.UnpackAny(msg, &sdkMsg) + for _, proposalID := range ids { + err = m.UpdateProposalTallyResult(proposalID, block.Height) if err != nil { - return fmt.Errorf("error while unpacking proposal message: %s", err) + return fmt.Errorf("error while updating proposal %d tally result : %s", proposalID, err) } - - switch msg := sdkMsg.(type) { - case *distrtypes.MsgCommunityPoolSpend: - addresses = append(addresses, msg.Recipient) - case *govtypesv1.MsgExecLegacyContent: - content, ok := msg.Content.GetCachedValue().(*distrtypes.CommunityPoolSpendProposal) - if ok { - addresses = append(addresses, content.Recipient) - } - } - } - - height, err := m.db.GetLastBlockHeight() - if err != nil { - return fmt.Errorf("error while getting last block height: %s", err) - } - - return m.authModule.RefreshAccounts(height, addresses) -} - -// updateProposalStakingPoolSnapshot updates the staking pool snapshot associated with the gov -// proposal having the provided id -func (m *Module) updateProposalStakingPoolSnapshot(height int64, proposalID uint64) error { - pool, err := m.stakingModule.GetStakingPoolSnapshot(height) - if err != nil { - return fmt.Errorf("error while getting staking pool: %s", err) } - return m.db.SaveProposalStakingPoolSnapshot( - types.NewProposalStakingPoolSnapshot(proposalID, pool), - ) + return nil } -// updateProposalValidatorStatusesSnapshot updates the snapshots of the various validators for -// the proposal having the given id -func (m *Module) updateProposalValidatorStatusesSnapshot( - height int64, proposalID uint64, blockVals *tmctypes.ResultValidators, -) error { - validators, _, err := m.stakingModule.GetValidatorsWithStatus(height, stakingtypes.Bonded.String()) +// UpdateProposalTallyResult updates the tally result associated with the given proposal ID +func (m *Module) UpdateProposalTallyResult(proposalID uint64, height int64) error { + result, err := m.source.TallyResult(height, proposalID) if err != nil { - return fmt.Errorf("error while getting validators with bonded status: %s", err) - } - - votingPowers, err := m.stakingModule.GetValidatorsVotingPowers(height, blockVals) - if err != nil { - return fmt.Errorf("error while getting validators voting powers: %s", err) - } - - statuses, err := m.stakingModule.GetValidatorsStatuses(height, validators) - if err != nil { - return fmt.Errorf("error while getting validator statuses: %s", err) + return fmt.Errorf("error while getting tally result: %s", err) } - var snapshots = make([]types.ProposalValidatorStatusSnapshot, len(validators)) - for index, validator := range validators { - consAddr, err := validator.GetConsAddr() - if err != nil { - return err - } - - status, err := findStatus(consAddr.String(), statuses) - if err != nil { - return fmt.Errorf("error while searching for status: %s", err) - } - - votingPower, err := findVotingPower(consAddr.String(), votingPowers) - if err != nil { - return fmt.Errorf("error while searching for voting power: %s", err) - } - - snapshots[index] = types.NewProposalValidatorStatusSnapshot( + return m.db.SaveTallyResults([]types.TallyResult{ + types.NewTallyResult( proposalID, - consAddr.String(), - votingPower.VotingPower, - status.Status, - status.Jailed, + result.YesCount, + result.AbstainCount, + result.NoCount, + result.NoWithVetoCount, height, - ) - } - - return m.db.SaveProposalValidatorsStatusesSnapshots(snapshots) -} - -func findVotingPower(consAddr string, powers []types.ValidatorVotingPower) (types.ValidatorVotingPower, error) { - for _, votingPower := range powers { - if votingPower.ConsensusAddress == consAddr { - return votingPower, nil - } - } - return types.ValidatorVotingPower{}, fmt.Errorf("voting power not found for validator with consensus address %s", consAddr) -} - -func findStatus(consAddr string, statuses []types.ValidatorStatus) (types.ValidatorStatus, error) { - for _, status := range statuses { - if status.ConsensusAddress == consAddr { - return status, nil - } - } - return types.ValidatorStatus{}, fmt.Errorf("cannot find status for validator with consensus address %s", consAddr) + ), + }) } -// handlePassedProposal handles a passed proposal by updating the proposal status and -// updating any other data that might be involved in the proposal (eg. fund community recipient) func (m *Module) handlePassedProposal(proposal *govtypesv1.Proposal, height int64) error { if proposal.Status != govtypesv1.StatusPassed { // If proposal status is not passed, do nothing diff --git a/modules/mint/handle_periodic_operations.go b/modules/mint/handle_periodic_operations.go index 725f048b5..0a926f8bf 100644 --- a/modules/mint/handle_periodic_operations.go +++ b/modules/mint/handle_periodic_operations.go @@ -29,16 +29,16 @@ func (m *Module) UpdateInflation() error { Str("operation", "inflation"). Msg("getting inflation data") - height, err := m.db.GetLastBlockHeight() + block, err := m.db.GetLastBlockHeightAndTimestamp() if err != nil { return err } // Get the inflation - inflation, err := m.source.GetInflation(height) + inflation, err := m.source.GetInflation(block.Height) if err != nil { return err } - return m.db.SaveInflation(inflation, height) + return m.db.SaveInflation(inflation, block.Height) } diff --git a/modules/registrar.go b/modules/registrar.go index fcd2089c5..08de32377 100644 --- a/modules/registrar.go +++ b/modules/registrar.go @@ -83,7 +83,7 @@ func (r *Registrar) BuildModules(ctx registrar.Context) jmodules.Modules { mintModule := mint.NewModule(sources.MintSource, cdc, db) slashingModule := slashing.NewModule(sources.SlashingSource, cdc, db) stakingModule := staking.NewModule(sources.StakingSource, cdc, db) - govModule := gov.NewModule(sources.GovSource, authModule, distrModule, mintModule, slashingModule, stakingModule, cdc, db) + govModule := gov.NewModule(sources.GovSource, distrModule, mintModule, slashingModule, stakingModule, cdc, db) upgradeModule := upgrade.NewModule(db, stakingModule) return []jmodules.Module{ diff --git a/modules/staking/handle_block.go b/modules/staking/handle_block.go index fa5df5e08..741e16b53 100644 --- a/modules/staking/handle_block.go +++ b/modules/staking/handle_block.go @@ -6,7 +6,6 @@ import ( "github.com/forbole/bdjuno/v4/types" - stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" juno "github.com/forbole/juno/v5/types" tmctypes "github.com/cometbft/cometbft/rpc/core/types" @@ -19,77 +18,30 @@ func (m *Module) HandleBlock( block *tmctypes.ResultBlock, res *tmctypes.ResultBlockResults, _ []*juno.Tx, vals *tmctypes.ResultValidators, ) error { // Update the validators - validators, err := m.updateValidators(block.Block.Height) + _, err := m.updateValidators(block.Block.Height) if err != nil { return fmt.Errorf("error while updating validators: %s", err) } - // Update the voting powers - go m.updateValidatorVotingPower(block.Block.Height, vals) - - // Update the validators statuses - go m.updateValidatorsStatus(block.Block.Height, validators) - // Updated the double sign evidences go m.updateDoubleSignEvidence(block.Block.Height, block.Block.Evidence.Evidence) return nil } -// updateValidatorsStatus updates all validators' statuses -func (m *Module) updateValidatorsStatus(height int64, validators []stakingtypes.Validator) { - log.Debug().Str("module", "staking").Int64("height", height). - Msg("updating validators statuses") - - statuses, err := m.GetValidatorsStatuses(height, validators) - if err != nil { - log.Error().Str("module", "staking").Err(err). - Int64("height", height). - Send() - return - } - - err = m.db.SaveValidatorsStatuses(statuses) - if err != nil { - log.Error().Str("module", "staking").Err(err). - Int64("height", height). - Msg("error while saving validators statuses") - } -} - -// updateValidatorVotingPower fetches and stores into the database all the current validators' voting powers -func (m *Module) updateValidatorVotingPower(height int64, vals *tmctypes.ResultValidators) { - log.Debug().Str("module", "staking").Int64("height", height). - Msg("updating validators voting powers") - - // Get the voting powers - votingPowers, err := m.GetValidatorsVotingPowers(height, vals) - if err != nil { - log.Error().Str("module", "staking").Err(err).Int64("height", height). - Msg("error while getting validators voting powers") - return - } - - // Save all the voting powers - err = m.db.SaveValidatorsVotingPowers(votingPowers) - if err != nil { - log.Error().Str("module", "staking").Err(err).Int64("height", height). - Msg("error while saving validators voting powers") - } -} - // updateDoubleSignEvidence updates the double sign evidence of all validators func (m *Module) updateDoubleSignEvidence(height int64, evidenceList tmtypes.EvidenceList) { log.Debug().Str("module", "staking").Int64("height", height). Msg("updating double sign evidence") + var evidences []types.DoubleSignEvidence for _, ev := range evidenceList { dve, ok := ev.(*tmtypes.DuplicateVoteEvidence) if !ok { continue } - evidence := types.NewDoubleSignEvidence( + evidences = append(evidences, types.NewDoubleSignEvidence( height, types.NewDoubleSignVote( int(dve.VoteA.Type), @@ -109,14 +61,15 @@ func (m *Module) updateDoubleSignEvidence(height int64, evidenceList tmtypes.Evi dve.VoteB.ValidatorIndex, hex.EncodeToString(dve.VoteB.Signature), ), + ), ) + } - err := m.db.SaveDoubleSignEvidence(evidence) - if err != nil { - log.Error().Str("module", "staking").Err(err).Int64("height", height). - Msg("error while saving double sign evidence") - return - } - + err := m.db.SaveDoubleSignEvidences(evidences) + if err != nil { + log.Error().Str("module", "staking").Err(err).Int64("height", height). + Msg("error while saving double sign evidence") + return } + } diff --git a/modules/staking/handle_msg.go b/modules/staking/handle_msg.go index 68e9d2a9b..d463a0d17 100644 --- a/modules/staking/handle_msg.go +++ b/modules/staking/handle_msg.go @@ -27,6 +27,19 @@ func (m *Module) HandleMsg(_ int, msg sdk.Msg, tx *juno.Tx) error { case *stakingtypes.MsgEditValidator: return m.handleEditValidator(tx.Height, cosmosMsg) + + // update validators statuses, voting power + // and proposals validators satatus snapshots + // when there is a voting power change + case *stakingtypes.MsgDelegate: + return m.UpdateValidatorStatuses() + + case *stakingtypes.MsgBeginRedelegate: + return m.UpdateValidatorStatuses() + + case *stakingtypes.MsgUndelegate: + return m.UpdateValidatorStatuses() + } return nil diff --git a/modules/staking/handle_periodic_operations.go b/modules/staking/handle_periodic_operations.go index 352ad8cab..646ca2d2b 100644 --- a/modules/staking/handle_periodic_operations.go +++ b/modules/staking/handle_periodic_operations.go @@ -20,19 +20,26 @@ func (m *Module) RegisterPeriodicOperations(scheduler *gocron.Scheduler) error { return fmt.Errorf("error while scheduling staking pool periodic operation: %s", err) } + // refresh proposal validators status snapshots every 5 mins + if _, err := scheduler.Every(5).Minutes().Do(func() { + utils.WatchMethod(m.UpdateValidatorStatuses) + }); err != nil { + return fmt.Errorf("error while setting up gov period operations: %s", err) + } + return nil } // UpdateStakingPool reads from the LCD the current staking pool and stores its value inside the database func (m *Module) UpdateStakingPool() error { - height, err := m.db.GetLastBlockHeight() + block, err := m.db.GetLastBlockHeightAndTimestamp() if err != nil { return fmt.Errorf("error while getting latest block height: %s", err) } - log.Debug().Str("module", "staking").Int64("height", height). + log.Debug().Str("module", "staking").Int64("height", block.Height). Msg("updating staking pool") - pool, err := m.GetStakingPool(height) + pool, err := m.GetStakingPool(block.Height) if err != nil { return fmt.Errorf("error while getting staking pool: %s", err) diff --git a/modules/staking/utils_validators.go b/modules/staking/utils_validators.go index e5a0d2e29..30ae17f40 100644 --- a/modules/staking/utils_validators.go +++ b/modules/staking/utils_validators.go @@ -3,9 +3,6 @@ package staking import ( "fmt" - tmctypes "github.com/cometbft/cometbft/rpc/core/types" - juno "github.com/forbole/juno/v5/types" - "github.com/forbole/bdjuno/v4/modules/staking/keybase" "github.com/forbole/bdjuno/v4/types" @@ -204,35 +201,120 @@ func (m *Module) GetValidatorsStatuses(height int64, validators []stakingtypes.V return statuses, nil } -func (m *Module) GetValidatorsVotingPowers(height int64, vals *tmctypes.ResultValidators) ([]types.ValidatorVotingPower, error) { - stakingVals, _, err := m.getValidators(height) +// UpdateValidatorStatuses allows to update validators status, voting power +// and active proposals validator status snapshots +func (m *Module) UpdateValidatorStatuses() error { + // get the latest block height from db + block, err := m.db.GetLastBlockHeightAndTimestamp() if err != nil { - return nil, err + return fmt.Errorf("error while getting latest block height from db: %s", err) + } + + validators, _, err := m.GetValidatorsWithStatus(block.Height, stakingtypes.Bonded.String()) + if err != nil { + return fmt.Errorf("error while getting validators with bonded status: %s", err) + } + + // update validators status and voting power in database + err = m.updateValidatorStatusAndVP(block.Height, validators) + if err != nil { + return fmt.Errorf("error while updating validators status and voting power: %s", err) + } + + // get all active proposals IDs from db + ids, err := m.db.GetOpenProposalsIds(block.BlockTimestamp) + if err != nil { + return fmt.Errorf("error while getting open proposals ids: %s", err) + } + + // update validator status snapshots for all proposals IDs + // returned from database + for _, id := range ids { + // update validator status snapshot for given height and proposal ID + err = m.updateProposalValidatorStatusSnapshot(block.Height, id, validators) + if err != nil { + return fmt.Errorf("error while updating proposal validator status snapshots: %s", err) + } } - votingPowers := make([]types.ValidatorVotingPower, len(stakingVals)) - for index, validator := range stakingVals { - // Get the validator consensus address + return nil +} + +// updateProposalValidatorStatusSnapshot updates validators snapshot for +// the proposal having the given id +func (m *Module) updateProposalValidatorStatusSnapshot( + height int64, proposalID uint64, validators []stakingtypes.Validator) error { + snapshots := make([]types.ProposalValidatorStatusSnapshot, len(validators)) + + for index, validator := range validators { consAddr, err := validator.GetConsAddr() if err != nil { - return nil, err + return err } - // Find the voting power of this validator - var votingPower int64 = 0 - for _, blockVal := range vals.Validators { - blockValConsAddr := juno.ConvertValidatorAddressToBech32String(blockVal.Address) - if blockValConsAddr == consAddr.String() { - votingPower = blockVal.VotingPower - } + snapshots[index] = types.NewProposalValidatorStatusSnapshot( + proposalID, + consAddr.String(), + validator.Tokens.Int64(), + validator.Status, + validator.Jailed, + height, + ) + } + + log.Debug().Str("module", "staking").Msg("refreshing proposal validator statuses snapshots") + + return m.db.SaveProposalValidatorsStatusesSnapshots(snapshots) +} + +// updateValidatorStatusAndVP updates validators status +// and validators voting power +func (m *Module) updateValidatorStatusAndVP(height int64, validators []stakingtypes.Validator) error { + votingPowers := make([]types.ValidatorVotingPower, len(validators)) + statuses := make([]types.ValidatorStatus, len(validators)) + + for index, validator := range validators { + consAddr, err := validator.GetConsAddr() + if err != nil { + return err } if found, _ := m.db.HasValidator(consAddr.String()); !found { continue } - votingPowers[index] = types.NewValidatorVotingPower(consAddr.String(), votingPower, height) + consPubKey, err := m.getValidatorConsPubKey(validator) + if err != nil { + return err + } + + votingPowers[index] = types.NewValidatorVotingPower(consAddr.String(), validator.Tokens.Int64(), height) + + statuses[index] = types.NewValidatorStatus( + consAddr.String(), + consPubKey.String(), + int(validator.GetStatus()), + validator.IsJailed(), + height, + ) + } + + log.Debug().Str("module", "staking").Msg("refreshing validator voting power") + // Save validators voting powers in db + err := m.db.SaveValidatorsVotingPowers(votingPowers) + if err != nil { + log.Error().Str("module", "staking").Err(err).Int64("height", height). + Msg("error while saving validators voting powers") + } + + log.Debug().Str("module", "staking").Msg("refreshing validator statuses") + // Save validators statuses in db + err = m.db.SaveValidatorsStatuses(statuses) + if err != nil { + log.Error().Str("module", "staking").Err(err). + Int64("height", height). + Msg("error while saving validators statuses") } - return votingPowers, nil + return nil } diff --git a/types/gov.go b/types/gov.go index a0ef2d83e..7836d2136 100644 --- a/types/gov.go +++ b/types/gov.go @@ -7,6 +7,7 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" govtypesv1 "github.com/cosmos/cosmos-sdk/x/gov/types/v1" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" ) const ( @@ -94,11 +95,12 @@ func NewProposalUpdate(proposalID uint64, status string, votingStartTime, voting // Deposit contains the data of a single deposit made towards a proposal type Deposit struct { - ProposalID uint64 - Depositor string - Amount sdk.Coins - Timestamp time.Time - Height int64 + ProposalID uint64 + Depositor string + Amount sdk.Coins + Timestamp time.Time + TransactionHash string + Height int64 } // NewDeposit return a new Deposit instance @@ -107,14 +109,16 @@ func NewDeposit( depositor string, amount sdk.Coins, timestamp time.Time, + transactionHash string, height int64, ) Deposit { return Deposit{ - ProposalID: proposalID, - Depositor: depositor, - Amount: amount, - Timestamp: timestamp, - Height: height, + ProposalID: proposalID, + Depositor: depositor, + Amount: amount, + Timestamp: timestamp, + TransactionHash: transactionHash, + Height: height, } } @@ -125,6 +129,7 @@ type Vote struct { ProposalID uint64 Voter string Option govtypesv1.VoteOption + Weight string Timestamp time.Time Height int64 } @@ -134,6 +139,7 @@ func NewVote( proposalID uint64, voter string, option govtypesv1.VoteOption, + weight string, timestamp time.Time, height int64, ) Vote { @@ -141,6 +147,7 @@ func NewVote( ProposalID: proposalID, Voter: voter, Option: option, + Weight: weight, Timestamp: timestamp, Height: height, } @@ -201,7 +208,7 @@ type ProposalValidatorStatusSnapshot struct { ProposalID uint64 ValidatorConsAddress string ValidatorVotingPower int64 - ValidatorStatus int + ValidatorStatus stakingtypes.BondStatus ValidatorJailed bool Height int64 } @@ -211,7 +218,7 @@ func NewProposalValidatorStatusSnapshot( proposalID uint64, validatorConsAddr string, validatorVotingPower int64, - validatorStatus int, + validatorStatus stakingtypes.BondStatus, validatorJailed bool, height int64, ) ProposalValidatorStatusSnapshot {