From 54807f33cd077410c2039b6252b5c67e7bf7c8ed Mon Sep 17 00:00:00 2001 From: Aaron <76254323+huichiaotsou@users.noreply.github.com> Date: Wed, 5 Oct 2022 05:40:18 +0800 Subject: [PATCH] feat: store software upgrade plan and refresh data at upgrade height (#467) Closes: #XXXX jira: https://forbole.atlassian.net/browse/BDU-604 Background: Sifchain hard-coded `validator minimum commission` in a new release and updated it with `software upgrade`. Without any `MsgEditValidator` involved, the `validator commission %` could not be updated and were then incorrect. This PR stores `software upgrade proposal's upgrade plan` and uses the `upgrade` module to check if there's an upgrade plan at each height, if yes it will proceed with neccessary updates of data. Currently it's only updating the validator infos (the rest of the data I believe we have `periodic task` or `handle block` which should be enough). We can add more in the future if similar cases pop up. --- *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 - [ ] 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 *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) --- cmd/parse/staking/validators.go | 13 +- database/gov.go | 60 ++++++++ database/gov_test.go | 130 ++++++++++++++++++ database/schema/12-upgrade.sql | 10 ++ database/types/upgrade.go | 21 +++ .../tables/public_software_upgrade_plan.yaml | 18 +++ .../databases/bdjuno/tables/tables.yaml | 1 + modules/gov/handle_msg.go | 7 - modules/gov/utils_proposal.go | 68 ++++++--- modules/registrar.go | 3 + modules/staking/utils_validators.go | 19 +++ modules/upgrade/expected_modules.go | 5 + modules/upgrade/handle_block.go | 45 ++++++ modules/upgrade/module.go | 31 +++++ 14 files changed, 391 insertions(+), 40 deletions(-) create mode 100644 database/schema/12-upgrade.sql create mode 100644 database/types/upgrade.go create mode 100644 hasura/metadata/databases/bdjuno/tables/public_software_upgrade_plan.yaml create mode 100644 modules/upgrade/expected_modules.go create mode 100644 modules/upgrade/handle_block.go create mode 100644 modules/upgrade/module.go diff --git a/cmd/parse/staking/validators.go b/cmd/parse/staking/validators.go index ffe67febe..2497bd786 100644 --- a/cmd/parse/staking/validators.go +++ b/cmd/parse/staking/validators.go @@ -41,18 +41,9 @@ func validatorsCmd(parseConfig *parsecmdtypes.Config) *cobra.Command { return fmt.Errorf("error while getting latest block height: %s", err) } - // Get all validators - validators, err := sources.StakingSource.GetValidatorsWithStatus(height, "") + err = stakingModule.RefreshAllValidatorInfos(height) if err != nil { - return fmt.Errorf("error while getting validators: %s", err) - } - - // Refresh each validator - for _, validator := range validators { - err = stakingModule.RefreshValidatorInfos(height, validator.OperatorAddress) - if err != nil { - return fmt.Errorf("error while refreshing validator: %s", err) - } + return fmt.Errorf("error while refreshing all validators infos: %s", err) } return nil diff --git a/database/gov.go b/database/gov.go index 923cbc518..97585a3ea 100644 --- a/database/gov.go +++ b/database/gov.go @@ -7,6 +7,7 @@ import ( codectypes "github.com/cosmos/cosmos-sdk/codec/types" govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" + upgradetypes "github.com/cosmos/cosmos-sdk/x/upgrade/types" "github.com/gogo/protobuf/proto" "github.com/forbole/bdjuno/v3/types" @@ -396,3 +397,62 @@ WHERE proposal_validator_status_snapshot.height <= excluded.height` return nil } + +// SaveSoftwareUpgradePlan allows to save the given software upgrade plan with its proposal id +func (db *Db) SaveSoftwareUpgradePlan(proposalID uint64, plan upgradetypes.Plan, height int64) error { + + stmt := ` +INSERT INTO software_upgrade_plan(proposal_id, plan_name, upgrade_height, info, height) +VALUES ($1, $2, $3, $4, $5) +ON CONFLICT (proposal_id) DO UPDATE SET + plan_name = excluded.plan_name, + upgrade_height = excluded.upgrade_height, + info = excluded.info, + height = excluded.height +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 nil +} + +// DeleteSoftwareUpgradePlan allows to delete a SoftwareUpgradePlan with proposal ID +func (db *Db) DeleteSoftwareUpgradePlan(proposalID uint64) error { + stmt := `DELETE FROM software_upgrade_plan WHERE proposal_id = $1` + + _, err := db.Sql.Exec(stmt, proposalID) + if err != nil { + return fmt.Errorf("error while deleting software upgrade plan: %s", err) + } + + return nil +} + +// CheckSoftwareUpgradePlan returns true if an upgrade is scheduled at the given height +func (db *Db) CheckSoftwareUpgradePlan(upgradeHeight int64) (bool, error) { + var exist bool + + stmt := `SELECT EXISTS (SELECT 1 FROM software_upgrade_plan WHERE upgrade_height=$1)` + err := db.Sql.QueryRow(stmt, upgradeHeight).Scan(&exist) + if err != nil { + return exist, fmt.Errorf("error while checking software upgrade plan existence: %s", err) + } + + return exist, nil +} + +// TruncateSoftwareUpgradePlan delete software upgrade plans once the upgrade height passed +func (db *Db) TruncateSoftwareUpgradePlan(height int64) error { + stmt := `DELETE FROM software_upgrade_plan WHERE upgrade_height <= $1` + + _, err := db.Sql.Exec(stmt, height) + if err != nil { + return fmt.Errorf("error while deleting software upgrade plan: %s", err) + } + + return nil +} diff --git a/database/gov_test.go b/database/gov_test.go index 5f536b17e..bcffd5fdf 100644 --- a/database/gov_test.go +++ b/database/gov_test.go @@ -5,6 +5,7 @@ import ( "time" stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" + upgradetypes "github.com/cosmos/cosmos-sdk/x/upgrade/types" codectypes "github.com/cosmos/cosmos-sdk/codec/types" "github.com/gogo/protobuf/proto" @@ -825,3 +826,132 @@ func (suite *DbTestSuite) TestBigDipperDb_SaveProposalValidatorsStatusesSnapshot ), }) } + +func (suite *DbTestSuite) TestBigDipperDb_SaveSoftwareUpgradePlan() { + _ = suite.getProposalRow(1) + + // ---------------------------------------------------------------------------------------------------------------- + // Save software upgrade plan at height 10 with upgrade height at 100 + var plan = upgradetypes.Plan{ + Name: "name", + Height: 100, + Info: "info", + } + + err := suite.database.SaveSoftwareUpgradePlan(1, plan, 10) + suite.Require().NoError(err) + + var rows []dbtypes.SoftwareUpgradePlanRow + err = suite.database.Sqlx.Select(&rows, `SELECT * FROM software_upgrade_plan`) + suite.Require().NoError(err) + suite.Require().Len(rows, 1) + suite.Require().Equal(rows, []dbtypes.SoftwareUpgradePlanRow{ + dbtypes.NewSoftwareUpgradePlanRow(1, plan.Name, plan.Height, plan.Info, 10), + }) + + // ---------------------------------------------------------------------------------------------------------------- + // Update software upgrade plan with lower height + planEdit1 := upgradetypes.Plan{ + Name: "name_edit_1", + Height: 101, + Info: "info_edit_1", + } + + err = suite.database.SaveSoftwareUpgradePlan(1, planEdit1, 9) + suite.Require().NoError(err) + + rows = []dbtypes.SoftwareUpgradePlanRow{} + err = suite.database.Sqlx.Select(&rows, `SELECT * FROM software_upgrade_plan`) + suite.Require().NoError(err) + suite.Require().Len(rows, 1) + suite.Require().Equal(rows, []dbtypes.SoftwareUpgradePlanRow{ + dbtypes.NewSoftwareUpgradePlanRow(1, plan.Name, plan.Height, plan.Info, 10), + }) + + // ---------------------------------------------------------------------------------------------------------------- + // Update software upgrade plan with same height + planEdit2 := upgradetypes.Plan{ + Name: "name_edit_2", + Height: 102, + Info: "info_edit_2", + } + + err = suite.database.SaveSoftwareUpgradePlan(1, planEdit2, 10) + suite.Require().NoError(err) + + rows = []dbtypes.SoftwareUpgradePlanRow{} + err = suite.database.Sqlx.Select(&rows, `SELECT * FROM software_upgrade_plan`) + suite.Require().NoError(err) + suite.Require().Len(rows, 1) + suite.Require().Equal(rows, []dbtypes.SoftwareUpgradePlanRow{ + dbtypes.NewSoftwareUpgradePlanRow(1, planEdit2.Name, planEdit2.Height, planEdit2.Info, 10), + }) + + // ---------------------------------------------------------------------------------------------------------------- + // Update software upgrade plan with higher height + planEdit3 := upgradetypes.Plan{ + Name: "name_edit_3", + Height: 103, + Info: "info_edit_3", + } + + err = suite.database.SaveSoftwareUpgradePlan(1, planEdit3, 11) + suite.Require().NoError(err) + + rows = []dbtypes.SoftwareUpgradePlanRow{} + err = suite.database.Sqlx.Select(&rows, `SELECT * FROM software_upgrade_plan`) + suite.Require().NoError(err) + suite.Require().Len(rows, 1) + suite.Require().Equal(rows, []dbtypes.SoftwareUpgradePlanRow{ + dbtypes.NewSoftwareUpgradePlanRow(1, planEdit3.Name, planEdit3.Height, planEdit3.Info, 11), + }) +} + +func (suite *DbTestSuite) TestBigDipperDb_DeleteSoftwareUpgradePlan() { + _ = suite.getProposalRow(1) + + // Save software upgrade plan at height 10 with upgrade height at 100 + var plan = upgradetypes.Plan{ + Name: "name", + Height: 100, + Info: "info", + } + + err := suite.database.SaveSoftwareUpgradePlan(1, plan, 10) + suite.Require().NoError(err) + + // Delete software upgrade plan + err = suite.database.DeleteSoftwareUpgradePlan(1) + suite.Require().NoError(err) + + var rows []dbtypes.SoftwareUpgradePlanRow + err = suite.database.Sqlx.Select(&rows, `SELECT * FROM software_upgrade_plan`) + suite.Require().NoError(err) + suite.Require().Len(rows, 0) + +} + +func (suite *DbTestSuite) TestBigDipperDb_CheckSoftwareUpgradePlan() { + _ = suite.getProposalRow(1) + + // Save software upgrade plan at height 10 with upgrade height at 100 + var plan = upgradetypes.Plan{ + Name: "name", + // the Height here is the upgrade height + Height: 100, + Info: "info", + } + + err := suite.database.SaveSoftwareUpgradePlan(1, plan, 10) + suite.Require().NoError(err) + + // Check software upgrade plan at existing height + exist, err := suite.database.CheckSoftwareUpgradePlan(100) + suite.Require().NoError(err) + suite.Require().Equal(true, exist) + + // Check software upgrade plan at non-existing height + exist, err = suite.database.CheckSoftwareUpgradePlan(11) + suite.Require().NoError(err) + suite.Require().Equal(false, exist) +} diff --git a/database/schema/12-upgrade.sql b/database/schema/12-upgrade.sql new file mode 100644 index 000000000..982be6a2f --- /dev/null +++ b/database/schema/12-upgrade.sql @@ -0,0 +1,10 @@ +CREATE TABLE software_upgrade_plan +( + proposal_id INTEGER REFERENCES proposal (id) UNIQUE, + plan_name TEXT NOT NULL, + upgrade_height BIGINT NOT NULL, + info TEXT NOT NULL, + height BIGINT NOT NULL +); +CREATE INDEX software_upgrade_plan_proposal_id_index ON software_upgrade_plan (proposal_id); +CREATE INDEX software_upgrade_plan_height_index ON software_upgrade_plan (height); diff --git a/database/types/upgrade.go b/database/types/upgrade.go new file mode 100644 index 000000000..c3abc7407 --- /dev/null +++ b/database/types/upgrade.go @@ -0,0 +1,21 @@ +package types + +type SoftwareUpgradePlanRow struct { + ProposalID uint64 `db:"proposal_id"` + PlanName string `db:"plan_name"` + UpgradeHeight int64 `db:"upgrade_height"` + Info string `db:"info"` + Height int64 `db:"height"` +} + +func NewSoftwareUpgradePlanRow( + proposalID uint64, planName string, upgradeHeight int64, info string, height int64, +) SoftwareUpgradePlanRow { + return SoftwareUpgradePlanRow{ + ProposalID: proposalID, + PlanName: planName, + UpgradeHeight: upgradeHeight, + Info: info, + Height: height, + } +} diff --git a/hasura/metadata/databases/bdjuno/tables/public_software_upgrade_plan.yaml b/hasura/metadata/databases/bdjuno/tables/public_software_upgrade_plan.yaml new file mode 100644 index 000000000..d7ab38f50 --- /dev/null +++ b/hasura/metadata/databases/bdjuno/tables/public_software_upgrade_plan.yaml @@ -0,0 +1,18 @@ +table: + name: software_upgrade_plan + schema: public +object_relationships: +- name: proposal + using: + foreign_key_constraint_on: proposal_id +select_permissions: +- permission: + allow_aggregations: true + columns: + - proposal_id + - plan_name + - upgrade_height + - info + - height + filter: {} + role: anonymous diff --git a/hasura/metadata/databases/bdjuno/tables/tables.yaml b/hasura/metadata/databases/bdjuno/tables/tables.yaml index 919fa44cd..920a84a31 100644 --- a/hasura/metadata/databases/bdjuno/tables/tables.yaml +++ b/hasura/metadata/databases/bdjuno/tables/tables.yaml @@ -23,6 +23,7 @@ - "!include public_proposal_validator_status_snapshot.yaml" - "!include public_proposal_vote.yaml" - "!include public_slashing_params.yaml" +- "!include public_software_upgrade_plan.yaml" - "!include public_staking_params.yaml" - "!include public_staking_pool.yaml" - "!include public_supply.yaml" diff --git a/modules/gov/handle_msg.go b/modules/gov/handle_msg.go index 50798df75..0b4a88078 100644 --- a/modules/gov/handle_msg.go +++ b/modules/gov/handle_msg.go @@ -55,13 +55,6 @@ func (m *Module) handleMsgSubmitProposal(tx *juno.Tx, index int, msg *govtypes.M return fmt.Errorf("error while getting proposal: %s", err) } - // Unpack the content - var content govtypes.Content - err = m.cdc.UnpackAny(proposal.Content, &content) - if err != nil { - return fmt.Errorf("error while unpacking proposal content: %s", err) - } - // Store the proposal proposalObj := types.NewProposal( proposal.ProposalId, diff --git a/modules/gov/utils_proposal.go b/modules/gov/utils_proposal.go index 61e0246d4..61e9fa793 100644 --- a/modules/gov/utils_proposal.go +++ b/modules/gov/utils_proposal.go @@ -8,6 +8,7 @@ import ( 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" tmctypes "github.com/tendermint/tendermint/rpc/core/types" "google.golang.org/grpc/codes" @@ -31,11 +32,6 @@ func (m *Module) UpdateProposal(height int64, id uint64) error { return fmt.Errorf("error while getting proposal: %s", err) } - err = m.handleParamChangeProposal(height, proposal) - if err != nil { - return fmt.Errorf("error while updating params from ParamChangeProposal: %s", err) - } - err = m.updateProposalStatus(proposal) if err != nil { return fmt.Errorf("error while updating proposal status: %s", err) @@ -50,6 +46,12 @@ func (m *Module) UpdateProposal(height int64, id uint64) error { 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) + } + return nil } @@ -86,23 +88,7 @@ func (m *Module) updateDeletedProposalStatus(id uint64) error { } // handleParamChangeProposal updates params to the corresponding modules if a ParamChangeProposal has passed -func (m *Module) handleParamChangeProposal(height int64, proposal govtypes.Proposal) error { - if proposal.Status != govtypes.StatusPassed { - // If the status of ParamChangeProposal is not passed, do nothing - return nil - } - - var content govtypes.Content - err := m.db.EncodingConfig.Marshaler.UnpackAny(proposal.Content, &content) - if err != nil { - return fmt.Errorf("error while handling ParamChangeProposal: %s", err) - } - - paramChangeProposal, ok := content.(*proposaltypes.ParameterChangeProposal) - if !ok { - return nil - } - +func (m *Module) handleParamChangeProposal(height int64, paramChangeProposal *proposaltypes.ParameterChangeProposal) (err error) { for _, change := range paramChangeProposal.Changes { // Update the params for corresponding modules switch change.Subspace { @@ -279,3 +265,41 @@ func findStatus(consAddr string, statuses []types.ValidatorStatus) (types.Valida } return types.ValidatorStatus{}, fmt.Errorf("cannot find status for validator with consensus address %s", consAddr) } + +func (m *Module) handlePassedProposal(proposal govtypes.Proposal, height int64) error { + if proposal.Status != govtypes.StatusPassed { + // If proposal status is not passed, do nothing + return nil + } + + // Unpack proposal + var content govtypes.Content + err := m.db.EncodingConfig.Marshaler.UnpackAny(proposal.Content, &content) + if err != nil { + return fmt.Errorf("error while handling ParamChangeProposal: %s", err) + } + + switch p := content.(type) { + case *proposaltypes.ParameterChangeProposal: + // Update params while ParameterChangeProposal passed + err = m.handleParamChangeProposal(height, p) + if err != nil { + return fmt.Errorf("error while updating params from ParamChangeProposal: %s", err) + } + + case *upgradetypes.SoftwareUpgradeProposal: + // Store software upgrade plan while SoftwareUpgradeProposal passed + err = m.db.SaveSoftwareUpgradePlan(proposal.ProposalId, p.Plan, height) + if err != nil { + return fmt.Errorf("error while storing software upgrade plan: %s", err) + } + + case *upgradetypes.CancelSoftwareUpgradeProposal: + // Delete software upgrade plan while CancelSoftwareUpgradeProposal passed + err = m.db.DeleteSoftwareUpgradePlan(proposal.ProposalId) + if err != nil { + return fmt.Errorf("error while deleting software upgrade plan: %s", err) + } + } + return nil +} diff --git a/modules/registrar.go b/modules/registrar.go index 2e5d41cc7..b1848080c 100644 --- a/modules/registrar.go +++ b/modules/registrar.go @@ -30,6 +30,7 @@ import ( "github.com/forbole/bdjuno/v3/modules/modules" "github.com/forbole/bdjuno/v3/modules/pricefeed" "github.com/forbole/bdjuno/v3/modules/staking" + "github.com/forbole/bdjuno/v3/modules/upgrade" ) // UniqueAddressesParser returns a wrapper around the given parser that removes all duplicated addresses @@ -81,6 +82,7 @@ func (r *Registrar) BuildModules(ctx registrar.Context) jmodules.Modules { 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) + upgradeModule := upgrade.NewModule(db, stakingModule) return []jmodules.Module{ messages.NewModule(r.parser, cdc, ctx.Database), @@ -100,5 +102,6 @@ func (r *Registrar) BuildModules(ctx registrar.Context) jmodules.Modules { pricefeed.NewModule(ctx.JunoConfig, cdc, db), slashingModule, stakingModule, + upgradeModule, } } diff --git a/modules/staking/utils_validators.go b/modules/staking/utils_validators.go index c96675e8e..a8307c38e 100644 --- a/modules/staking/utils_validators.go +++ b/modules/staking/utils_validators.go @@ -80,6 +80,25 @@ func (m *Module) convertValidatorDescription( // -------------------------------------------------------------------------------------------------------------------- +// RefreshAllValidatorInfos refreshes the info of all the validators at the given height +func (m *Module) RefreshAllValidatorInfos(height int64) error { + // Get all validators + validators, err := m.source.GetValidatorsWithStatus(height, "") + if err != nil { + return fmt.Errorf("error while getting validators: %s", err) + } + + // Refresh each validator + for _, validator := range validators { + err = m.RefreshValidatorInfos(height, validator.OperatorAddress) + if err != nil { + return fmt.Errorf("error while refreshing validator: %s", err) + } + } + + return nil +} + // RefreshValidatorInfos refreshes the info for the validator with the given operator address at the provided height func (m *Module) RefreshValidatorInfos(height int64, valOper string) error { stakingValidator, err := m.source.GetValidator(height, valOper) diff --git a/modules/upgrade/expected_modules.go b/modules/upgrade/expected_modules.go new file mode 100644 index 000000000..1ee349204 --- /dev/null +++ b/modules/upgrade/expected_modules.go @@ -0,0 +1,5 @@ +package upgrade + +type StakingModule interface { + RefreshAllValidatorInfos(height int64) error +} diff --git a/modules/upgrade/handle_block.go b/modules/upgrade/handle_block.go new file mode 100644 index 000000000..9a588b71f --- /dev/null +++ b/modules/upgrade/handle_block.go @@ -0,0 +1,45 @@ +package upgrade + +import ( + "fmt" + + "github.com/forbole/juno/v3/types" + + tmctypes "github.com/tendermint/tendermint/rpc/core/types" +) + +// HandleBlock implements modules.Module +func (m *Module) HandleBlock( + b *tmctypes.ResultBlock, _ *tmctypes.ResultBlockResults, _ []*types.Tx, _ *tmctypes.ResultValidators, +) error { + err := m.refreshDataUponSoftwareUpgrade(b.Block.Height) + if err != nil { + return fmt.Errorf("error while refreshing data upon software upgrade: %s", err) + } + + return nil +} + +func (m *Module) refreshDataUponSoftwareUpgrade(height int64) error { + exist, err := m.db.CheckSoftwareUpgradePlan(height) + if err != nil { + return fmt.Errorf("error while checking software upgrade plan existence: %s", err) + } + if !exist { + return nil + } + + // Refresh validator infos + err = m.stakingModule.RefreshAllValidatorInfos(height) + if err != nil { + return fmt.Errorf("error while refreshing validator infos upon software upgrade: %s", err) + } + + // Delete plan after refreshing data + err = m.db.TruncateSoftwareUpgradePlan(height) + if err != nil { + return fmt.Errorf("error while truncating software upgrade plan: %s", err) + } + + return nil +} diff --git a/modules/upgrade/module.go b/modules/upgrade/module.go new file mode 100644 index 000000000..6576bff4b --- /dev/null +++ b/modules/upgrade/module.go @@ -0,0 +1,31 @@ +package upgrade + +import ( + "github.com/forbole/bdjuno/v3/database" + + "github.com/forbole/juno/v3/modules" +) + +var ( + _ modules.Module = &Module{} + _ modules.BlockModule = &Module{} +) + +// Module represents the x/upgrade module +type Module struct { + db *database.Db + stakingModule StakingModule +} + +// NewModule builds a new Module instance +func NewModule(db *database.Db, stakingModule StakingModule) *Module { + return &Module{ + stakingModule: stakingModule, + db: db, + } +} + +// Name implements modules.Module +func (m *Module) Name() string { + return "upgrade" +}