diff --git a/database/bank.go b/database/bank.go index 91bfa3cab..e211c75b1 100644 --- a/database/bank.go +++ b/database/bank.go @@ -6,6 +6,7 @@ import ( dbtypes "github.com/forbole/callisto/v4/database/types" sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/forbole/callisto/v4/types" "github.com/lib/pq" ) @@ -26,3 +27,63 @@ WHERE supply.height <= excluded.height` return nil } + +func (db *Db) SaveTokenHolder(tokens map[string]int, height int64) error { + if len(tokens) == 0 { + return nil + } + + query := `INSERT INTO token_holder (denom, num_holder, height) VALUES` + + var param []interface{} + i := 0 + for denom, amount := range tokens { + vi := i * 3 + query += fmt.Sprintf("($%d,$%d,$%d),", vi+1, vi+2, vi+3) + param = append(param, denom, amount, height) + i++ + } + + query = query[:len(query)-1] // Remove trailing "," + query += ` +ON CONFLICT (denom) DO UPDATE + SET num_holder = excluded.num_holder, + height = excluded.height +WHERE token_holder.height <= excluded.height` + + _, err := db.SQL.Exec(query, param...) + if err != nil { + return fmt.Errorf("error while saving token_holder: %s", err) + } + + return nil +} + +func (db *Db) SaveAccountBalances(accountBalances []types.AccountBalance, height int64) error { + if len(accountBalances) == 0 { + return nil + } + + query := `INSERT INTO balance (address, balances, height) VALUES` + + var param []interface{} + for i, accountBalance := range accountBalances { + vi := i * 3 + query += fmt.Sprintf("($%d,$%d,$%d),", vi+1, vi+2, vi+3) + param = append(param, accountBalance.Address, pq.Array(dbtypes.NewDbCoins(accountBalance.Balance)), height) + } + + query = query[:len(query)-1] // Remove trailing "," + query += ` +ON CONFLICT (address) DO UPDATE + SET balances = excluded.balances, + height = excluded.height +WHERE balance.height <= excluded.height` + + _, err := db.SQL.Exec(query, param...) + if err != nil { + return fmt.Errorf("error while saving AccountBalances: %s", err) + } + + return nil +} diff --git a/database/multistaking.go b/database/multistaking.go new file mode 100644 index 000000000..3a04b215d --- /dev/null +++ b/database/multistaking.go @@ -0,0 +1,225 @@ +package database + +import ( + "fmt" + + cosmossdk_io_math "cosmossdk.io/math" + dbtypes "github.com/forbole/callisto/v4/database/types" + + "github.com/lib/pq" + multistakingtypes "github.com/realio-tech/multi-staking-module/x/multi-staking/types" +) + +func (db *Db) SaveMultiStakingLocks(height int64, multiStakingLocks []*multistakingtypes.MultiStakingLock) error { + if len(multiStakingLocks) == 0 { + return nil + } + + query := `INSERT INTO ms_locks (staker_addr, val_addr, ms_lock, height) VALUES` + + var param []interface{} + + for i, msLock := range multiStakingLocks { + vi := i * 4 + query += fmt.Sprintf("($%d,$%d,$%d,$%d),", vi+1, vi+2, vi+3, vi+4) + mStakerAddr := msLock.LockID.MultiStakerAddr + valAddr := msLock.LockID.ValAddr + msCoin := dbtypes.NewMSCoin(msLock.LockedCoin) + var mscoins dbtypes.MSCoins + mscoins = append(mscoins, &msCoin) + param = append(param, mStakerAddr, valAddr, pq.Array(mscoins), height) + } + + query = query[:len(query)-1] // Remove trailing "," + query += ` +ON CONFLICT (staker_addr, val_addr) DO UPDATE + SET ms_lock = excluded.ms_lock, + height = excluded.height +WHERE ms_locks.height <= excluded.height` + + _, err := db.SQL.Exec(query, param...) + if err != nil { + return fmt.Errorf("error while saving msLock: %s", err) + } + + return nil +} + +func (db *Db) SaveMultiStakingUnlocks(height int64, multiStakingUnlocks []*multistakingtypes.MultiStakingUnlock) error { + if len(multiStakingUnlocks) == 0 { + return nil + } + + query := `INSERT INTO ms_unlocks (staker_addr, val_addr, unlock_entry, height) VALUES` + + var param []interface{} + + for i, msUnlock := range multiStakingUnlocks { + vi := i * 4 + query += fmt.Sprintf("($%d,$%d,$%d,$%d),", vi+1, vi+2, vi+3, vi+4) + mStakerAddr := msUnlock.UnlockID.MultiStakerAddr + valAddr := msUnlock.UnlockID.ValAddr + entries := msUnlock.Entries + param = append(param, mStakerAddr, valAddr, pq.Array(dbtypes.NewUnlockEntries(entries)), height) + } + + query = query[:len(query)-1] // Remove trailing "," + query += ` +ON CONFLICT (staker_addr, val_addr) DO UPDATE + SET unlock_entry = excluded.unlock_entry, + height = excluded.height +WHERE ms_unlocks.height <= excluded.height` + + _, err := db.SQL.Exec(query, param...) + if err != nil { + return fmt.Errorf("error while saving msUnlock: %s", err) + } + + return nil +} + +func (db *Db) SaveUnbondingToken(height int64, multiStakingUnlocks []*multistakingtypes.MultiStakingUnlock) error { + total := make(map[string]cosmossdk_io_math.Int) + + for _, msUnlock := range multiStakingUnlocks { + entries := msUnlock.Entries + for _, entry := range entries { + denom := entry.UnlockingCoin.Denom + amount := entry.UnlockingCoin.Amount + if total[denom].IsNil() { + total[denom] = amount + } else { + total[denom].Add(amount) + } + } + } + + if len(total) == 0 { + return nil + } + + query := `INSERT INTO token_unbonding (denom, amount, height) VALUES` + + var param []interface{} + + i := 0 + for denom, amount := range total { + vi := i * 3 + query += fmt.Sprintf("($%d,$%d,$%d),", vi+1, vi+2, vi+3) + + param = append(param, denom, amount.String(), height) + i++ + } + + query = query[:len(query)-1] // Remove trailing "," + query += ` +ON CONFLICT (denom) DO UPDATE + SET amount = excluded.amount, + height = excluded.height +WHERE token_unbonding.height <= excluded.height` + + _, err := db.SQL.Exec(query, param...) + if err != nil { + return fmt.Errorf("error while saving token_unbonding: %s", err) + } + + return nil +} + +func (db *Db) SaveBondedToken(height int64, multiStakingLocks []*multistakingtypes.MultiStakingLock) error { + total := make(map[string]cosmossdk_io_math.Int) + + for _, msLock := range multiStakingLocks { + denom := msLock.LockedCoin.Denom + amount := msLock.LockedCoin.Amount + if total[denom].IsNil() { + total[denom] = amount + } else { + total[denom].Add(amount) + } + } + + if len(total) == 0 { + return nil + } + + query := `INSERT INTO token_bonded (denom, amount, height) VALUES` + + var param []interface{} + + i := 0 + for denom, amount := range total { + vi := i * 3 + query += fmt.Sprintf("($%d,$%d,$%d),", vi+1, vi+2, vi+3) + + param = append(param, denom, amount.String(), height) + i++ + } + + query = query[:len(query)-1] // Remove trailing "," + query += ` +ON CONFLICT (denom) DO UPDATE + SET amount = excluded.amount, + height = excluded.height +WHERE token_bonded.height <= excluded.height` + + _, err := db.SQL.Exec(query, param...) + if err != nil { + return fmt.Errorf("error while saving token_bonded: %s", err) + } + + return nil +} + +func (db *Db) SaveValidatorDenom(height int64, validatorInfo []multistakingtypes.ValidatorInfo) error { + if len(validatorInfo) == 0 { + return nil + } + + query := `INSERT INTO validator_denom (val_addr, denom, height) VALUES` + + var param []interface{} + for i, info := range validatorInfo { + vi := i * 3 + query += fmt.Sprintf("($%d,$%d,$%d),", vi+1, vi+2, vi+3) + param = append(param, info.OperatorAddress, info.BondDenom, height) + } + + query = query[:len(query)-1] // Remove trailing "," + query += ` +ON CONFLICT (val_addr) DO UPDATE + SET denom = excluded.denom, + height = excluded.height +WHERE validator_denom.height <= excluded.height` + + _, err := db.SQL.Exec(query, param...) + if err != nil { + return fmt.Errorf("error while saving ValidatorDenom: %s", err) + } + + return nil +} + +func (db *Db) SaveMSEvent(msEvents []dbtypes.MSEvent, height int64) error { + if len(msEvents) == 0 { + return nil + } + + query := `INSERT INTO ms_event (height, name, val_addr, del_addr, amount) VALUES` + + var param []interface{} + for i, msEvent := range msEvents { + vi := i * 5 + query += fmt.Sprintf("($%d,$%d,$%d),", vi+1, vi+2, vi+3, vi+4, vi+5) + param = append(param, height, msEvent.Name, msEvent.ValAddr, msEvent.DelAddr, msEvent.Amount) + } + + query = query[:len(query)-1] // Remove trailing "," + + _, err := db.SQL.Exec(query, param...) + if err != nil { + return fmt.Errorf("error while saving msEvents: %s", err) + } + + return nil +} diff --git a/database/schema/01-auth.sql b/database/schema/01-auth.sql index 16f9689ba..0617f6073 100644 --- a/database/schema/01-auth.sql +++ b/database/schema/01-auth.sql @@ -10,6 +10,12 @@ CREATE TYPE COIN AS amount TEXT ); +CREATE TYPE MS_UNLOCK_ENTRY AS +( + creation_height BIGINT, + unlock_coin COIN +); + /* ---- AUTH/ VESTING ACCOUNT ---- */ CREATE TABLE vesting_account ( @@ -31,4 +37,12 @@ CREATE TABLE vesting_period period_order BIGINT NOT NULL, length BIGINT NOT NULL, amount COIN[] NOT NULL DEFAULT '{}' -); \ No newline at end of file +); + +CREATE TABLE balance +( + address TEXT NOT NULL PRIMARY KEY, + balances COIN[] NOT NULL, + height BIGINT NOT NULL +); +CREATE INDEX balance_height_index ON balance (height); \ No newline at end of file diff --git a/database/schema/02-bank.sql b/database/schema/02-bank.sql index 5df120c12..3643bc6a7 100644 --- a/database/schema/02-bank.sql +++ b/database/schema/02-bank.sql @@ -7,4 +7,13 @@ CREATE TABLE supply height BIGINT NOT NULL, CHECK (one_row_id) ); -CREATE INDEX supply_height_index ON supply (height); \ No newline at end of file +CREATE INDEX supply_height_index ON supply (height); + +CREATE TABLE token_holder +( + denom TEXT NOT NULL PRIMARY KEY, + num_holder BIGINT NOT NULL, + height BIGINT NOT NULL +); + +CREATE INDEX token_holder_height_index ON token_holder (height); \ No newline at end of file diff --git a/database/schema/13-multistaking.sql b/database/schema/13-multistaking.sql new file mode 100644 index 000000000..cc6c2a3b8 --- /dev/null +++ b/database/schema/13-multistaking.sql @@ -0,0 +1,70 @@ +CREATE TYPE MS_COIN AS +( + denom TEXT, + amount TEXT, + bond_weight NUMERIC +); + +CREATE TYPE MS_UNLOCK_ENTRY AS +( + creation_height BIGINT, + denom TEXT, + amount TEXT, + bond_weight NUMERIC +); + +/* ---- PARAMS ---- */ + +CREATE TABLE ms_locks +( + staker_addr TEXT NOT NULL, + val_addr TEXT NOT NULL, + ms_lock MS_COIN[] NOT NULL, + height BIGINT NOT NULL, + PRIMARY KEY (staker_addr, val_addr) +); + +CREATE TABLE ms_unlocks +( + staker_addr TEXT NOT NULL, + val_addr TEXT NOT NULL, + unlock_entry MS_UNLOCK_ENTRY[] NOT NULL, + height BIGINT NOT NULL, + PRIMARY KEY (staker_addr, val_addr) +); +CREATE INDEX ms_locks_height_index ON ms_locks (height); +CREATE INDEX ms_unlocks_height_index ON ms_unlocks (height); + +CREATE TABLE validator_denom +( + val_addr TEXT NOT NULL PRIMARY KEY, + denom TEXT NOT NULL, + height BIGINT NOT NULL +); +CREATE INDEX validator_denom_height_index ON validator_denom (height); + +CREATE TABLE ms_event +( + height BIGINT NOT NULL REFERENCES block (height), + name TEXT NOT NULL, + val_addr TEXT NOT NULL, + del_addr TEXT NOT NULL, + amount TEXT NOT NULL +); +CREATE INDEX ms_event_height_index ON ms_event (height); + +CREATE TABLE token_unbonding +( + denom TEXT NOT NULL PRIMARY KEY, + amount TEXT NOT NULL, + height BIGINT NOT NULL +); +CREATE INDEX token_unbonding_height_index ON token_unbonding (height); + +CREATE TABLE token_bonded +( + denom TEXT NOT NULL PRIMARY KEY, + amount TEXT NOT NULL, + height BIGINT NOT NULL +); +CREATE INDEX token_bonded_height_index ON token_bonded (height); \ No newline at end of file diff --git a/database/schema/14-viewdata.sql b/database/schema/14-viewdata.sql new file mode 100644 index 000000000..ef3b093e3 --- /dev/null +++ b/database/schema/14-viewdata.sql @@ -0,0 +1,29 @@ +CREATE OR REPLACE VIEW validator_summary AS +SELECT + vi.consensus_address AS address, + COALESCE(vd.moniker, NULL) AS moniker, + vvp.voting_power, + COALESCE(vc.commission, NULL) AS commission, + vs.status, + vs.jailed +FROM + validator_info vi +LEFT JOIN + validator_voting_power vvp ON vi.consensus_address = vvp.validator_address +LEFT JOIN + validator_commission vc ON vi.consensus_address = vc.validator_address +LEFT JOIN + validator_status vs ON vi.consensus_address = vs.validator_address +LEFT JOIN + validator_description vd ON vi.consensus_address = vd.validator_address; + +CREATE OR REPLACE VIEW account_summary AS +SELECT + ac.address AS address, + COALESCE(bl.balances, NULL) AS balances +FROM + account ac +LEFT JOIN + balance bl ON bl.address = ac.address; + + diff --git a/database/types/multistaking.go b/database/types/multistaking.go new file mode 100644 index 000000000..71955ca35 --- /dev/null +++ b/database/types/multistaking.go @@ -0,0 +1,159 @@ +package types + +import ( + "database/sql/driver" + "fmt" + "strings" + + multistakingtypes "github.com/realio-tech/multi-staking-module/x/multi-staking/types" +) + +type MSCoin struct { + Denom string + Amount string + BondWeight string +} + +type UnlockEntry struct { + CreationHeight string + Denom string + Amount string + BondWeight string +} + +func NewMSCoin(coin multistakingtypes.MultiStakingCoin) MSCoin { + return MSCoin{ + Denom: coin.Denom, + Amount: coin.Amount.String(), + BondWeight: coin.BondWeight.String(), + } +} + +// Value implements driver.Valuer +func (coin *MSCoin) Value() (driver.Value, error) { + return fmt.Sprintf("(%s,%s,%s)", coin.Denom, coin.Amount, coin.BondWeight), nil +} + +// Scan implements sql.Scanner +func (coin *MSCoin) Scan(src interface{}) error { + strValue := string(src.([]byte)) + strValue = strings.ReplaceAll(strValue, `"`, "") + strValue = strings.ReplaceAll(strValue, "{", "") + strValue = strings.ReplaceAll(strValue, "}", "") + strValue = strings.ReplaceAll(strValue, "(", "") + strValue = strings.ReplaceAll(strValue, ")", "") + + values := strings.Split(strValue, ",") + + *coin = MSCoin{Denom: values[0], Amount: values[1], BondWeight: values[2]} + return nil +} + +type MSCoins []*MSCoin + +// Scan implements sql.Scanner +func (coins *MSCoins) Scan(src interface{}) error { + strValue := string(src.([]byte)) + strValue = strings.ReplaceAll(strValue, `"`, "") + strValue = strings.ReplaceAll(strValue, "{", "") + strValue = strings.ReplaceAll(strValue, "}", "") + strValue = strings.ReplaceAll(strValue, "),(", ") (") + strValue = strings.ReplaceAll(strValue, "(", "") + strValue = strings.ReplaceAll(strValue, ")", "") + + values := RemoveEmpty(strings.Split(strValue, " ")) + + coinsV := make(MSCoins, len(values)) + for index, value := range values { + v := strings.Split(value, ",") // Split the values + + coin := MSCoin{Denom: v[0], Amount: v[1], BondWeight: v[2]} + coinsV[index] = &coin + } + + *coins = coinsV + return nil +} + +func NewUnlockEntry(entry multistakingtypes.UnlockEntry) UnlockEntry { + return UnlockEntry{ + CreationHeight: fmt.Sprintf("%d", entry.GetCreationHeight()), + Denom: entry.UnlockingCoin.Denom, + Amount: entry.UnlockingCoin.Amount.String(), + BondWeight: entry.UnlockingCoin.BondWeight.String(), + } +} + +// Value implements driver.Valuer +func (entry *UnlockEntry) Value() (driver.Value, error) { + return fmt.Sprintf("(%s,%s,%s,%s)", entry.CreationHeight, entry.Denom, entry.Amount, entry.BondWeight), nil +} + +// Scan implements sql.Scanner +func (entry *UnlockEntry) Scan(src interface{}) error { + strValue := string(src.([]byte)) + strValue = strings.ReplaceAll(strValue, `"`, "") + strValue = strings.ReplaceAll(strValue, "{", "") + strValue = strings.ReplaceAll(strValue, "}", "") + strValue = strings.ReplaceAll(strValue, "(", "") + strValue = strings.ReplaceAll(strValue, ")", "") + + values := strings.Split(strValue, ",") + *entry = UnlockEntry{CreationHeight: values[0], Denom: values[1], Amount: values[2], BondWeight: values[3]} + return nil +} + +type UnlockEntries []*UnlockEntry + +func NewUnlockEntries(entries []multistakingtypes.UnlockEntry) UnlockEntries { + unlockEntries := make([]*UnlockEntry, 0) + for _, entry := range entries { + unlockEntry := NewUnlockEntry(entry) + unlockEntries = append(unlockEntries, &unlockEntry) + } + return unlockEntries +} + +type MSEvent struct { + Name string + ValAddr string + DelAddr string + Amount string +} + +func NewMSEvent(ValAddr string, DelAddr string, Amount string, Name string) (MSEvent, error) { + if ValAddr != "" && DelAddr != "" && Amount != "" && Name != "" { + return MSEvent{ + Name: Name, + ValAddr: ValAddr, + DelAddr: DelAddr, + Amount: Amount, + }, nil + } + + return MSEvent{}, fmt.Errorf("error") +} + +// Scan implements sql.Scanner +func (coins *UnlockEntries) Scan(src interface{}) error { + strValue := string(src.([]byte)) + strValue = strings.ReplaceAll(strValue, `"`, "") + strValue = strings.ReplaceAll(strValue, "{", "") + strValue = strings.ReplaceAll(strValue, "}", "") + strValue = strings.ReplaceAll(strValue, "),(", ") (") + strValue = strings.ReplaceAll(strValue, "(", "") + strValue = strings.ReplaceAll(strValue, ")", "") + + values := RemoveEmpty(strings.Split(strValue, " ")) + + coinsV := make(UnlockEntries, len(values)) + for index, value := range values { + v := strings.Split(value, ",") // Split the values + + coin := UnlockEntry{CreationHeight: v[0], Denom: v[1], Amount: v[2], BondWeight: v[3]} + coinsV[index] = &coin + } + + *coins = coinsV + return nil +} diff --git a/modules/asset/handle_msg.go b/modules/asset/handle_msg.go new file mode 100644 index 000000000..e7832767f --- /dev/null +++ b/modules/asset/handle_msg.go @@ -0,0 +1,62 @@ +package asset + +import ( + "fmt" + + "strings" + + "github.com/forbole/callisto/v4/types" + "github.com/forbole/callisto/v4/utils" + juno "github.com/forbole/juno/v6/types" + assettypes "github.com/realiotech/realio-network/x/asset/types" + "github.com/rs/zerolog/log" +) + +var msgFilter = map[string]bool{ + "/realionetwork.asset.v1.MsgCreateToken": true, + "/realionetwork.asset.v1.Msg/CreateToken": true, +} + +// HandleMsg implements MessageModule +// HandleMsgExec implements modules.AuthzMessageModule +func (m *Module) HandleMsgExec(index int, _ int, executedMsg juno.Message, tx *juno.Transaction) error { + return m.HandleMsg(index, executedMsg, tx) +} + +// HandleMsg implements MessageModule +func (m *Module) HandleMsg(_ int, msg juno.Message, tx *juno.Transaction) error { + if _, ok := msgFilter[msg.GetType()]; !ok { + return nil + } + + log.Debug().Str("module", "asset").Str("hash", tx.TxHash).Uint64("height", tx.Height).Msg(fmt.Sprintf("handling create token message %s", msg.GetType())) + + switch msg.GetType() { + case "/realionetwork.asset.v1.Msg/CreateToken": + cosmosMsg := utils.UnpackMessage(m.cdc, msg.GetBytes(), &assettypes.MsgCreateToken{}) + return m.handleMsgCreateToken(cosmosMsg) + + case "/realionetwork.asset.v1.MsgCreateToken": + cosmosMsg := utils.UnpackMessage(m.cdc, msg.GetBytes(), &assettypes.MsgCreateToken{}) + return m.handleMsgCreateToken(cosmosMsg) + } + + return nil +} + +// --------------------------------------------------------------------------------------------------------------------- + +// handleMsgCreateToken handles properly a MsgCreateToken instance by +// saving into the database all the data associated to such NewToken +func (m *Module) handleMsgCreateToken(msg *assettypes.MsgCreateToken) error { + symbol := msg.Symbol + if symbol == "" { + return nil + } + + lowerCaseSymbol := strings.ToLower(msg.Symbol) + baseDenom := fmt.Sprintf("a%s", lowerCaseSymbol) + tokenUnit := types.NewTokenUnit(baseDenom, 18, nil, "") + token := types.NewToken(symbol, []types.TokenUnit{tokenUnit}) + return m.db.SaveToken(token) +} diff --git a/modules/asset/handle_periodic_operations.go b/modules/asset/handle_periodic_operations.go new file mode 100644 index 000000000..636e14454 --- /dev/null +++ b/modules/asset/handle_periodic_operations.go @@ -0,0 +1,61 @@ +package asset + +import ( + "fmt" + "strings" + + "github.com/go-co-op/gocron" + "github.com/rs/zerolog/log" + + "github.com/forbole/callisto/v4/types" + assettypes "github.com/realiotech/realio-network/x/asset/types" +) + +// RegisterPeriodicOperations implements modules.Module +func (m *Module) RegisterPeriodicOperations(scheduler *gocron.Scheduler) error { + log.Debug().Str("module", "asset").Msg("setting up periodic tasks") + + err := m.InitAllTokens() + if err != nil { + return fmt.Errorf("error while initAllTokens: %s", err) + } + + return nil +} + +// InitAllTokens init the supply of all the tokens +func (m *Module) InitAllTokens() error { + log.Trace().Str("module", "bank").Str("operation", "total supply"). + Msg("updating total supply") + + block, err := m.db.GetLastBlockHeightAndTimestamp() + if err != nil { + return fmt.Errorf("error while getting latest block height: %s", err) + } + + tokens, err := m.source.GetTokens(block.Height) + if err != nil { + return err + } + + return m.updateAllTokens(block.Height, tokens) +} + +// updateTokenHolder updates num holder of all the tokens +func (m *Module) updateAllTokens(height int64, tokens []assettypes.Token) error { + log.Trace().Str("module", "asset").Str("operation", "token"). + Msg("updating token unit") + + for _, token := range tokens { + lowerCaseSymbol := strings.ToLower(token.Symbol) + baseDenom := fmt.Sprintf("a%s", lowerCaseSymbol) + tokenUnit := types.NewTokenUnit(baseDenom, 18, nil, "") + token := types.NewToken(token.Symbol, []types.TokenUnit{tokenUnit}) + err := m.db.SaveToken(token) + if err != nil { + return fmt.Errorf("error while save token2 : %s", err) + } + } + + return nil +} diff --git a/modules/asset/module.go b/modules/asset/module.go new file mode 100644 index 000000000..81165cabf --- /dev/null +++ b/modules/asset/module.go @@ -0,0 +1,39 @@ +package asset + +import ( + "github.com/cosmos/cosmos-sdk/codec" + assetsource "github.com/forbole/callisto/v4/modules/asset/source" + "github.com/forbole/juno/v6/modules" + + "github.com/forbole/callisto/v4/database" +) + +var ( + _ modules.Module = &Module{} + _ modules.PeriodicOperationsModule = &Module{} + _ modules.MessageModule = &Module{} + _ modules.AuthzMessageModule = &Module{} +) + +// Module represents the x/staking module +type Module struct { + cdc codec.Codec + db *database.Db + source assetsource.Source +} + +// NewModule returns a new Module instance +func NewModule( + source assetsource.Source, cdc codec.Codec, db *database.Db, +) *Module { + return &Module{ + cdc: cdc, + db: db, + source: source, + } +} + +// Name implements modules.Module +func (m *Module) Name() string { + return "asset" +} diff --git a/modules/asset/source/local/source.go b/modules/asset/source/local/source.go new file mode 100644 index 000000000..35dcd655e --- /dev/null +++ b/modules/asset/source/local/source.go @@ -0,0 +1,47 @@ +package local + +import ( + "fmt" + + assettypes "github.com/realiotech/realio-network/x/asset/types" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/forbole/juno/v6/node/local" + + "github.com/forbole/callisto/v4/modules/asset/source" +) + +var ( + _ source.Source = &Source{} +) + +// Source represents the implementation of the asset keeper that works on a local node +type Source struct { + *local.Source + q assettypes.QueryServer +} + +// NewSource builds a new Source instance +func NewSource(source *local.Source, bk assettypes.QueryServer) *Source { + return &Source{ + Source: source, + q: bk, + } +} + +// GetTokens implements Source +func (s Source) GetTokens(height int64) ([]assettypes.Token, error) { + ctx, err := s.LoadHeight(height) + if err != nil { + return nil, fmt.Errorf("error while loading height: %s", err) + } + + res, err := s.q.Tokens( + sdk.WrapSDKContext(ctx), + &assettypes.QueryTokensRequest{}) + if err != nil { + return nil, fmt.Errorf("error while getting tokens: %s", err) + } + + return res.Tokens, nil +} diff --git a/modules/asset/source/remote/source.go b/modules/asset/source/remote/source.go new file mode 100644 index 000000000..ed59a6bbd --- /dev/null +++ b/modules/asset/source/remote/source.go @@ -0,0 +1,41 @@ +package remote + +import ( + "fmt" + + "github.com/forbole/juno/v6/node/remote" + + assetsource "github.com/forbole/callisto/v4/modules/asset/source" + assettypes "github.com/realiotech/realio-network/x/asset/types" +) + +var ( + _ assetsource.Source = &Source{} +) + +type Source struct { + *remote.Source + assetClient assettypes.QueryClient +} + +// NewSource builds a new Source instance +func NewSource(source *remote.Source, assetClient assettypes.QueryClient) *Source { + return &Source{ + Source: source, + assetClient: assetClient, + } +} + +// GetSupply implements assetsource.Source +func (s Source) GetTokens(height int64) ([]assettypes.Token, error) { + ctx := remote.GetHeightRequestContext(s.Ctx, height) + + res, err := s.assetClient.Tokens( + ctx, + &assettypes.QueryTokensRequest{}) + if err != nil { + return nil, fmt.Errorf("error while getting total supply: %s", err) + } + + return res.Tokens, nil +} diff --git a/modules/asset/source/source.go b/modules/asset/source/source.go new file mode 100644 index 000000000..e5811de14 --- /dev/null +++ b/modules/asset/source/source.go @@ -0,0 +1,9 @@ +package source + +import ( + assettypes "github.com/realiotech/realio-network/x/asset/types" +) + +type Source interface { + GetTokens(height int64) ([]assettypes.Token, error) +} diff --git a/modules/bank/handle_block.go b/modules/bank/handle_block.go new file mode 100644 index 000000000..069aa3120 --- /dev/null +++ b/modules/bank/handle_block.go @@ -0,0 +1,97 @@ +package bank + +import ( + "fmt" + + abci "github.com/cometbft/cometbft/abci/types" + + juno "github.com/forbole/juno/v6/types" + + tmctypes "github.com/cometbft/cometbft/rpc/core/types" + "github.com/rs/zerolog/log" + + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" +) + +// HandleBlock implements BlockModule +func (m *Module) HandleBlock( + block *tmctypes.ResultBlock, res *tmctypes.ResultBlockResults, _ []*juno.Transaction, _ *tmctypes.ResultValidators, +) error { + + // Remove expired fee grant allowances + err := m.updateBalanceByEvent(block.Block.Height, res.FinalizeBlockEvents) + if err != nil { + fmt.Printf("Error when removing expired fee grant allowance, error: %s", err) + } + return nil +} + +// removeExpiredFeeGrantAllowances removes fee grant allowances in database that have expired +func (m *Module) updateBalanceByEvent(height int64, events []abci.Event) error { + log.Debug().Str("module", "bank").Int64("height", height). + Msg("updating balance by event") + + setAddr := make(map[string]struct{}) + for _, event := range events { + switch event.Type { + case banktypes.EventTypeTransfer: + address, err := juno.FindAttributeByKey(event, banktypes.AttributeKeyRecipient) + if err == nil && address.Value != "" { + setAddr[address.Value] = struct{}{} + } + + address, err = juno.FindAttributeByKey(event, banktypes.AttributeKeySender) + if err == nil && address.Value != "" { + setAddr[address.Value] = struct{}{} + } + + case banktypes.EventTypeCoinSpent: + address, err := juno.FindAttributeByKey(event, banktypes.AttributeKeySpender) + if err == nil && address.Value != "" { + setAddr[address.Value] = struct{}{} + } + + case banktypes.EventTypeCoinReceived: + address, err := juno.FindAttributeByKey(event, banktypes.AttributeKeyReceiver) + if err == nil && address.Value != "" { + setAddr[address.Value] = struct{}{} + } + + case banktypes.EventTypeCoinMint: + address, err := juno.FindAttributeByKey(event, banktypes.AttributeKeyMinter) + if err == nil && address.Value != "" { + setAddr[address.Value] = struct{}{} + } + + case banktypes.EventTypeCoinBurn: + address, err := juno.FindAttributeByKey(event, banktypes.AttributeKeyBurner) + if err == nil && address.Value != "" { + setAddr[address.Value] = struct{}{} + } + + } + } + + var addresses []string + for address := range setAddr { + addresses = append(addresses, address) + } + + if len(addresses) == 0 { + return nil + } + return m.UpdateBalance(addresses, height) +} + +func (m *Module) UpdateBalance(addresses []string, height int64) error { + log.Trace().Str("module", "bank").Str("operation", "account balance"). + Msg("updating account balance") + + accountBalances, err := m.keeper.GetBalances(addresses, height) + if err != nil { + return err + } + + err = m.db.SaveAccountBalances(accountBalances, height) + return err +} diff --git a/modules/bank/handle_periodic_operations.go b/modules/bank/handle_periodic_operations.go index a282d6afb..9c444a3c8 100644 --- a/modules/bank/handle_periodic_operations.go +++ b/modules/bank/handle_periodic_operations.go @@ -6,6 +6,7 @@ import ( "github.com/go-co-op/gocron" "github.com/rs/zerolog/log" + sdk "github.com/cosmos/cosmos-sdk/types" "github.com/forbole/callisto/v4/modules/utils" ) @@ -37,5 +38,33 @@ func (m *Module) UpdateSupply() error { return err } - return m.db.SaveSupply(supply, block.Height) + err = m.db.SaveSupply(supply, block.Height) + if err != nil { + return err + } + + err = m.updateTokenHolder(block.Height, supply) + return err +} + +// updateTokenHolder updates num holder of all the tokens +func (m *Module) updateTokenHolder(height int64, tokens sdk.Coins) error { + log.Trace().Str("module", "bank").Str("operation", "total holder"). + Msg("updating token holder") + + total := make(map[string]int) + for _, tokenUnit := range tokens { + denom := tokenUnit.Denom + numHolders, err := m.keeper.GetDenomOwners(height, denom) + if err != nil { + return fmt.Errorf("error while updating holder: %s", err) + } + + total[denom] = numHolders + } + + if len(total) == 0 { + return nil + } + return m.db.SaveTokenHolder(total, height) } diff --git a/modules/bank/module.go b/modules/bank/module.go index 6c77e4946..a29bbb5e0 100644 --- a/modules/bank/module.go +++ b/modules/bank/module.go @@ -14,6 +14,7 @@ import ( var ( _ modules.Module = &Module{} _ modules.PeriodicOperationsModule = &Module{} + _ modules.BlockModule = &Module{} ) // Module represents the x/bank module diff --git a/modules/bank/source/local/source.go b/modules/bank/source/local/source.go index 268841cf7..d384e14b3 100644 --- a/modules/bank/source/local/source.go +++ b/modules/bank/source/local/source.go @@ -96,3 +96,35 @@ func (s Source) GetAccountBalance(address string, height int64) ([]sdk.Coin, err return balRes.Balances, nil } + +// GetDenomOwners implements bankkeeper.Source +func (s Source) GetDenomOwners(height int64, denom string) (int, error) { + ctx, err := s.LoadHeight(height) + if err != nil { + return 0, fmt.Errorf("error while loading height: %s", err) + } + + var holders []*banktypes.DenomOwner + var nextKey []byte + var stop = false + for !stop { + res, err := s.q.DenomOwners( + sdk.WrapSDKContext(ctx), + &banktypes.QueryDenomOwnersRequest{ + Denom: denom, + Pagination: &query.PageRequest{ + Key: nextKey, + Limit: 100, + }, + }) + if err != nil { + return 0, fmt.Errorf("error while getting holder: %s", err) + } + + nextKey = res.Pagination.NextKey + stop = len(res.Pagination.NextKey) == 0 + holders = append(holders, res.DenomOwners...) + } + + return len(holders), nil +} diff --git a/modules/bank/source/remote/source.go b/modules/bank/source/remote/source.go index f5fd4afae..0ced8c83b 100644 --- a/modules/bank/source/remote/source.go +++ b/modules/bank/source/remote/source.go @@ -77,3 +77,32 @@ func (s Source) GetSupply(height int64) (sdk.Coins, error) { return coins, nil } + +// GetDenomOwners implements bankkeeper.Source +func (s Source) GetDenomOwners(height int64, denom string) (int, error) { + ctx := remote.GetHeightRequestContext(s.Ctx, height) + + var holders []*banktypes.DenomOwner + var nextKey []byte + var stop = false + for !stop { + res, err := s.bankClient.DenomOwners( + ctx, + &banktypes.QueryDenomOwnersRequest{ + Denom: denom, + Pagination: &query.PageRequest{ + Key: nextKey, + Limit: 100, + }, + }) + if err != nil { + return 0, fmt.Errorf("error while getting holders: %s", err) + } + + nextKey = res.Pagination.NextKey + stop = len(res.Pagination.NextKey) == 0 + holders = append(holders, res.DenomOwners...) + } + + return len(holders), nil +} diff --git a/modules/bank/source/source.go b/modules/bank/source/source.go index 2cd5504ac..55975e912 100644 --- a/modules/bank/source/source.go +++ b/modules/bank/source/source.go @@ -12,4 +12,5 @@ type Source interface { // -- For hasura action -- GetAccountBalance(address string, height int64) ([]sdk.Coin, error) + GetDenomOwners(height int64, denom string) (int, error) } diff --git a/modules/multistaking/handle_block.go b/modules/multistaking/handle_block.go new file mode 100644 index 000000000..60917db71 --- /dev/null +++ b/modules/multistaking/handle_block.go @@ -0,0 +1,74 @@ +package multistaking + +import ( + "fmt" + + abci "github.com/cometbft/cometbft/abci/types" + + juno "github.com/forbole/juno/v6/types" + + tmctypes "github.com/cometbft/cometbft/rpc/core/types" + "github.com/rs/zerolog/log" + + sdk "github.com/cosmos/cosmos-sdk/types" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" + dbtypes "github.com/forbole/callisto/v4/database/types" +) + +// HandleBlock implements BlockModule +func (m *Module) HandleBlock( + block *tmctypes.ResultBlock, res *tmctypes.ResultBlockResults, _ []*juno.Transaction, _ *tmctypes.ResultValidators, +) error { + + err := m.updateTxsByEvent(block.Block.Height, res.FinalizeBlockEvents) + if err != nil { + fmt.Printf("Error when updateTxsByEvent, error: %s", err) + } + return nil +} + +func (m *Module) updateTxsByEvent(height int64, events []abci.Event) error { + log.Debug().Str("module", "multistaking").Int64("height", height). + Msg("updating txs by event") + + var msEvents []dbtypes.MSEvent + for _, event := range events { + switch event.Type { + case stakingtypes.EventTypeDelegate: + valAddr, _ := juno.FindAttributeByKey(event, stakingtypes.AttributeKeyValidator) + delAddr, _ := juno.FindAttributeByKey(event, stakingtypes.AttributeKeyDelegator) + amount, _ := juno.FindAttributeByKey(event, sdk.AttributeKeyAmount) + msEvent, err := dbtypes.NewMSEvent("delegate", valAddr.Value, delAddr.Value, amount.Value) + + if err == nil { + msEvents = append(msEvents, msEvent) + } + + case stakingtypes.EventTypeUnbond: + valAddr, _ := juno.FindAttributeByKey(event, stakingtypes.AttributeKeyValidator) + delAddr, _ := juno.FindAttributeByKey(event, stakingtypes.AttributeKeyDelegator) + amount, _ := juno.FindAttributeByKey(event, sdk.AttributeKeyAmount) + msEvent, err := dbtypes.NewMSEvent("unbond", valAddr.Value, delAddr.Value, amount.Value) + + if err == nil { + msEvents = append(msEvents, msEvent) + } + + case stakingtypes.EventTypeCancelUnbondingDelegation: + valAddr, _ := juno.FindAttributeByKey(event, stakingtypes.AttributeKeyValidator) + delAddr, _ := juno.FindAttributeByKey(event, stakingtypes.AttributeKeyDelegator) + amount, _ := juno.FindAttributeByKey(event, sdk.AttributeKeyAmount) + msEvent, err := dbtypes.NewMSEvent("cancel_unbond", valAddr.Value, delAddr.Value, amount.Value) + + if err == nil { + msEvents = append(msEvents, msEvent) + } + + } + } + + if len(msEvents) == 0 { + return nil + } + return m.db.SaveMSEvent(msEvents, height) +} diff --git a/modules/multistaking/handle_periodic_operations.go b/modules/multistaking/handle_periodic_operations.go new file mode 100644 index 000000000..9133222b8 --- /dev/null +++ b/modules/multistaking/handle_periodic_operations.go @@ -0,0 +1,95 @@ +package multistaking + +import ( + "fmt" + + "github.com/go-co-op/gocron" + "github.com/rs/zerolog/log" + + "github.com/forbole/callisto/v4/modules/utils" +) + +// RegisterPeriodicOperations implements modules.Module +func (m *Module) RegisterPeriodicOperations(scheduler *gocron.Scheduler) error { + log.Debug().Str("module", "multistaking").Msg("setting up periodic tasks") + + if _, err := scheduler.Every(5).Hour().Do(func() { + utils.WatchMethod(m.UpdateMultiStaking) + }); err != nil { + return fmt.Errorf("error while setting up multistaking token periodic operation: %s", err) + } + + return nil +} + +func (m *Module) UpdateMultiStaking() error { + log.Trace().Str("module", "multistaking").Str("operation", "multistaking lock"). + Msg("updating multistaking lock") + + block, err := m.db.GetLastBlockHeightAndTimestamp() + if err != nil { + return fmt.Errorf("error while getting latest block height: %s", err) + } + err = m.UpdateMultiStakingLocks(block.Height) + if err != nil { + return fmt.Errorf("error while update UpdateMultiStakingLocks: %s", err) + } + + err = m.UpdateMultiStakingUnlocks(block.Height) + if err != nil { + return fmt.Errorf("error while update UpdateMultiStakingUnlocks: %s", err) + } + + err = m.UpdateValidatorInfo(block.Height) + if err != nil { + return fmt.Errorf("error while update UpdateValidatorInfo: %s", err) + } + + return nil +} + +func (m *Module) UpdateMultiStakingLocks(height int64) error { + log.Trace().Str("module", "multistaking").Str("operation", "multistaking lock"). + Msg("updating multistaking lock") + + multiStakingLocks, err := m.source.GetMultiStakingLocks(height) + if err != nil { + return err + } + + err = m.db.SaveBondedToken(height, multiStakingLocks) + if err != nil { + return err + } + + return m.db.SaveMultiStakingLocks(height, multiStakingLocks) +} + +func (m *Module) UpdateMultiStakingUnlocks(height int64) error { + log.Trace().Str("module", "multistaking").Str("operation", "multistaking unlock"). + Msg("updating multistaking unlock") + + multiStakingUnlocks, err := m.source.GetMultiStakingUnlocks(height) + if err != nil { + return err + } + + err = m.db.SaveUnbondingToken(height, multiStakingUnlocks) + if err != nil { + return err + } + + return m.db.SaveMultiStakingUnlocks(height, multiStakingUnlocks) +} + +func (m *Module) UpdateValidatorInfo(height int64) error { + log.Trace().Str("module", "multistaking").Str("operation", "validator info"). + Msg("updating validator info") + + validatorInfo, err := m.source.GetValidators(height, "") + if err != nil { + return err + } + + return m.db.SaveValidatorDenom(height, validatorInfo) +} diff --git a/modules/multistaking/module.go b/modules/multistaking/module.go new file mode 100644 index 000000000..95eb151a7 --- /dev/null +++ b/modules/multistaking/module.go @@ -0,0 +1,38 @@ +package multistaking + +import ( + "github.com/cosmos/cosmos-sdk/codec" + multistakingsource "github.com/forbole/callisto/v4/modules/multistaking/source" + "github.com/forbole/juno/v6/modules" + + "github.com/forbole/callisto/v4/database" +) + +var ( + _ modules.Module = &Module{} + _ modules.BlockModule = &Module{} + _ modules.PeriodicOperationsModule = &Module{} +) + +// Module represents the x/staking module +type Module struct { + cdc codec.Codec + db *database.Db + source multistakingsource.Source +} + +// NewModule returns a new Module instance +func NewModule( + source multistakingsource.Source, cdc codec.Codec, db *database.Db, +) *Module { + return &Module{ + cdc: cdc, + db: db, + source: source, + } +} + +// Name implements modules.Module +func (m *Module) Name() string { + return "multistaking" +} diff --git a/modules/multistaking/source/local/source.go b/modules/multistaking/source/local/source.go new file mode 100644 index 000000000..28fd11909 --- /dev/null +++ b/modules/multistaking/source/local/source.go @@ -0,0 +1,121 @@ +package local + +import ( + "fmt" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/forbole/juno/v6/node/local" + multistakingtypes "github.com/realio-tech/multi-staking-module/x/multi-staking/types" + + "github.com/cosmos/cosmos-sdk/types/query" + "github.com/forbole/callisto/v4/modules/multistaking/source" +) + +var ( + _ source.Source = &Source{} +) + +// Source represents the implementation of the multistaking keeper that works on a local node +type Source struct { + *local.Source + qs multistakingtypes.QueryServer +} + +// NewSource builds a new Source instance +func NewSource(source *local.Source, qs multistakingtypes.QueryServer) *Source { + return &Source{ + Source: source, + qs: qs, + } +} + +func (s Source) GetMultiStakingLocks(height int64) ([]*multistakingtypes.MultiStakingLock, error) { + ctx, err := s.LoadHeight(height) + if err != nil { + return nil, fmt.Errorf("error while loading height: %s", err) + } + + var multiStakingLock []*multistakingtypes.MultiStakingLock + var nextKey []byte + var stop = false + for !stop { + res, err := s.qs.MultiStakingLocks( + sdk.WrapSDKContext(ctx), + &multistakingtypes.QueryMultiStakingLocksRequest{ + Pagination: &query.PageRequest{ + Key: nextKey, + Limit: 100, // Query 100 multiStakingLock at time + }, + }) + if err != nil { + return nil, fmt.Errorf("error while getting multiStakingLock: %s", err) + } + + nextKey = res.Pagination.NextKey + stop = len(res.Pagination.NextKey) == 0 + multiStakingLock = append(multiStakingLock, res.Locks...) + } + + return multiStakingLock, nil +} + +func (s Source) GetMultiStakingUnlocks(height int64) ([]*multistakingtypes.MultiStakingUnlock, error) { + ctx, err := s.LoadHeight(height) + if err != nil { + return nil, fmt.Errorf("error while loading height: %s", err) + } + + var multiStakingUnlock []*multistakingtypes.MultiStakingUnlock + var nextKey []byte + var stop = false + for !stop { + res, err := s.qs.MultiStakingUnlocks( + sdk.WrapSDKContext(ctx), + &multistakingtypes.QueryMultiStakingUnlocksRequest{ + Pagination: &query.PageRequest{ + Key: nextKey, + Limit: 100, // Query 100 multiStakingLock at time + }, + }) + if err != nil { + return nil, fmt.Errorf("error while getting multiStakingLock: %s", err) + } + + nextKey = res.Pagination.NextKey + stop = len(res.Pagination.NextKey) == 0 + multiStakingUnlock = append(multiStakingUnlock, res.Unlocks...) + } + + return multiStakingUnlock, nil +} + +func (s Source) GetValidators(height int64, status string) ([]multistakingtypes.ValidatorInfo, error) { + ctx, err := s.LoadHeight(height) + if err != nil { + return nil, fmt.Errorf("error while loading height: %s", err) + } + + var validatorInfo []multistakingtypes.ValidatorInfo + var nextKey []byte + var stop = false + for !stop { + res, err := s.qs.Validators( + sdk.WrapSDKContext(ctx), + &multistakingtypes.QueryValidatorsRequest{ + Status: status, + Pagination: &query.PageRequest{ + Key: nextKey, + Limit: 100, // Query 100 validatorInfo at time + }, + }) + if err != nil { + return nil, fmt.Errorf("error while getting validatorInfo: %s", err) + } + + nextKey = res.Pagination.NextKey + stop = len(res.Pagination.NextKey) == 0 + validatorInfo = append(validatorInfo, res.Validators...) + } + + return validatorInfo, nil +} diff --git a/modules/multistaking/source/remote/source.go b/modules/multistaking/source/remote/source.go new file mode 100644 index 000000000..9c90a9253 --- /dev/null +++ b/modules/multistaking/source/remote/source.go @@ -0,0 +1,109 @@ +package remote + +import ( + "fmt" + + "github.com/cosmos/cosmos-sdk/types/query" + multistakingsource "github.com/forbole/callisto/v4/modules/multistaking/source" + "github.com/forbole/juno/v6/node/remote" + multistakingtypes "github.com/realio-tech/multi-staking-module/x/multi-staking/types" +) + +var ( + _ multistakingsource.Source = &Source{} +) + +type Source struct { + *remote.Source + msClient multistakingtypes.QueryClient +} + +// NewSource builds a new Source instance +func NewSource(source *remote.Source, msClient multistakingtypes.QueryClient) *Source { + return &Source{ + Source: source, + msClient: msClient, + } +} + +func (s Source) GetMultiStakingLocks(height int64) ([]*multistakingtypes.MultiStakingLock, error) { + ctx := remote.GetHeightRequestContext(s.Ctx, height) + + var multiStakingLock []*multistakingtypes.MultiStakingLock + var nextKey []byte + var stop = false + for !stop { + res, err := s.msClient.MultiStakingLocks( + ctx, + &multistakingtypes.QueryMultiStakingLocksRequest{ + Pagination: &query.PageRequest{ + Key: nextKey, + Limit: 100, // Query 100 multiStakingLock at time + }, + }) + if err != nil { + return nil, fmt.Errorf("error while getting total supply: %s", err) + } + + nextKey = res.Pagination.NextKey + stop = len(res.Pagination.NextKey) == 0 + multiStakingLock = append(multiStakingLock, res.Locks...) + } + + return multiStakingLock, nil +} + +func (s Source) GetMultiStakingUnlocks(height int64) ([]*multistakingtypes.MultiStakingUnlock, error) { + ctx := remote.GetHeightRequestContext(s.Ctx, height) + + var multiStakingUnlock []*multistakingtypes.MultiStakingUnlock + var nextKey []byte + var stop = false + for !stop { + res, err := s.msClient.MultiStakingUnlocks( + ctx, + &multistakingtypes.QueryMultiStakingUnlocksRequest{ + Pagination: &query.PageRequest{ + Key: nextKey, + Limit: 100, // Query 100 multiStakingLock at time + }, + }) + if err != nil { + return nil, fmt.Errorf("error while getting total supply: %s", err) + } + + nextKey = res.Pagination.NextKey + stop = len(res.Pagination.NextKey) == 0 + multiStakingUnlock = append(multiStakingUnlock, res.Unlocks...) + } + + return multiStakingUnlock, nil +} + +func (s Source) GetValidators(height int64, status string) ([]multistakingtypes.ValidatorInfo, error) { + ctx := remote.GetHeightRequestContext(s.Ctx, height) + + var validatorInfo []multistakingtypes.ValidatorInfo + var nextKey []byte + var stop = false + for !stop { + res, err := s.msClient.Validators( + ctx, + &multistakingtypes.QueryValidatorsRequest{ + Status: status, + Pagination: &query.PageRequest{ + Key: nextKey, + Limit: 100, // Query 100 validatorInfo at time + }, + }) + if err != nil { + return nil, fmt.Errorf("error while getting total validatorInfo: %s", err) + } + + nextKey = res.Pagination.NextKey + stop = len(res.Pagination.NextKey) == 0 + validatorInfo = append(validatorInfo, res.Validators...) + } + + return validatorInfo, nil +} diff --git a/modules/multistaking/source/source.go b/modules/multistaking/source/source.go new file mode 100644 index 000000000..b41864532 --- /dev/null +++ b/modules/multistaking/source/source.go @@ -0,0 +1,11 @@ +package source + +import ( + multistakingtypes "github.com/realio-tech/multi-staking-module/x/multi-staking/types" +) + +type Source interface { + GetMultiStakingLocks(height int64) ([]*multistakingtypes.MultiStakingLock, error) + GetMultiStakingUnlocks(height int64) ([]*multistakingtypes.MultiStakingUnlock, error) + GetValidators(height int64, status string) ([]multistakingtypes.ValidatorInfo, error) +} diff --git a/modules/registrar.go b/modules/registrar.go index 69f8c3aff..8f3ee7409 100644 --- a/modules/registrar.go +++ b/modules/registrar.go @@ -18,6 +18,7 @@ import ( "github.com/forbole/callisto/v4/utils" "github.com/forbole/callisto/v4/database" + "github.com/forbole/callisto/v4/modules/asset" "github.com/forbole/callisto/v4/modules/auth" "github.com/forbole/callisto/v4/modules/bank" "github.com/forbole/callisto/v4/modules/consensus" @@ -31,6 +32,7 @@ import ( messagetype "github.com/forbole/callisto/v4/modules/message_type" "github.com/forbole/callisto/v4/modules/mint" "github.com/forbole/callisto/v4/modules/modules" + "github.com/forbole/callisto/v4/modules/multistaking" "github.com/forbole/callisto/v4/modules/pricefeed" "github.com/forbole/callisto/v4/modules/staking" "github.com/forbole/callisto/v4/modules/upgrade" @@ -90,6 +92,8 @@ func (r *Registrar) BuildModules(ctx registrar.Context) jmodules.Modules { stakingModule := staking.NewModule(sources.StakingSource, r.cdc, db) govModule := gov.NewModule(sources.GovSource, distrModule, mintModule, slashingModule, stakingModule, r.cdc, db) upgradeModule := upgrade.NewModule(db, stakingModule) + assetModule := asset.NewModule(sources.AssetSource, r.cdc, db) + multistakingModule := multistaking.NewModule(sources.MultistakingSource, r.cdc, db) return []jmodules.Module{ messages.NewModule(r.parser, ctx.Database), @@ -111,5 +115,7 @@ func (r *Registrar) BuildModules(ctx registrar.Context) jmodules.Modules { slashingModule, stakingModule, upgradeModule, + assetModule, + multistakingModule, } } diff --git a/modules/staking/handle_msg.go b/modules/staking/handle_msg.go index 866cbbd1b..750695c35 100644 --- a/modules/staking/handle_msg.go +++ b/modules/staking/handle_msg.go @@ -8,7 +8,9 @@ import ( juno "github.com/forbole/juno/v6/types" + dbtypes "github.com/forbole/callisto/v4/database/types" "github.com/forbole/callisto/v4/utils" + multistakingtypes "github.com/realio-tech/multi-staking-module/x/multi-staking/types" ) var msgFilter = map[string]bool{ @@ -48,6 +50,12 @@ func (m *Module) HandleMsg(_ int, msg juno.Message, tx *juno.Transaction) error return m.UpdateValidatorStatuses() case "/cosmos.staking.v1beta1.MsgBeginRedelegate": + cosmosMsg := utils.UnpackMessage(m.cdc, msg.GetBytes(), &stakingtypes.MsgBeginRedelegate{}) + err := m.updateStakingEvent(int64(tx.Height), cosmosMsg) + if err != nil { + return err + } + return m.UpdateValidatorStatuses() case "/cosmos.staking.v1beta1.MsgUndelegate": @@ -67,7 +75,15 @@ func (m *Module) handleMsgCreateValidator(height int64, msg *stakingtypes.MsgCre if err != nil { return fmt.Errorf("error while refreshing validator from MsgCreateValidator: %s", err) } - return nil + + var infos []multistakingtypes.ValidatorInfo + validatorInfo := multistakingtypes.ValidatorInfo{ + OperatorAddress: msg.ValidatorAddress, + BondDenom: msg.Value.Denom, + } + infos = append(infos, validatorInfo) + + return m.db.SaveValidatorDenom(height, infos) } // handleEditValidator handles MsgEditValidator utils, updating the validator info @@ -79,3 +95,15 @@ func (m *Module) handleEditValidator(height int64, msg *stakingtypes.MsgEditVali return nil } + +func (m *Module) updateStakingEvent(height int64, msg *stakingtypes.MsgBeginRedelegate) error { + var msEvents []dbtypes.MSEvent + event := dbtypes.MSEvent{ + Name: "redelegate", + ValAddr: msg.ValidatorSrcAddress, + DelAddr: msg.DelegatorAddress, + Amount: msg.Amount.String(), + } + msEvents = append(msEvents, event) + return m.db.SaveMSEvent(msEvents, height) +} diff --git a/modules/types/sources.go b/modules/types/sources.go index c627d0fd3..a2d3a5735 100644 --- a/modules/types/sources.go +++ b/modules/types/sources.go @@ -15,12 +15,16 @@ import ( stakingkeeper "github.com/cosmos/cosmos-sdk/x/staking/keeper" stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" "github.com/forbole/juno/v6/node/local" + assetkeeper "github.com/realiotech/realio-network/x/asset/keeper" mintkeeper "github.com/realiotech/realio-network/x/mint/keeper" minttypes "github.com/realiotech/realio-network/x/mint/types" nodeconfig "github.com/forbole/juno/v6/node/config" "cosmossdk.io/log" + assetsource "github.com/forbole/callisto/v4/modules/asset/source" + localassetsource "github.com/forbole/callisto/v4/modules/asset/source/local" + remoteassetsource "github.com/forbole/callisto/v4/modules/asset/source/remote" banksource "github.com/forbole/callisto/v4/modules/bank/source" localbanksource "github.com/forbole/callisto/v4/modules/bank/source/local" remotebanksource "github.com/forbole/callisto/v4/modules/bank/source/remote" @@ -33,6 +37,9 @@ import ( mintsource "github.com/forbole/callisto/v4/modules/mint/source" localmintsource "github.com/forbole/callisto/v4/modules/mint/source/local" remotemintsource "github.com/forbole/callisto/v4/modules/mint/source/remote" + multistakingsource "github.com/forbole/callisto/v4/modules/multistaking/source" + localmultistakingsource "github.com/forbole/callisto/v4/modules/multistaking/source/local" + remotemultistakingsource "github.com/forbole/callisto/v4/modules/multistaking/source/remote" slashingsource "github.com/forbole/callisto/v4/modules/slashing/source" localslashingsource "github.com/forbole/callisto/v4/modules/slashing/source/local" remoteslashingsource "github.com/forbole/callisto/v4/modules/slashing/source/remote" @@ -40,17 +47,22 @@ import ( localstakingsource "github.com/forbole/callisto/v4/modules/staking/source/local" remotestakingsource "github.com/forbole/callisto/v4/modules/staking/source/remote" "github.com/forbole/callisto/v4/utils/simapp" + multistakingkeeper "github.com/realio-tech/multi-staking-module/x/multi-staking/keeper" + multistakingtypes "github.com/realio-tech/multi-staking-module/x/multi-staking/types" realioapp "github.com/realiotech/realio-network/app" + assettypes "github.com/realiotech/realio-network/x/asset/types" "github.com/spf13/viper" ) type Sources struct { - BankSource banksource.Source - DistrSource distrsource.Source - GovSource govsource.Source - MintSource mintsource.Source - SlashingSource slashingsource.Source - StakingSource stakingsource.Source + BankSource banksource.Source + DistrSource distrsource.Source + GovSource govsource.Source + MintSource mintsource.Source + SlashingSource slashingsource.Source + StakingSource stakingsource.Source + AssetSource assetsource.Source + MultistakingSource multistakingsource.Source } func BuildSources(nodeCfg nodeconfig.Config, cdc codec.Codec) (*Sources, error) { @@ -77,12 +89,14 @@ func buildLocalSources(cfg *local.Details, cdc codec.Codec) (*Sources, error) { cfg.Home, 0, realioapp.MakeEncodingConfig(), viper.New(), ) sources := &Sources{ - BankSource: localbanksource.NewSource(source, banktypes.QueryServer(app.BankKeeper)), - DistrSource: localdistrsource.NewSource(source, distrkeeper.NewQuerier(app.DistrKeeper)), - GovSource: localgovsource.NewSource(source, govkeeper.NewQueryServer(&app.GovKeeper)), - MintSource: localmintsource.NewSource(source, mintkeeper.NewQueryServerImpl(realioApp.MintKeeper)), - SlashingSource: localslashingsource.NewSource(source, slashingtypes.QueryServer(app.SlashingKeeper)), - StakingSource: localstakingsource.NewSource(source, stakingkeeper.Querier{Keeper: app.StakingKeeper}), + BankSource: localbanksource.NewSource(source, banktypes.QueryServer(app.BankKeeper)), + DistrSource: localdistrsource.NewSource(source, distrkeeper.NewQuerier(app.DistrKeeper)), + GovSource: localgovsource.NewSource(source, govkeeper.NewQueryServer(&app.GovKeeper)), + MintSource: localmintsource.NewSource(source, mintkeeper.NewQueryServerImpl(realioApp.MintKeeper)), + SlashingSource: localslashingsource.NewSource(source, slashingtypes.QueryServer(app.SlashingKeeper)), + StakingSource: localstakingsource.NewSource(source, stakingkeeper.Querier{Keeper: app.StakingKeeper}), + AssetSource: localassetsource.NewSource(source, assetkeeper.NewQueryServerImpl(realioApp.AssetKeeper)), + MultistakingSource: localmultistakingsource.NewSource(source, multistakingkeeper.NewQueryServerImpl(realioApp.MultiStakingKeeper)), } // Mount and initialize the stores @@ -106,11 +120,13 @@ func buildRemoteSources(cfg *remote.Details) (*Sources, error) { } return &Sources{ - BankSource: remotebanksource.NewSource(source, banktypes.NewQueryClient(source.GrpcConn)), - DistrSource: remotedistrsource.NewSource(source, distrtypes.NewQueryClient(source.GrpcConn)), - GovSource: remotegovsource.NewSource(source, govtypesv1.NewQueryClient(source.GrpcConn)), - MintSource: remotemintsource.NewSource(source, minttypes.NewQueryClient(source.GrpcConn)), - SlashingSource: remoteslashingsource.NewSource(source, slashingtypes.NewQueryClient(source.GrpcConn)), - StakingSource: remotestakingsource.NewSource(source, stakingtypes.NewQueryClient(source.GrpcConn)), + BankSource: remotebanksource.NewSource(source, banktypes.NewQueryClient(source.GrpcConn)), + DistrSource: remotedistrsource.NewSource(source, distrtypes.NewQueryClient(source.GrpcConn)), + GovSource: remotegovsource.NewSource(source, govtypesv1.NewQueryClient(source.GrpcConn)), + MintSource: remotemintsource.NewSource(source, minttypes.NewQueryClient(source.GrpcConn)), + SlashingSource: remoteslashingsource.NewSource(source, slashingtypes.NewQueryClient(source.GrpcConn)), + StakingSource: remotestakingsource.NewSource(source, stakingtypes.NewQueryClient(source.GrpcConn)), + AssetSource: remoteassetsource.NewSource(source, assettypes.NewQueryClient(source.GrpcConn)), + MultistakingSource: remotemultistakingsource.NewSource(source, multistakingtypes.NewQueryClient(source.GrpcConn)), }, nil }