diff --git a/cmd/parse-genesis/parseGenesis.go b/cmd/parse-genesis/parseGenesis.go index 2f08d7cc8..a5b7b50c6 100644 --- a/cmd/parse-genesis/parseGenesis.go +++ b/cmd/parse-genesis/parseGenesis.go @@ -15,7 +15,7 @@ func NewParseGenesisCmd(parseCfg *parse.Config) *cobra.Command { return &cobra.Command{ Use: "parse-genesis [[module names]]", Short: "Parse genesis file. To parse specific modules, input module names as arguments", - Example: "bdjuno parse-genesis auth bank consensus gov history staking", + Example: "bdjuno parse-genesis auth bank consensus gov staking", PreRunE: parse.ReadConfig(parseCfg), RunE: func(cmd *cobra.Command, args []string) error { parseCtx, err := parse.GetParsingContext(parseCfg) diff --git a/database/history.go b/database/history.go deleted file mode 100644 index 10f7bc787..000000000 --- a/database/history.go +++ /dev/null @@ -1,36 +0,0 @@ -package database - -import ( - "fmt" - - "github.com/forbole/bdjuno/v2/types" -) - -// SaveTokenPricesHistory stores the given prices as historic ones -func (db *Db) SaveTokenPricesHistory(prices []types.TokenPrice) error { - if len(prices) == 0 { - return nil - } - - query := `INSERT INTO token_price_history (unit_name, price, market_cap, timestamp) VALUES` - var param []interface{} - - for i, ticker := range prices { - vi := i * 4 - query += fmt.Sprintf("($%d,$%d,$%d,$%d),", vi+1, vi+2, vi+3, vi+4) - param = append(param, ticker.UnitName, ticker.Price, ticker.MarketCap, ticker.Timestamp) - } - - query = query[:len(query)-1] // Remove trailing "," - query += ` -ON CONFLICT ON CONSTRAINT unique_price_for_timestamp DO UPDATE - SET price = excluded.price, - market_cap = excluded.market_cap` - - _, err := db.Sql.Exec(query, param...) - if err != nil { - return fmt.Errorf("error while storing tokens price history: %s", err) - } - - return nil -} diff --git a/database/pricefeed.go b/database/pricefeed.go index 318d9b658..4a4f1554a 100644 --- a/database/pricefeed.go +++ b/database/pricefeed.go @@ -92,3 +92,32 @@ WHERE token_price.timestamp <= excluded.timestamp` return nil } + +// SaveTokenPricesHistory stores the given prices as historic ones +func (db *Db) SaveTokenPricesHistory(prices []types.TokenPrice) error { + if len(prices) == 0 { + return nil + } + + query := `INSERT INTO token_price_history (unit_name, price, market_cap, timestamp) VALUES` + var param []interface{} + + for i, ticker := range prices { + vi := i * 4 + query += fmt.Sprintf("($%d,$%d,$%d,$%d),", vi+1, vi+2, vi+3, vi+4) + param = append(param, ticker.UnitName, ticker.Price, ticker.MarketCap, ticker.Timestamp) + } + + query = query[:len(query)-1] // Remove trailing "," + query += ` +ON CONFLICT ON CONSTRAINT unique_price_for_timestamp DO UPDATE + SET price = excluded.price, + market_cap = excluded.market_cap` + + _, err := db.Sql.Exec(query, param...) + if err != nil { + return fmt.Errorf("error while storing tokens price history: %s", err) + } + + return nil +} diff --git a/database/pricefeed_test.go b/database/pricefeed_test.go index 0aa12fe37..d48e006fe 100644 --- a/database/pricefeed_test.go +++ b/database/pricefeed_test.go @@ -123,3 +123,141 @@ func (suite *DbTestSuite) TestBigDipperDb_SaveTokenPrice() { suite.Require().True(expected[i].Equals(row)) } } + +func (suite *DbTestSuite) TestBigDipperDb_SaveTokenPriceHistory() { + suite.insertToken("desmos") + suite.insertToken("atom") + + // Save data + tickers := []types.TokenPrice{ + types.NewTokenPrice( + "desmos", + 100.01, + 10, + time.Date(2020, 10, 10, 15, 00, 00, 000, time.UTC), + ), + types.NewTokenPrice( + "desmos", + 200.01, + 20, + time.Date(2020, 10, 10, 15, 02, 00, 000, time.UTC), + ), + types.NewTokenPrice( + "atom", + 1, + 20, + time.Date(2020, 10, 10, 15, 00, 00, 000, time.UTC), + ), + types.NewTokenPrice( + "atom", + 1, + 20, + time.Date(2020, 10, 10, 15, 02, 00, 000, time.UTC), + ), + } + err := suite.database.SaveTokenPricesHistory(tickers) + suite.Require().NoError(err) + + // Verify data + expected := []dbtypes.TokenPriceRow{ + dbtypes.NewTokenPriceRow( + "desmos", + 100.01, + 10, + time.Date(2020, 10, 10, 15, 00, 00, 000, time.UTC), + ), + dbtypes.NewTokenPriceRow( + "desmos", + 200.01, + 20, + time.Date(2020, 10, 10, 15, 02, 00, 000, time.UTC), + ), + dbtypes.NewTokenPriceRow( + "atom", + 1, + 20, + time.Date(2020, 10, 10, 15, 00, 00, 000, time.UTC), + ), + dbtypes.NewTokenPriceRow( + "atom", + 1, + 20, + time.Date(2020, 10, 10, 15, 02, 00, 000, time.UTC), + ), + } + + var rows []dbtypes.TokenPriceRow + err = suite.database.Sqlx.Select(&rows, `SELECT * FROM token_price_history`) + suite.Require().NoError(err) + + for i, row := range rows { + suite.Require().True(expected[i].Equals(row)) + } + + // Update data + tickers = []types.TokenPrice{ + types.NewTokenPrice( + "desmos", + 100.01, + 10, + time.Date(2020, 10, 10, 15, 00, 00, 000, time.UTC), + ), + types.NewTokenPrice( + "desmos", + 300.01, + 20, + time.Date(2020, 10, 10, 15, 02, 00, 000, time.UTC), + ), + types.NewTokenPrice( + "atom", + 1, + 20, + time.Date(2020, 10, 10, 15, 00, 00, 000, time.UTC), + ), + types.NewTokenPrice( + "atom", + 10, + 20, + time.Date(2020, 10, 10, 15, 02, 00, 000, time.UTC), + ), + } + err = suite.database.SaveTokenPricesHistory(tickers) + suite.Require().NoError(err) + + // Verify data + expected = []dbtypes.TokenPriceRow{ + dbtypes.NewTokenPriceRow( + "desmos", + 100.01, + 10, + time.Date(2020, 10, 10, 15, 00, 00, 000, time.UTC), + ), + dbtypes.NewTokenPriceRow( + "atom", + 1, + 20, + time.Date(2020, 10, 10, 15, 00, 00, 000, time.UTC), + ), + dbtypes.NewTokenPriceRow( + "desmos", + 300.01, + 20, + time.Date(2020, 10, 10, 15, 02, 00, 000, time.UTC), + ), + + dbtypes.NewTokenPriceRow( + "atom", + 10, + 20, + time.Date(2020, 10, 10, 15, 02, 00, 000, time.UTC), + ), + } + + rows = []dbtypes.TokenPriceRow{} + err = suite.database.Sqlx.Select(&rows, `SELECT * FROM token_price_history ORDER BY timestamp`) + suite.Require().NoError(err) + + for i, row := range rows { + suite.Require().True(expected[i].Equals(row)) + } +} diff --git a/modules/history/module.go b/modules/history/module.go deleted file mode 100644 index c3b0ff37e..000000000 --- a/modules/history/module.go +++ /dev/null @@ -1,42 +0,0 @@ -package history - -import ( - "github.com/cosmos/cosmos-sdk/codec" - "github.com/forbole/juno/v2/modules" - "github.com/forbole/juno/v2/modules/messages" - "github.com/forbole/juno/v2/types/config" - - "github.com/forbole/bdjuno/v2/database" -) - -const ( - moduleName = "history" -) - -var ( - _ modules.Module = &Module{} -) - -// Module represents the module that allows to store historic information -type Module struct { - cfg config.ChainConfig - cdc codec.Marshaler - db *database.Db - - getAddresses messages.MessageAddressesParser -} - -// NewModule allows to build a new Module instance -func NewModule(cfg config.ChainConfig, messagesParser messages.MessageAddressesParser, cdc codec.Marshaler, db *database.Db) *Module { - return &Module{ - cfg: cfg, - cdc: cdc, - db: db, - getAddresses: messagesParser, - } -} - -// Name implements modules.Module -func (m *Module) Name() string { - return moduleName -} diff --git a/modules/history/utils_prices.go b/modules/history/utils_prices.go deleted file mode 100644 index 492782821..000000000 --- a/modules/history/utils_prices.go +++ /dev/null @@ -1,14 +0,0 @@ -package history - -import ( - "github.com/forbole/bdjuno/v2/types" -) - -// UpdatePricesHistory stores the given prices inside the price history table -func (m *Module) UpdatePricesHistory(prices []types.TokenPrice) error { - if !m.cfg.IsModuleEnabled(moduleName) { - return nil - } - - return m.db.SaveTokenPricesHistory(prices) -} diff --git a/modules/pricefeed/expected_modules.go b/modules/pricefeed/expected_modules.go deleted file mode 100644 index 19ac16113..000000000 --- a/modules/pricefeed/expected_modules.go +++ /dev/null @@ -1,7 +0,0 @@ -package pricefeed - -import "github.com/forbole/bdjuno/v2/types" - -type HistoryModule interface { - UpdatePricesHistory([]types.TokenPrice) error -} diff --git a/modules/pricefeed/handle_additional_operations.go b/modules/pricefeed/handle_additional_operations.go index 3ed557792..e5bd62459 100644 --- a/modules/pricefeed/handle_additional_operations.go +++ b/modules/pricefeed/handle_additional_operations.go @@ -56,5 +56,5 @@ func (m *Module) storeTokens() error { return fmt.Errorf("error while storing token prices: %s", err) } - return m.historyModule.UpdatePricesHistory(prices) + return nil } diff --git a/modules/pricefeed/handle_periodic_operations.go b/modules/pricefeed/handle_periodic_operations.go index 4cd5d023d..00f4332c2 100644 --- a/modules/pricefeed/handle_periodic_operations.go +++ b/modules/pricefeed/handle_periodic_operations.go @@ -2,10 +2,13 @@ package pricefeed import ( "fmt" + "time" "github.com/go-co-op/gocron" "github.com/rs/zerolog/log" + "github.com/forbole/bdjuno/v2/types" + "github.com/forbole/bdjuno/v2/modules/pricefeed/coingecko" "github.com/forbole/bdjuno/v2/modules/utils" ) @@ -14,38 +17,55 @@ import ( func (m *Module) RegisterPeriodicOperations(scheduler *gocron.Scheduler) error { log.Debug().Str("module", "pricefeed").Msg("setting up periodic tasks") - // Fetch total supply of token in 30 seconds each - if _, err := scheduler.Every(30).Second().Do(func() { + // Fetch the token prices every 2 mins + if _, err := scheduler.Every(2).Minutes().Do(func() { utils.WatchMethod(m.updatePrice) }); err != nil { return fmt.Errorf("error while setting up pricefeed period operations: %s", err) } + // Update the historical token prices every 1 hour + if _, err := scheduler.Every(1).Hour().Do(func() { + utils.WatchMethod(m.updatePricesHistory) + }); err != nil { + return fmt.Errorf("error while setting up history period operations: %s", err) + } + return nil } -// updatePrice fetch total amount of coins in the system from RPC and store it into database -func (m *Module) updatePrice() error { - log.Debug(). - Str("module", "pricefeed"). - Str("operation", "pricefeed"). - Msg("getting token price and market cap") - +// getTokenPrices allows to get the most up-to-date token prices +func (m *Module) getTokenPrices() ([]types.TokenPrice, error) { // Get the list of tokens price id ids, err := m.db.GetTokensPriceID() if err != nil { - return fmt.Errorf("error while getting tokens price id: %s", err) + return nil, fmt.Errorf("error while getting tokens price id: %s", err) } if len(ids) == 0 { log.Debug().Str("module", "pricefeed").Msg("no traded tokens price id found") - return nil + return nil, nil } // Get the tokens prices prices, err := coingecko.GetTokensPrices(ids) if err != nil { - return fmt.Errorf("error while getting tokens prices: %s", err) + return nil, fmt.Errorf("error while getting tokens prices: %s", err) + } + + return prices, nil +} + +// updatePrice fetch total amount of coins in the system from RPC and store it into database +func (m *Module) updatePrice() error { + log.Debug(). + Str("module", "pricefeed"). + Str("operation", "pricefeed"). + Msg("updating token price and market cap") + + prices, err := m.getTokenPrices() + if err != nil { + return err } // Save the token prices @@ -54,5 +74,36 @@ func (m *Module) updatePrice() error { return fmt.Errorf("error while saving token prices: %s", err) } - return m.historyModule.UpdatePricesHistory(prices) + return nil + +} + +// updatePricesHistory fetches total amount of coins in the system from RPC +// and stores historical perice data inside the database +func (m *Module) updatePricesHistory() error { + log.Debug(). + Str("module", "pricefeed"). + Str("operation", "pricefeed"). + Msg("updating token price and market cap history") + + prices, err := m.getTokenPrices() + if err != nil { + return err + } + + // Normally, the last updated value reflects the time when the price was last updated. + // If price hasn't changed, the returned timestamp will be the same as one hour ago, and it will not + // be stored in db as it will be a duplicated value. + // To fix this, we set each price timestamp to be the same as other ones. + timestamp := time.Now() + for _, price := range prices { + price.Timestamp = timestamp + } + + err = m.db.SaveTokenPricesHistory(prices) + if err != nil { + return fmt.Errorf("error while saving token prices history: %s", err) + } + + return nil } diff --git a/modules/pricefeed/module.go b/modules/pricefeed/module.go index d9bacd046..f327bd268 100644 --- a/modules/pricefeed/module.go +++ b/modules/pricefeed/module.go @@ -17,24 +17,22 @@ var ( // Module represents the module that allows to get the token prices type Module struct { - cfg *Config - cdc codec.Marshaler - db *database.Db - historyModule HistoryModule + cfg *Config + cdc codec.Marshaler + db *database.Db } // NewModule returns a new Module instance -func NewModule(cfg config.Config, historyModule HistoryModule, cdc codec.Marshaler, db *database.Db) *Module { +func NewModule(cfg config.Config, cdc codec.Marshaler, db *database.Db) *Module { pricefeedCfg, err := ParseConfig(cfg.GetBytes()) if err != nil { panic(err) } return &Module{ - cfg: pricefeedCfg, - cdc: cdc, - db: db, - historyModule: historyModule, + cfg: pricefeedCfg, + cdc: cdc, + db: db, } } diff --git a/modules/registrar.go b/modules/registrar.go index 7b896f45c..6c426a203 100644 --- a/modules/registrar.go +++ b/modules/registrar.go @@ -13,7 +13,6 @@ import ( "github.com/cosmos/cosmos-sdk/simapp/params" "github.com/forbole/juno/v2/node/remote" - "github.com/forbole/bdjuno/v2/modules/history" "github.com/forbole/bdjuno/v2/modules/slashing" "github.com/cosmos/cosmos-sdk/codec" @@ -110,7 +109,6 @@ func (r *Registrar) BuildModules(ctx registrar.Context) jmodules.Modules { bankModule := bank.NewModule(r.parser, sources.BankSource, cdc, db) consensusModule := consensus.NewModule(db) distrModule := distribution.NewModule(sources.DistrSource, cdc, db) - historyModule := history.NewModule(ctx.JunoConfig.Chain, r.parser, cdc, db) mintModule := mint.NewModule(sources.MintSource, cdc, db) slashingModule := slashing.NewModule(sources.SlashingSource, cdc, db) stakingModule := staking.NewModule(sources.StakingSource, slashingModule, cdc, db) @@ -126,10 +124,9 @@ func (r *Registrar) BuildModules(ctx registrar.Context) jmodules.Modules { consensusModule, distrModule, govModule, - historyModule, mintModule, modules.NewModule(ctx.JunoConfig.Chain, db), - pricefeed.NewModule(ctx.JunoConfig, historyModule, cdc, db), + pricefeed.NewModule(ctx.JunoConfig, cdc, db), slashingModule, stakingModule, }