diff --git a/database/auth.go b/database/auth.go index d513da6bd..a5bee32a9 100644 --- a/database/auth.go +++ b/database/auth.go @@ -2,8 +2,14 @@ package database import ( "fmt" + "time" + "github.com/cosmos/cosmos-sdk/x/auth/vesting/exported" + vestingtypes "github.com/cosmos/cosmos-sdk/x/auth/vesting/types" + dbtypes "github.com/forbole/bdjuno/v2/database/types" dbutils "github.com/forbole/bdjuno/v2/database/utils" + "github.com/gogo/protobuf/proto" + "github.com/lib/pq" "github.com/forbole/bdjuno/v2/types" ) @@ -52,6 +58,86 @@ func (db *Db) saveAccounts(paramsNumber int, accounts []types.Account) error { return nil } +// SaveVestingAccounts saves the given vesting accounts inside the database +func (db *Db) SaveVestingAccounts(vestingAccounts []exported.VestingAccount) error { + if len(vestingAccounts) == 0 { + return nil + } + + for _, account := range vestingAccounts { + switch vestingAccount := account.(type) { + case *vestingtypes.ContinuousVestingAccount, *vestingtypes.DelayedVestingAccount: + _, err := db.storeVestingAccount(account) + if err != nil { + return err + } + + case *vestingtypes.PeriodicVestingAccount: + vestingAccountRowID, err := db.storeVestingAccount(account) + if err != nil { + return err + } + err = db.storeVestingPeriods(vestingAccountRowID, vestingAccount.VestingPeriods) + if err != nil { + return err + } + } + } + + return nil +} + +func (db *Db) storeVestingAccount(account exported.VestingAccount) (int, error) { + stmt := ` + INSERT INTO vesting_account (type, address, original_vesting, end_time, start_time) + VALUES ($1, $2, $3, $4, $5) + ON CONFLICT (address) DO UPDATE + SET original_vesting = excluded.original_vesting, + end_time = excluded.end_time, + start_time = excluded.start_time + RETURNING id ` + + var vestingAccountRowID int + err := db.Sql.QueryRow(stmt, + proto.MessageName(account), + account.GetAddress().String(), + pq.Array(dbtypes.NewDbCoins(account.GetOriginalVesting())), + time.Unix(account.GetEndTime(), 0), + time.Unix(account.GetStartTime(), 0), + ).Scan(&vestingAccountRowID) + + if err != nil { + return vestingAccountRowID, fmt.Errorf("error while saving Vesting Account of type %v: %s", proto.MessageName(account), err) + } + + return vestingAccountRowID, nil +} + +// storeVestingPeriods handles storing the vesting periods of PeriodicVestingAccount type +func (db *Db) storeVestingPeriods(id int, vestingPeriods []vestingtypes.Period) error { + stmt := ` +INSERT INTO vesting_period (vesting_account_id, period_order, length, amount) +VALUES ` + + var params []interface{} + for i, period := range vestingPeriods { + ai := i * 4 + stmt += fmt.Sprintf("($%d,$%d,$%d,$%d),", ai+1, ai+2, ai+3, ai+4) + + order := i + amount := pq.Array(dbtypes.NewDbCoins(period.Amount)) + params = append(params, id, order, period.Length, amount) + } + stmt = stmt[:len(stmt)-1] + + _, err := db.Sql.Exec(stmt, params...) + if err != nil { + return fmt.Errorf("error while saving vesting periods: %s", err) + } + + return nil +} + // GetAccounts returns all the accounts that are currently stored inside the database. func (db *Db) GetAccounts() ([]string, error) { var rows []string diff --git a/database/schema/01-auth.sql b/database/schema/01-auth.sql index e2465a40b..16f9689ba 100644 --- a/database/schema/01-auth.sql +++ b/database/schema/01-auth.sql @@ -1,4 +1,34 @@ CREATE TABLE account ( address TEXT NOT NULL PRIMARY KEY +); + +/* ---- Moved from bank.sql for vesting account usage ---- */ +CREATE TYPE COIN AS +( + denom TEXT, + amount TEXT +); + +/* ---- AUTH/ VESTING ACCOUNT ---- */ +CREATE TABLE vesting_account +( + id SERIAL PRIMARY KEY NOT NULL, + type TEXT NOT NULL, + address TEXT NOT NULL REFERENCES account (address), + original_vesting COIN[] NOT NULL DEFAULT '{}', + end_time TIMESTAMP WITHOUT TIME ZONE NOT NULL, + start_time TIMESTAMP WITHOUT TIME ZONE +); +/* ---- start_time can be empty on DelayedVestingAccount ---- */ + +CREATE UNIQUE INDEX vesting_account_address_idx ON vesting_account (address); + + +CREATE TABLE vesting_period +( + vesting_account_id BIGINT NOT NULL REFERENCES vesting_account (id), + period_order BIGINT NOT NULL, + length BIGINT NOT NULL, + amount COIN[] NOT NULL DEFAULT '{}' ); \ No newline at end of file diff --git a/database/schema/02-bank.sql b/database/schema/02-bank.sql index 4fc688498..9a2408d1c 100644 --- a/database/schema/02-bank.sql +++ b/database/schema/02-bank.sql @@ -1,9 +1,3 @@ -CREATE TYPE COIN AS -( - denom TEXT, - amount TEXT -); - /* ---- SUPPLY ---- */ CREATE TABLE supply diff --git a/modules/auth/auth_vesting_accounts.go b/modules/auth/auth_vesting_accounts.go new file mode 100644 index 000000000..9476ebd9c --- /dev/null +++ b/modules/auth/auth_vesting_accounts.go @@ -0,0 +1,35 @@ +package auth + +import ( + "encoding/json" + + "github.com/cosmos/cosmos-sdk/codec" + authttypes "github.com/cosmos/cosmos-sdk/x/auth/types" + "github.com/cosmos/cosmos-sdk/x/auth/vesting/exported" +) + +// GetGenesisVestingAccounts parses the given appState and returns the genesis vesting accounts +func GetGenesisVestingAccounts(appState map[string]json.RawMessage, cdc codec.Marshaler) ([]exported.VestingAccount, error) { + var authState authttypes.GenesisState + if err := cdc.UnmarshalJSON(appState[authttypes.ModuleName], &authState); err != nil { + return nil, err + } + + // Build vestingAccounts Array + vestingAccounts := []exported.VestingAccount{} + for _, account := range authState.Accounts { + var accountI authttypes.AccountI + err := cdc.UnpackAny(account, &accountI) + if err != nil { + return nil, err + } + + vestingAccount, ok := accountI.(exported.VestingAccount) + if !ok { + continue + } + vestingAccounts = append(vestingAccounts, vestingAccount) + } + + return vestingAccounts, nil +} diff --git a/modules/auth/handle_genesis.go b/modules/auth/handle_genesis.go index 00ab20945..a0fa1b072 100644 --- a/modules/auth/handle_genesis.go +++ b/modules/auth/handle_genesis.go @@ -12,16 +12,23 @@ import ( // HandleGenesis implements modules.GenesisModule func (m *Module) HandleGenesis(_ *tmtypes.GenesisDoc, appState map[string]json.RawMessage) error { log.Debug().Str("module", "auth").Msg("parsing genesis") - accounts, err := GetGenesisAccounts(appState, m.cdc) if err != nil { return fmt.Errorf("error while getting genesis accounts: %s", err) } - err = m.db.SaveAccounts(accounts) if err != nil { return fmt.Errorf("error while storing genesis accounts: %s", err) } + vestingAccounts, err := GetGenesisVestingAccounts(appState, m.cdc) + if err != nil { + return fmt.Errorf("error while getting genesis vesting accounts: %s", err) + } + err = m.db.SaveVestingAccounts(vestingAccounts) + if err != nil { + return fmt.Errorf("error while storing genesis vesting accounts: %s", err) + } + return nil }