diff --git a/CHANGELOG.md b/CHANGELOG.md index ce15182fb..b1bf851a1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ### Changes #### Staking Module - ([\#443](https://github.com/forbole/bdjuno/pull/443)) Remove tombstone status from staking module(already stored in slashing module) +- ([\#455](https://github.com/forbole/bdjuno/pull/455)) Added `unbonding_tokens` and `staked_not_bonded_tokens` values to staking pool table + ## Version v3.2.0 ### Changes diff --git a/database/gov_test.go b/database/gov_test.go index 6050bfaa3..51a56c09c 100644 --- a/database/gov_test.go +++ b/database/gov_test.go @@ -508,6 +508,8 @@ func (suite *DbTestSuite) TestBigDipperDb_SaveProposalStakingPoolSnapshot() { snapshot := types.NewProposalStakingPoolSnapshot(1, types.NewPool( sdk.NewInt(100), sdk.NewInt(200), + sdk.NewInt(20), + sdk.NewInt(30), 10, )) err := suite.database.SaveProposalStakingPoolSnapshot(snapshot) @@ -530,6 +532,8 @@ func (suite *DbTestSuite) TestBigDipperDb_SaveProposalStakingPoolSnapshot() { err = suite.database.SaveProposalStakingPoolSnapshot(types.NewProposalStakingPoolSnapshot(1, types.NewPool( sdk.NewInt(200), sdk.NewInt(500), + sdk.NewInt(14), + sdk.NewInt(10), 9, ))) suite.Require().NoError(err) @@ -551,6 +555,8 @@ func (suite *DbTestSuite) TestBigDipperDb_SaveProposalStakingPoolSnapshot() { err = suite.database.SaveProposalStakingPoolSnapshot(types.NewProposalStakingPoolSnapshot(1, types.NewPool( sdk.NewInt(500), sdk.NewInt(1000), + sdk.NewInt(20), + sdk.NewInt(30), 10, ))) suite.Require().NoError(err) @@ -572,6 +578,8 @@ func (suite *DbTestSuite) TestBigDipperDb_SaveProposalStakingPoolSnapshot() { err = suite.database.SaveProposalStakingPoolSnapshot(types.NewProposalStakingPoolSnapshot(1, types.NewPool( sdk.NewInt(1000), sdk.NewInt(2000), + sdk.NewInt(80), + sdk.NewInt(40), 11, ))) suite.Require().NoError(err) diff --git a/database/schema/03-staking.sql b/database/schema/03-staking.sql index c5b9ecb4f..f2f1eb491 100644 --- a/database/schema/03-staking.sql +++ b/database/schema/03-staking.sql @@ -13,10 +13,12 @@ CREATE INDEX staking_params_height_index ON staking_params (height); CREATE TABLE staking_pool ( - one_row_id BOOLEAN NOT NULL DEFAULT TRUE PRIMARY KEY, - bonded_tokens TEXT NOT NULL, - not_bonded_tokens TEXT NOT NULL, - height BIGINT NOT NULL, + one_row_id BOOLEAN NOT NULL DEFAULT TRUE PRIMARY KEY, + bonded_tokens TEXT NOT NULL, + not_bonded_tokens TEXT NOT NULL, + unbonding_tokens TEXT NOT NULL, + staked_not_bonded_tokens TEXT NOT NULL, + height BIGINT NOT NULL, CHECK (one_row_id) ); CREATE INDEX staking_pool_height_index ON staking_pool (height); diff --git a/database/staking_pool.go b/database/staking_pool.go index 4e2e6eae2..4b8637239 100644 --- a/database/staking_pool.go +++ b/database/staking_pool.go @@ -9,15 +9,22 @@ import ( // SaveStakingPool allows to save for the given height the given stakingtypes pool func (db *Db) SaveStakingPool(pool *types.Pool) error { stmt := ` -INSERT INTO staking_pool (bonded_tokens, not_bonded_tokens, height) -VALUES ($1, $2, $3) +INSERT INTO staking_pool (bonded_tokens, not_bonded_tokens, unbonding_tokens, staked_not_bonded_tokens, height) +VALUES ($1, $2, $3, $4, $5) ON CONFLICT (one_row_id) DO UPDATE SET bonded_tokens = excluded.bonded_tokens, not_bonded_tokens = excluded.not_bonded_tokens, + unbonding_tokens = excluded.unbonding_tokens, + staked_not_bonded_tokens = excluded.staked_not_bonded_tokens, height = excluded.height WHERE staking_pool.height <= excluded.height` - _, err := db.Sql.Exec(stmt, pool.BondedTokens.String(), pool.NotBondedTokens.String(), pool.Height) + _, err := db.Sql.Exec(stmt, + pool.BondedTokens.String(), + pool.NotBondedTokens.String(), + pool.UnbondingTokens.String(), + pool.StakedNotBondedTokens.String(), + pool.Height) if err != nil { return fmt.Errorf("error while storing staking pool: %s", err) } diff --git a/database/staking_pool_test.go b/database/staking_pool_test.go index 290bd66cf..49b13f7b4 100644 --- a/database/staking_pool_test.go +++ b/database/staking_pool_test.go @@ -9,12 +9,12 @@ import ( func (suite *DbTestSuite) TestBigDipperDb_SaveStakingPool() { // Save the data - original := types.NewPool(sdk.NewInt(50), sdk.NewInt(100), 10) + original := types.NewPool(sdk.NewInt(50), sdk.NewInt(100), sdk.NewInt(5), sdk.NewInt(1), 10) err := suite.database.SaveStakingPool(original) suite.Require().NoError(err) // Verify the data - expected := dbtypes.NewStakingPoolRow(50, 100, 10) + expected := dbtypes.NewStakingPoolRow(50, 100, 5, 1, 10) var rows []dbtypes.StakingPoolRow err = suite.database.Sqlx.Select(&rows, `SELECT * FROM staking_pool`) @@ -25,7 +25,7 @@ func (suite *DbTestSuite) TestBigDipperDb_SaveStakingPool() { // ---------------------------------------------------------------------------------------------------------------- // Try updating using a lower height - pool := types.NewPool(sdk.NewInt(1), sdk.NewInt(1), 8) + pool := types.NewPool(sdk.NewInt(1), sdk.NewInt(1), sdk.NewInt(1), sdk.NewInt(1), 8) err = suite.database.SaveStakingPool(pool) suite.Require().NoError(err) @@ -39,12 +39,12 @@ func (suite *DbTestSuite) TestBigDipperDb_SaveStakingPool() { // ---------------------------------------------------------------------------------------------------------------- // Try updating with the same height - pool = types.NewPool(sdk.NewInt(1), sdk.NewInt(1), 10) + pool = types.NewPool(sdk.NewInt(1), sdk.NewInt(1), sdk.NewInt(1), sdk.NewInt(1), 10) err = suite.database.SaveStakingPool(pool) suite.Require().NoError(err) // Verify the data - expected = dbtypes.NewStakingPoolRow(1, 1, 10) + expected = dbtypes.NewStakingPoolRow(1, 1, 1, 1, 10) rows = []dbtypes.StakingPoolRow{} err = suite.database.Sqlx.Select(&rows, `SELECT * FROM staking_pool`) @@ -55,12 +55,12 @@ func (suite *DbTestSuite) TestBigDipperDb_SaveStakingPool() { // ---------------------------------------------------------------------------------------------------------------- // Try updating with a higher height - pool = types.NewPool(sdk.NewInt(1000000), sdk.NewInt(1000000), 20) + pool = types.NewPool(sdk.NewInt(1000000), sdk.NewInt(1000000), sdk.NewInt(20), sdk.NewInt(15), 20) err = suite.database.SaveStakingPool(pool) suite.Require().NoError(err) // Verify the data - expected = dbtypes.NewStakingPoolRow(1000000, 1000000, 20) + expected = dbtypes.NewStakingPoolRow(1000000, 1000000, 20, 15, 20) rows = []dbtypes.StakingPoolRow{} err = suite.database.Sqlx.Select(&rows, `SELECT * FROM staking_pool`) diff --git a/database/types/staking_pool.go b/database/types/staking_pool.go index 0fae3620a..b97b96a0c 100644 --- a/database/types/staking_pool.go +++ b/database/types/staking_pool.go @@ -2,19 +2,23 @@ package types // StakingPoolRow represents a single row inside the staking_pool table type StakingPoolRow struct { - OneRowID bool `db:"one_row_id"` - BondedTokens int64 `db:"bonded_tokens"` - NotBondedTokens int64 `db:"not_bonded_tokens"` - Height int64 `db:"height"` + OneRowID bool `db:"one_row_id"` + BondedTokens int64 `db:"bonded_tokens"` + NotBondedTokens int64 `db:"not_bonded_tokens"` + UnbondingTokens int64 `db:"unbonding_tokens"` + StakedNotBondedTokens int64 `db:"staked_not_bonded_tokens"` + Height int64 `db:"height"` } // NewStakingPoolRow allows to easily create a new StakingPoolRow -func NewStakingPoolRow(bondedTokens, notBondedTokens int64, height int64) StakingPoolRow { +func NewStakingPoolRow(bondedTokens, notBondedTokens, unbondingTokens, stakedNotBondedTokens int64, height int64) StakingPoolRow { return StakingPoolRow{ - OneRowID: true, - BondedTokens: bondedTokens, - NotBondedTokens: notBondedTokens, - Height: height, + OneRowID: true, + BondedTokens: bondedTokens, + NotBondedTokens: notBondedTokens, + UnbondingTokens: unbondingTokens, + StakedNotBondedTokens: stakedNotBondedTokens, + Height: height, } } @@ -22,5 +26,7 @@ func NewStakingPoolRow(bondedTokens, notBondedTokens int64, height int64) Stakin func (r StakingPoolRow) Equal(s StakingPoolRow) bool { return r.BondedTokens == s.BondedTokens && r.NotBondedTokens == s.NotBondedTokens && + r.UnbondingTokens == s.UnbondingTokens && + r.StakedNotBondedTokens == s.StakedNotBondedTokens && r.Height == s.Height } diff --git a/hasura/metadata/databases/bdjuno/tables/public_staking_pool.yaml b/hasura/metadata/databases/bdjuno/tables/public_staking_pool.yaml index b2dfa5e66..0f7c65e9e 100644 --- a/hasura/metadata/databases/bdjuno/tables/public_staking_pool.yaml +++ b/hasura/metadata/databases/bdjuno/tables/public_staking_pool.yaml @@ -8,5 +8,7 @@ select_permissions: - height - bonded_tokens - not_bonded_tokens + - unbonding_tokens + - staked_not_bonded_tokens filter: {} role: anonymous diff --git a/modules/staking/handle_block.go b/modules/staking/handle_block.go index b6dbcc6a0..65732df9c 100644 --- a/modules/staking/handle_block.go +++ b/modules/staking/handle_block.go @@ -33,9 +33,6 @@ func (m *Module) HandleBlock( // Updated the double sign evidences go m.updateDoubleSignEvidence(block.Block.Height, block.Block.Evidence.Evidence) - // Update the staking pool - go m.updateStakingPool(block.Block.Height) - return nil } @@ -123,23 +120,3 @@ func (m *Module) updateDoubleSignEvidence(height int64, evidenceList tmtypes.Evi } } - -// updateStakingPool reads from the LCD the current staking pool and stores its value inside the database -func (m *Module) updateStakingPool(height int64) { - log.Debug().Str("module", "staking").Int64("height", height). - Msg("updating staking pool") - - pool, err := m.GetStakingPool(height) - if err != nil { - log.Error().Str("module", "staking").Err(err).Int64("height", height). - Msg("error while getting staking pool") - return - } - - err = m.db.SaveStakingPool(pool) - if err != nil { - log.Error().Str("module", "staking").Err(err).Int64("height", height). - Msg("error while saving staking pool") - return - } -} diff --git a/modules/staking/handle_periodic_operations.go b/modules/staking/handle_periodic_operations.go new file mode 100644 index 000000000..395042f77 --- /dev/null +++ b/modules/staking/handle_periodic_operations.go @@ -0,0 +1,48 @@ +package staking + +import ( + "fmt" + + "github.com/go-co-op/gocron" + "github.com/rs/zerolog/log" + + "github.com/forbole/bdjuno/v3/modules/utils" +) + +// RegisterPeriodicOperations implements modules.PeriodicOperationsModule +func (m *Module) RegisterPeriodicOperations(scheduler *gocron.Scheduler) error { + log.Debug().Str("module", "staking").Msg("setting up periodic tasks") + + // Update the staking pool every 5 mins + if _, err := scheduler.Every(5).Minutes().Do(func() { + utils.WatchMethod(m.updateStakingPool) + }); err != nil { + return fmt.Errorf("error while scheduling staking pool periodic operation: %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() + if err != nil { + return fmt.Errorf("error while getting latest block height: %s", err) + } + log.Debug().Str("module", "staking").Int64("height", height). + Msg("updating staking pool") + + pool, err := m.GetStakingPool(height) + if err != nil { + return fmt.Errorf("error while getting staking pool: %s", err) + + } + + err = m.db.SaveStakingPool(pool) + if err != nil { + return fmt.Errorf("error while saving staking pool: %s", err) + + } + + return nil +} diff --git a/modules/staking/module.go b/modules/staking/module.go index 10f42c6be..c6c0d58aa 100644 --- a/modules/staking/module.go +++ b/modules/staking/module.go @@ -9,10 +9,11 @@ import ( ) var ( - _ modules.Module = &Module{} - _ modules.GenesisModule = &Module{} - _ modules.BlockModule = &Module{} - _ modules.MessageModule = &Module{} + _ modules.Module = &Module{} + _ modules.GenesisModule = &Module{} + _ modules.BlockModule = &Module{} + _ modules.MessageModule = &Module{} + _ modules.PeriodicOperationsModule = &Module{} ) // Module represents the x/staking module diff --git a/modules/staking/utils_staking_pool.go b/modules/staking/utils_staking_pool.go index 21991fce9..9164ff6ed 100644 --- a/modules/staking/utils_staking_pool.go +++ b/modules/staking/utils_staking_pool.go @@ -3,6 +3,9 @@ package staking import ( "fmt" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/query" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" "github.com/forbole/bdjuno/v3/types" ) @@ -12,5 +15,49 @@ func (m *Module) GetStakingPool(height int64) (*types.Pool, error) { return nil, fmt.Errorf("error while getting staking pool: %s", err) } - return types.NewPool(pool.BondedTokens, pool.NotBondedTokens, height), nil + validatorsList, err := m.db.GetValidators() + if err != nil { + return nil, fmt.Errorf("error while getting validators list: %s", err) + } + + var unbondingTokens = sdk.NewInt(0) + + for _, validator := range validatorsList { + // get list of all unbonding delegations for each validator + unbondingDelegations := m.getTotalUnbondingDelegationsFromValidator(height, validator.GetOperator()) + if len(unbondingDelegations) > 0 { + // calculate total value of unbonding tokens + for _, unbonding := range unbondingDelegations { + for _, entry := range unbonding.Entries { + unbondingTokens = unbondingTokens.Add(entry.Balance) + } + } + } + + } + + // calculate total value of staked tokens that are not bonded + stakedNotBondedTokens := pool.NotBondedTokens.Sub(unbondingTokens) + + return types.NewPool(pool.BondedTokens, pool.NotBondedTokens, unbondingTokens, stakedNotBondedTokens, height), nil +} + +func (m *Module) getTotalUnbondingDelegationsFromValidator(height int64, valOperatorAddress string) []stakingtypes.UnbondingDelegation { + var unbondingDelegations []stakingtypes.UnbondingDelegation + var nextKey []byte + var stop = false + for !stop { + res, err := m.source.GetUnbondingDelegationsFromValidator(height, + valOperatorAddress, + &query.PageRequest{Key: nextKey}, + ) + if err != nil { + return []stakingtypes.UnbondingDelegation{} + } + + nextKey = res.Pagination.NextKey + stop = len(res.Pagination.NextKey) == 0 + unbondingDelegations = append(unbondingDelegations, res.UnbondingResponses...) + } + return unbondingDelegations } diff --git a/types/staking_pool_params.go b/types/staking_pool_params.go index ced553090..708ed1ed0 100644 --- a/types/staking_pool_params.go +++ b/types/staking_pool_params.go @@ -7,17 +7,21 @@ import ( // Pool contains the data of the staking pool at the given height type Pool struct { - BondedTokens sdk.Int - NotBondedTokens sdk.Int - Height int64 + BondedTokens sdk.Int + NotBondedTokens sdk.Int + UnbondingTokens sdk.Int + StakedNotBondedTokens sdk.Int + Height int64 } // NewPool allows to build a new Pool instance -func NewPool(bondedTokens, notBondedTokens sdk.Int, height int64) *Pool { +func NewPool(bondedTokens, notBondedTokens, unbondingTokens, stakedNotBondedTokens sdk.Int, height int64) *Pool { return &Pool{ - BondedTokens: bondedTokens, - NotBondedTokens: notBondedTokens, - Height: height, + BondedTokens: bondedTokens, + NotBondedTokens: notBondedTokens, + UnbondingTokens: unbondingTokens, + StakedNotBondedTokens: stakedNotBondedTokens, + Height: height, } }