diff --git a/.gitignore b/.gitignore index c75a80a..404c728 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,10 @@ /logs.txt /config.ini /bin/ -./indexer \ No newline at end of file +./indexer +indexer +build.sh +log +dev_config.json +test_config.json +dev_config_jsonrpc.json diff --git a/README.md b/README.md index 8ded4a3..37ead05 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,7 @@ make build install ### Build indexer ``` make build install-indexer -indexer -config config.json +indexer --config config.json or indexer -c config.json ``` @@ -42,7 +42,7 @@ indexer -config config.json ### Build apiserver ``` make build install-jsonrpc -apiserver -config config_jsonrpc.json +apiserver --config config_jsonrpc.json or apiserver -c config_jsonrpc.json ``` diff --git a/client/evm/client.go b/client/evm/client.go index d2ea45c..fb4a0c1 100644 --- a/client/evm/client.go +++ b/client/evm/client.go @@ -24,6 +24,7 @@ package evm import ( "context" + "encoding/json" "errors" "fmt" "github.com/ethereum/go-ethereum" @@ -57,7 +58,7 @@ func (ec *RawClient) Client() *rpc.Client { } func (ec *RawClient) doCallContext(retry int, result interface{}, method string, args ...interface{}) (err error) { - timeCtx, cancel := context.WithTimeout(context.Background(), time.Second*5) + timeCtx, cancel := context.WithTimeout(context.Background(), time.Second*10) defer cancel() t1 := time.Now() @@ -88,6 +89,8 @@ func (ec *RawClient) CallContext(ctx context.Context, result interface{}, method if result == nil { return rpc.ErrNoResult } + d, _ := json.Marshal(result) + xylog.Logger.Debugf("CallContext result=%v", string(d)) return nil } diff --git a/cmd/indexer/main.go b/cmd/indexer/main.go index 40f9d08..5ac3ec8 100644 --- a/cmd/indexer/main.go +++ b/cmd/indexer/main.go @@ -26,15 +26,15 @@ import ( "context" "flag" "github.com/sirupsen/logrus" + "github.com/spf13/pflag" "github.com/uxuycom/indexer/client" - "github.com/uxuycom/indexer/devents" - "github.com/uxuycom/indexer/protocol" - "github.com/uxuycom/indexer/xylog" - "github.com/uxuycom/indexer/config" "github.com/uxuycom/indexer/dcache" + "github.com/uxuycom/indexer/devents" "github.com/uxuycom/indexer/explorer" + "github.com/uxuycom/indexer/protocol" "github.com/uxuycom/indexer/storage" + "github.com/uxuycom/indexer/xylog" "net/http" _ "net/http/pprof" "os" @@ -107,6 +107,8 @@ func main() { } func initArgs() { - flag.StringVar(&flagConfig, "config", "config.json", "config file") - flag.Parse() + + pflag.StringVarP(&flagConfig, "config", "c", "config.json", "config file") + pflag.CommandLine.AddGoFlagSet(flag.CommandLine) + pflag.Parse() } diff --git a/cmd/jsonrpc/main.go b/cmd/jsonrpc/main.go index a714768..c2f56b7 100644 --- a/cmd/jsonrpc/main.go +++ b/cmd/jsonrpc/main.go @@ -23,8 +23,10 @@ package main import ( + "encoding/json" "flag" "github.com/sirupsen/logrus" + "github.com/spf13/pflag" "github.com/uxuycom/indexer/config" "github.com/uxuycom/indexer/jsonrpc" "github.com/uxuycom/indexer/storage" @@ -36,7 +38,7 @@ import ( ) var ( - cfg config.JsonRcpConfig + cfg config.RpcConfig flagConfig string ) @@ -47,7 +49,13 @@ func main() { config.LoadJsonRpcConfig(&cfg, flagConfig) - logLevel, _ := logrus.ParseLevel(cfg.LogLevel) + cfgJson, _ := json.Marshal(&cfg) + log.Printf("start server with config = %v\n", string(cfgJson)) + + logLevel, err := logrus.ParseLevel(cfg.LogLevel) + if err != nil { + log.Printf("start server parse log level err = %v\n", err) + } xylog.InitLog(logLevel, cfg.LogPath) //db client @@ -57,7 +65,7 @@ func main() { return } //init server - server, err := jsonrpc.NewRPCServer(dbc, cfg.CacheStore) + server, err := jsonrpc.NewRPCServer(dbc, &cfg) if err != nil { log.Fatalf("server init err[%v]", err) } @@ -83,6 +91,8 @@ func main() { } func initArgs() { - flag.StringVar(&flagConfig, "config", "config_jsonrpc.json", "config file") - flag.Parse() + + pflag.StringVarP(&flagConfig, "config", "c", "config_jsonrpc.json", "config file") + pflag.CommandLine.AddGoFlagSet(flag.CommandLine) + pflag.Parse() } diff --git a/config.json b/config.json index a654d0f..9f3302c 100644 --- a/config.json +++ b/config.json @@ -37,5 +37,9 @@ "profile": { "enabled": false, "listen": ":6060" + }, + "stat": { + "address_start_id": 348870000, + "balance_start_id": 390790000 } } \ No newline at end of file diff --git a/config/config.go b/config/config.go index 9735f33..dc2d2fc 100644 --- a/config/config.go +++ b/config/config.go @@ -23,26 +23,30 @@ package config import ( - "encoding/json" + "github.com/spf13/viper" "github.com/uxuycom/indexer/model" "log" - "os" "path/filepath" ) type ScanConfig struct { - StartBlock uint64 `json:"start_block"` - BlockBatchWorkers uint64 `json:"block_batch_workers"` - TxBatchWorkers uint64 `json:"tx_batch_workers"` - DelayedBlockNum uint64 `json:"delayed_block_num"` + StartBlock uint64 `json:"start_block" mapstructure:"start_block"` + BlockBatchWorkers uint64 `json:"block_batch_workers" mapstructure:"block_batch_workers"` + TxBatchWorkers uint64 `json:"tx_batch_workers" mapstructure:"tx_batch_workers"` + DelayedBlockNum uint64 `json:"delayed_block_num" mapstructure:"delayed_block_num"` } type ChainConfig struct { - ChainName string `json:"chain_name"` + ChainName string `json:"chain_name" mapstructure:"chain_name"` Rpc string `json:"rpc"` UserName string `json:"username"` PassWord string `json:"password"` - ChainGroup model.ChainGroup `json:"chain_group"` + ChainGroup model.ChainGroup `json:"chain_group" mapstructure:"chain_group"` +} + +type StatConfig struct { + AddressStartId uint64 `json:"address_start_id" mapstructure:"address_start_id"` + BalanceStartId uint64 `json:"balance_start_id" mapstructure:"balance_start_id"` } type IndexFilter struct { @@ -50,14 +54,14 @@ type IndexFilter struct { Ticks []string `json:"ticks"` Protocols []string `json:"protocols"` } `json:"whitelist"` - EventTopics []string `json:"event_topics"` + EventTopics []string `json:"event_topics" mapstructure:"event_topics"` } // DatabaseConfig database config type DatabaseConfig struct { Type string `json:"type"` Dsn string `json:"dsn"` - EnableLog bool `json:"enable_log"` + EnableLog bool `json:"enable_log" mapstructure:"enable_log"` } type ProfileConfig struct { @@ -68,82 +72,67 @@ type ProfileConfig struct { type Config struct { Scan ScanConfig `json:"scan"` Chain ChainConfig `json:"chain"` - LogLevel string `json:"log_level"` - LogPath string `json:"log_path"` + LogLevel string `json:"log_level" mapstructure:"log_level"` + LogPath string `json:"log_path" mapstructure:"log_path"` Filters *IndexFilter `json:"filters"` Database DatabaseConfig `json:"database"` Profile *ProfileConfig `json:"profile"` + Stat *StatConfig `json:"stat"` } -type JsonRcpConfig struct { - RpcListen []string `json:"rpclisten"` - RpcMaxClients int64 `json:"rpcmaxclients"` - LogLevel string `json:"log_level"` - LogPath string `json:"log_path"` - Database DatabaseConfig `json:"database"` - Profile *ProfileConfig `json:"profile"` - CacheStore *CacheConfig `json:"cache_store"` +type RpcConfig struct { + LogLevel string `json:"log_level" mapstructure:"log_level"` + LogPath string `json:"log_path" mapstructure:"log_path"` + Database DatabaseConfig `json:"database"` + Profile *ProfileConfig `json:"profile"` + CacheStore *CacheConfig `json:"cache_store" mapstructure:"cache_store"` + DebugLevel string `json:"debug_level" mapstructure:"debug_level"` + DisableTLS bool `json:"notls" description:"Disable TLS for the RPC server -- NOTE: This is only allowed if the RPC server is bound to localhost"` + RPCCert string `json:"rpccert" description:"File containing the certificate file"` + RPCKey string `json:"rpckey" description:"File containing the certificate key"` + RPCLimitPass string `json:"rpclimitpass" default-mask:"-" description:"Password for limited RPC connections"` + RPCLimitUser string `json:"rpclimituser" description:"Username for limited RPC connections"` + RPCListeners []string `json:"rpclisten" mapstructure:"rpclisten" description:"Add an interface/port to listen for RPC +connections (default port: 6583, testnet: 16583)"` + RPCMaxClients int `json:"rpcmaxclients" description:"Max number of RPC clients for standard connections"` + RPCMaxConcurrentReqs int `json:"rpcmaxconcurrentreqs" description:"Max number of concurrent RPC requests that may be processed concurrently"` + RPCMaxWebsockets int `json:"rpcmaxwebsockets" description:"Max number of RPC websocket connections"` + RPCQuirks bool `json:"rpcquirks" description:"Mirror some JSON-RPC quirks of Bitcoin Core -- NOTE: Discouraged unless interoperability issues need to be worked around"` + RPCPass string `json:"rpcpass" default-mask:"-" description:"Password for RPC connections"` + RPCUser string `json:"rpcuser" description:"Username for RPC connections"` } type CacheConfig struct { Started bool `json:"started"` - MaxCapacity int64 `json:"max_capacity"` + MaxCapacity int64 `json:"max_capacity" mapstructure:"max_capacity"` Duration uint32 `json:"duration"` } -func LoadConfig(cfg *Config, filePath string) { - // Default config. - configFileName := "config.json" - if len(os.Args) > 1 { - configFileName = os.Args[1] - } - - configFileName, _ = filepath.Abs(configFileName) - log.Printf("Loading config: %v", configFileName) +func LoadConfig(cfg *Config, configFile string) { + UnmarshalConfig(configFile, cfg) +} - if filePath != "" { - configFileName = filePath - } - configFile, err := os.Open(configFileName) - if err != nil { - log.Fatal("File error: ", err.Error()) - } - defer func() { - _ = configFile.Close() - }() - jsonParser := json.NewDecoder(configFile) - if err := jsonParser.Decode(&cfg); err != nil { - log.Fatal("Config error: ", err.Error()) - } +func LoadJsonRpcConfig(cfg *RpcConfig, configFile string) { + UnmarshalConfig(configFile, cfg) } -func LoadJsonRpcConfig(cfg *JsonRcpConfig, filePath string) { - // Default config. - configFileName := "config_jsonrpc.json" - if len(os.Args) > 1 { - configFileName = os.Args[1] - } +func UnmarshalConfig(configFile string, cfg interface{}) { + fileName := filepath.Base(configFile) + viper.SetConfigFile(fileName) + viper.SetConfigType("json") - configFileName, _ = filepath.Abs(configFileName) - log.Printf("Loading config: %v", configFileName) + dir := filepath.Dir(configFile) + viper.AddConfigPath(dir) - if filePath != "" { - configFileName = filePath + if err := viper.ReadInConfig(); err != nil { + log.Fatalf("Read file error, error:%v", err.Error()) } - configFile, err := os.Open(configFileName) - if err != nil { - log.Fatal("File error: ", err.Error()) - } - defer func() { - _ = configFile.Close() - }() - jsonParser := json.NewDecoder(configFile) - if err := jsonParser.Decode(&cfg); err != nil { - log.Fatal("Config error: ", err.Error()) + if err := viper.Unmarshal(&cfg); err != nil { + log.Fatalf("Unmarshal config fail! error:%v ", err) } + viper.WatchConfig() } - -func (cfg *JsonRcpConfig) GetConfig() *JsonRcpConfig { +func (cfg *RpcConfig) GetConfig() *RpcConfig { return cfg } diff --git a/config_jsonrpc.json b/config_jsonrpc.json index d3a9aee..182fafb 100644 --- a/config_jsonrpc.json +++ b/config_jsonrpc.json @@ -20,4 +20,4 @@ "enabled": false, "listen": ":6060" } -} +} \ No newline at end of file diff --git a/db/20240221-create-chain-info-add-index.sql b/db/20240221-create-chain-info-add-index.sql new file mode 100644 index 0000000..cca1f92 --- /dev/null +++ b/db/20240221-create-chain-info-add-index.sql @@ -0,0 +1,46 @@ +Use +tap_indexer; +-- chain statics by hour table --------- +CREATE TABLE `chain_stats_hour` +( + `id` int unsigned NOT NULL AUTO_INCREMENT, + `chain` varchar(32) COLLATE utf8mb4_general_ci NOT NULL COMMENT 'chain name', + `date_hour` int unsigned NOT NULL COMMENT 'date_hour', + `address_count` int unsigned NOT NULL COMMENT 'address_count', + `address_last_id` bigint unsigned NOT NULL COMMENT 'address_last_id', + `inscriptions_count` int unsigned NOT NULL COMMENT 'inscriptions_count', + `balance_sum` DECIMAL(38, 18) NOT NULL COMMENT 'balance_sum', + `balance_last_id` bigint unsigned NOT NULL COMMENT 'balance_last_id', + `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + UNIQUE KEY `uqx_chain_date_hour` (`chain`, `date_hour`) +) ENGINE = InnoDB + DEFAULT CHARSET = utf8mb4 + COLLATE = utf8mb4_general_ci; + +-- chain info --------- +CREATE TABLE `chain_info` +( + `id` int unsigned NOT NULL AUTO_INCREMENT, + `chain_id` int unsigned NOT NULL COMMENT 'chain id', + `chain` varchar(32) COLLATE utf8mb4_general_ci NOT NULL COMMENT 'inner chain name', + `outer_chain` varchar(32) COLLATE utf8mb4_general_ci NOT NULL COMMENT 'outer chain name', + `name` varchar(32) COLLATE utf8mb4_general_ci NOT NULL COMMENT 'name', + `logo` varchar(1024) COLLATE utf8mb4_general_ci NOT NULL COMMENT 'logo url', + `network_id` int unsigned NOT NULL COMMENT 'network id', + `ext` varchar(4098) NOT NUll COMMENT 'ext', + `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + UNIQUE KEY `uqx_chain_id_chain_name` (`chain_id`, `chain`, `name`) +) ENGINE = InnoDB + DEFAULT CHARSET = utf8mb4 + COLLATE = utf8mb4_general_ci; + +INSERT INTO chain_info (chain_id, chain, outer_chain, name, logo, network_id,ext)VALUES (1, 'eth', 'eth', 'Ethereum', '', 1, ''); +INSERT INTO chain_info (chain_id, chain, outer_chain, name, logo, network_id,ext)VALUES (43114, 'avalanche', 'avax', 'Avalanche', '', 43114, ''); +INSERT INTO chain_info (chain_id, chain, outer_chain, name, logo, network_id,ext)VALUES (42161, 'arbitrum', 'ETH', 'Arbitrum One', '', 42161, ''); +INSERT INTO chain_info (chain_id, chain, outer_chain, name, logo, network_id,ext)VALUES (56, 'bsc', 'BSC', 'BNB Smart Chain Mainnet', '', 56, ''); +INSERT INTO chain_info (chain_id, chain, outer_chain, name, logo, network_id,ext)VALUES (250, 'fantom', 'FTM', 'Fantom Opera', '', 250, ''); +INSERT INTO chain_info (chain_id, chain, outer_chain, name, logo, network_id,ext)VALUES (137, 'polygon', 'Polygon', 'Polygon Mainnet', '', 137, ''); \ No newline at end of file diff --git a/db/20240222_alter_tx_hash_chainid.sql b/db/20240222_alter_tx_hash_chainid.sql new file mode 100644 index 0000000..77bdbe2 --- /dev/null +++ b/db/20240222_alter_tx_hash_chainid.sql @@ -0,0 +1,11 @@ + +Use +tap_indexer; + +CREATE INDEX idx_tx_hash ON balance_txn (tx_hash(12)); +ALTER TABLE txs MODIFY COLUMN tx_hash VARBINARY(128); +ALTER TABLE address_txs MODIFY COLUMN tx_hash VARBINARY(128); +ALTER TABLE balance_txn MODIFY COLUMN tx_hash VARBINARY(128); +ALTER TABLE address_txs ADD related_address varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL + COMMENT 'related address'; +ALTER TABLE block ADD chain_id BIGINT NOT NULL DEFAULT 0 COMMENT "chain id"; diff --git a/db/init_mysql.sql b/db/init_mysql.sql index a954eb4..5767d65 100644 --- a/db/init_mysql.sql +++ b/db/init_mysql.sql @@ -173,4 +173,4 @@ CREATE TABLE `block` UNIQUE KEY `uqx_chain` (`chain`) ) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 - COLLATE = utf8mb4_general_ci; \ No newline at end of file + COLLATE = utf8mb4_general_ci; diff --git a/dcache/manager.go b/dcache/manager.go index ed0dd76..bf3e058 100644 --- a/dcache/manager.go +++ b/dcache/manager.go @@ -58,6 +58,10 @@ func NewManager(db *storage.DBClient, chain string) *Manager { return e } +func (h *Manager) GetDataSource() *storage.DBClient { + return h.db +} + func (h *Manager) initInscriptionCache(chain string) { h.Inscription = NewInscription() diff --git a/devents/cache.go b/devents/cache.go index d48fece..b9a33a8 100644 --- a/devents/cache.go +++ b/devents/cache.go @@ -25,6 +25,7 @@ package devents import ( "github.com/shopspring/decimal" "github.com/uxuycom/indexer/dcache" + "github.com/uxuycom/indexer/xylog" ) type TxResultHandler struct { @@ -141,5 +142,6 @@ func (tc *TxResultHandler) updateTransferCache(r *TxResult) { if holders == 0 { return } + xylog.Logger.Infof("update tick[%s] holders. holders[%d]", r.MD.Tick, holders) tc.cache.InscriptionStats.Holders(r.MD.Protocol, r.MD.Tick, holders) } diff --git a/devents/event.go b/devents/event.go index 304b42f..19219fb 100644 --- a/devents/event.go +++ b/devents/event.go @@ -33,6 +33,7 @@ import ( type Event struct { Chain string + ChainId int64 BlockNum uint64 BlockTime uint64 BlockHash string @@ -89,6 +90,11 @@ func (h *DEvent) Flush() { // getDBLockTillSuccess get db lock until success, func (h *DEvent) getDBLockTillSuccess(db *storage.DBClient) { + startTs := time.Now() + defer func() { + xylog.Logger.Infof("get db lock success, cost:%v", time.Since(startTs)) + }() + for { ok, err := db.GetLock() if err != nil { diff --git a/devents/model.go b/devents/model.go index e6964ab..9871447 100644 --- a/devents/model.go +++ b/devents/model.go @@ -23,6 +23,8 @@ package devents import ( + "encoding/json" + "github.com/ethereum/go-ethereum/common" "github.com/shopspring/decimal" "github.com/uxuycom/indexer/model" "github.com/uxuycom/indexer/xylog" @@ -121,8 +123,9 @@ func (tc *TxResultHandler) BuildInscriptionStat(e *TxResult) map[DBAction]*model } type AddressTxEvent struct { - Address string - Amount decimal.Decimal + Address string + RelatedAddress string + Amount decimal.Decimal } func (tc *TxResultHandler) BuildAddressTxEvents(e *TxResult) []*AddressTxEvent { @@ -147,15 +150,22 @@ func (tc *TxResultHandler) BuildAddressTxEvents(e *TxResult) []*AddressTxEvent { sendTotalAmount = sendTotalAmount.Add(item.Amount) } + sendToAddr := "" + if len(e.Transfer.Receives) == 1 { + sendToAddr = e.Transfer.Receives[0].Address + } + items = append(items, &AddressTxEvent{ - Address: e.Transfer.Sender, - Amount: sendTotalAmount, + Address: e.Transfer.Sender, + RelatedAddress: sendToAddr, + Amount: sendTotalAmount.Neg(), }) for _, item := range e.Transfer.Receives { items = append(items, &AddressTxEvent{ - Address: item.Address, - Amount: item.Amount, + Address: item.Address, + RelatedAddress: e.Transfer.Sender, + Amount: item.Amount, }) } } @@ -167,15 +177,16 @@ func (tc *TxResultHandler) BuildAddressTxs(e *TxResult) (txs []*model.AddressTxs txs = make([]*model.AddressTxs, 0, len(addressTxEvents)) for _, item := range addressTxEvents { txs = append(txs, &model.AddressTxs{ - Event: tc.getEventByOperate(e.MD.Operate), - Address: item.Address, - Amount: item.Amount, - TxHash: e.Tx.Hash, - Tick: e.MD.Tick, - Protocol: e.MD.Protocol, - Operate: e.MD.Operate, - Chain: e.MD.Chain, - CreatedAt: time.Unix(int64(e.Block.Time), 0), + Event: tc.getEventByOperate(e.MD.Operate), + Address: item.Address, + RelatedAddress: item.RelatedAddress, + Amount: item.Amount, + TxHash: common.FromHex(e.Tx.Hash), + Tick: e.MD.Tick, + Protocol: e.MD.Protocol, + Operate: e.MD.Operate, + Chain: e.MD.Chain, + CreatedAt: time.Unix(int64(e.Block.Time), 0), }) } return txs @@ -275,7 +286,7 @@ func (tc *TxResultHandler) BuildBalance(e *TxResult) (txns []*model.BalanceTxn, Amount: event.Amount, Balance: event.OverallBalance, Available: event.AvailableBalance, - TxHash: e.Tx.Hash, + TxHash: common.FromHex(e.Tx.Hash), CreatedAt: time.Unix(int64(e.Block.Time), 0), }) @@ -298,15 +309,12 @@ func (tc *TxResultHandler) BuildBalance(e *TxResult) (txns []*model.BalanceTxn, func (tc *TxResultHandler) BuildTx(e *TxResult) *model.Transaction { return &model.Transaction{ Chain: e.MD.Chain, - Protocol: e.MD.Protocol, BlockHeight: e.Tx.BlockNumber.Uint64(), PositionInBlock: e.Tx.TxIndex.Uint64(), BlockTime: time.Unix(int64(e.Block.Time), 0), - TxHash: e.Tx.Hash, + TxHash: common.FromHex(e.Tx.Hash), From: e.Tx.From, To: e.Tx.To, - Op: e.MD.Operate, - Tick: e.MD.Tick, Gas: e.Tx.Gas.Int64(), GasPrice: e.Tx.GasPrice.Int64(), } @@ -350,6 +358,10 @@ func BuildDBUpdateModel(blocksEvents []*Event) (dmf *DBModelsFattened) { BalanceTxs: make([]*model.BalanceTxn, 0, len(blocksEvents)*2), } for _, blockEvent := range blocksEvents { + + data, _ := json.Marshal(blockEvent) + xylog.Logger.Debugf("BuildDBUpdateModel blockEvent = %v", string(data)) + for _, event := range blockEvent.Items { for action, item := range event.Inscriptions { if _, ok := dm.Inscriptions[action][item.SID]; ok { @@ -376,7 +388,7 @@ func BuildDBUpdateModel(blocksEvents []*Event) (dmf *DBModelsFattened) { dm.InscriptionStats[action][item.SID] = item } - txIdx := event.Tx.TxHash + txIdx := common.Bytes2Hex(event.Tx.TxHash) if _, ok := dm.Txs[txIdx]; ok { xylog.Logger.Debugf("tx[%s] exist & force update", txIdx) } @@ -407,6 +419,7 @@ func BuildDBUpdateModel(blocksEvents []*Event) (dmf *DBModelsFattened) { BlockHash: lastBlockEvent.BlockHash, BlockNumber: lastBlockEvent.BlockNum, BlockTime: time.Unix(int64(lastBlockEvent.BlockTime), 0), + ChainId: lastBlockEvent.ChainId, } dmf = &DBModelsFattened{ diff --git a/docs/openapi.json b/docs/openapi.json index 8ace315..0c779e0 100644 --- a/docs/openapi.json +++ b/docs/openapi.json @@ -9,7 +9,7 @@ }, "servers": [ { - "url": "https://api.indexs.io/v1/rpc/" + "url": "https://alpha.indexs.io/v1/rpc" } ], "paths": { diff --git a/docs/openapi_v2.json b/docs/openapi_v2.json index 28dcee9..4936ada 100644 --- a/docs/openapi_v2.json +++ b/docs/openapi_v2.json @@ -9,7 +9,7 @@ }, "servers": [ { - "url": "https://api.indexs.io/v2/rpc/" + "url": "https://alpha.indexs.io/v2/rpc" } ], "paths": { @@ -69,7 +69,16 @@ "description": "A param to include" } }, - "default": [10, 0, "", "", "", "", 3, 1] + "default": [ + 10, + 0, + "", + "", + "", + "", + 3, + 1 + ] } } } @@ -134,7 +143,15 @@ "description": "A param to include" } }, - "default": [10, 0, "0x6Ac6f9231c86ba4Ea4C40D196DA930C1d66439f5", "", "", "", 0] + "default": [ + 10, + 0, + "0x6Ac6f9231c86ba4Ea4C40D196DA930C1d66439f5", + "", + "", + "", + 0 + ] } } } @@ -143,9 +160,9 @@ } } }, - "/inds_getBalanceByAddress": { + "/inds_getBalancesByAddress": { "post": { - "operationId": "inds_getBalanceByAddress", + "operationId": "inds_getBalancesByAddress", "deprecated": false, "summary": "Get Address Balances", "description": "Get Address Balances From UXUY Indexer", @@ -172,7 +189,7 @@ "properties": { "method": { "type": "string", - "default": "inds_getBalanceByAddress", + "default": "inds_getBalancesByAddress", "description": "Method name" }, "id": { @@ -199,7 +216,15 @@ "description": "A param to include" } }, - "default": [10, 0, "0xF2f9D2575023D320475ed7875FCDCB9b52787E59", "", "", "", 1] + "default": [ + 10, + 0, + "0xF2f9D2575023D320475ed7875FCDCB9b52787E59", + "", + "", + "", + 1 + ] } } } @@ -264,7 +289,14 @@ "description": "A param to include" } }, - "default": [10, 0, "avalanche", "asc-20", "crazydog", 1] + "default": [ + 10, + 0, + "avalanche", + "asc-20", + "crazydog", + 1 + ] } } } @@ -329,7 +361,11 @@ "description": "A param to include" } }, - "default": [["avalanche"]] + "default": [ + [ + "avalanche" + ] + ] } } } @@ -394,7 +430,10 @@ "description": "A param to include" } }, - "default": ["avalanche", "0x646174613a2c7b2270223a226173632d3230222c226f70223a226d696e74222c227469636b223a22746f6d726179222c22616d74223a2231227d"] + "default": [ + "avalanche", + "0x646174613a2c7b2270223a226173632d3230222c226f70223a226d696e74222c227469636b223a22746f6d726179222c22616d74223a2231227d" + ] } } } @@ -459,7 +498,568 @@ "description": "A param to include" } }, - "default": ["avalanche", "0x2f88df8669337ec739d8414df0f3ef32bf166cb73233c965e805b7fa54eef1f2"] + "default": [ + "avalanche", + "0x2f88df8669337ec739d8414df0f3ef32bf166cb73233c965e805b7fa54eef1f2" + ] + } + } + } + } + } + } + } + }, + "/inds_getTransactions": { + "post": { + "operationId": "inds_getTransactions", + "deprecated": false, + "summary": "Get Transaction List Info", + "description": "Get Transaction Info From UXUY Indexer", + "tags": [ + "JSONRPC" + ], + "parameters": [], + "responses": { + "200": { + "description": "Successful response" + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "method", + "id", + "jsonrpc", + "params" + ], + "properties": { + "method": { + "type": "string", + "default": "inds_getTransactions", + "description": "Method name" + }, + "id": { + "type": "integer", + "default": 1, + "format": "int32", + "description": "Request ID" + }, + "jsonrpc": { + "type": "string", + "default": "2.0", + "description": "JSON-RPC Version (2.0)" + }, + "params": { + "title": "Parameters", + "type": "array", + "required": [ + "jsonParam" + ], + "properties": { + "jsonParam": { + "type": "integer", + "default": 1, + "description": "A param to include" + } + }, + "default": [ + 10, + 0, + "", + "", + "", + "", + 1 + ] + } + } + } + } + } + } + } + }, + "/inds_getInscriptions": { + "post": { + "operationId": "inds_getInscriptions", + "deprecated": false, + "summary": "Get InscriptionsStats List Info", + "description": "Get Transaction Info From UXUY Indexer", + "tags": [ + "JSONRPC" + ], + "parameters": [], + "responses": { + "200": { + "description": "Successful response" + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "method", + "id", + "jsonrpc", + "params" + ], + "properties": { + "method": { + "type": "string", + "default": "inds_getInscriptions", + "description": "Method name" + }, + "id": { + "type": "integer", + "default": 1, + "format": "int32", + "description": "Request ID" + }, + "jsonrpc": { + "type": "string", + "default": "2.0", + "description": "JSON-RPC Version (2.0)" + }, + "params": { + "title": "Parameters", + "type": "array", + "required": [ + "jsonParam" + ], + "properties": { + "jsonParam": { + "type": "integer", + "default": 1, + "description": "A param to include" + } + }, + "default": [ + 10, + 0, + "", + "", + "", + "", + 3 + ] + } + } + } + } + } + } + } + }, + "/inds_getInscriptionTxOperate": { + "post": { + "operationId": "inds_getInscriptionTxOperate", + "deprecated": false, + "summary": "Get InscriptionTxOperate", + "description": "Get InscriptionTxOperate From UXUY Indexer", + "tags": [ + "JSONRPC" + ], + "parameters": [], + "responses": { + "200": { + "description": "Successful response" + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "method", + "id", + "jsonrpc", + "params" + ], + "properties": { + "method": { + "type": "string", + "default": "inds_getInscriptionTxOperate", + "description": "Method name" + }, + "id": { + "type": "integer", + "default": 1, + "format": "int32", + "description": "Request ID" + }, + "jsonrpc": { + "type": "string", + "default": "2.0", + "description": "JSON-RPC Version (2.0)" + }, + "params": { + "title": "Parameters", + "type": "array", + "required": [ + "jsonParam" + ], + "properties": { + "jsonParam": { + "type": "integer", + "default": 1, + "description": "A param to include" + } + }, + "default": [ + "avalanche", + "0x646174613a2c7b2270223a226173632d3230222c226f70223a226d696e74222c227469636b223a22746f6d726179222c22616d74223a2231227d" + ] + } + } + } + } + } + } + } + }, + "/index_getInscriptionByTick": { + "post": { + "operationId": "index_getInscriptionByTick", + "deprecated": false, + "summary": "Get Inscription by Tick", + "description": "Get Inscription Tick From UXUY Indexer", + "tags": [ + "JSONRPC" + ], + "parameters": [], + "responses": { + "200": { + "description": "Successful response" + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "method", + "id", + "jsonrpc", + "params" + ], + "properties": { + "method": { + "type": "string", + "default": "index_getInscriptionByTick", + "description": "Method name" + }, + "id": { + "type": "integer", + "default": 1, + "format": "int32", + "description": "Request ID" + }, + "jsonrpc": { + "type": "string", + "default": "2.0", + "description": "JSON-RPC Version (2.0)" + }, + "params": { + "title": "Parameters", + "type": "array", + "required": [ + "jsonParam" + ], + "properties": { + "jsonParam": { + "type": "integer", + "default": 1, + "description": "A param to include" + } + }, + "default": [ + "avalanche", + "asc-20", + "crazydog" + ] + } + } + } + } + } + } + } + }, + "/inds_getAddressBalance": { + "post": { + "operationId": "inds_getAddressBalance", + "deprecated": false, + "summary": "Get Address Balance", + "description": "Get Address Balance From UXUY Indexer", + "tags": [ + "JSONRPC" + ], + "parameters": [], + "responses": { + "200": { + "description": "Successful response" + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "method", + "id", + "jsonrpc", + "params" + ], + "properties": { + "method": { + "type": "string", + "default": "inds_getAddressBalance", + "description": "Method name" + }, + "id": { + "type": "integer", + "default": 1, + "format": "int32", + "description": "Request ID" + }, + "jsonrpc": { + "type": "string", + "default": "2.0", + "description": "JSON-RPC Version (2.0)" + }, + "params": { + "title": "Parameters", + "type": "array", + "required": [ + "jsonParam" + ], + "properties": { + "jsonParam": { + "type": "integer", + "default": 1, + "description": "A param to include" + } + }, + "default": [ + "0xF2f9D2575023D320475ed7875FCDCB9b52787E59", + "avalanche", + "asc-20", + "crazydog" + ] + } + } + } + } + } + } + } + }, + "/inds_getTickBriefs": { + "post": { + "operationId": "inds_getTickBriefs", + "deprecated": false, + "summary": "Get tick briefs", + "description": "Get Tick Briefs From UXUY Indexer", + "tags": [ + "JSONRPC" + ], + "parameters": [], + "responses": { + "200": { + "description": "Successful response" + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "method", + "id", + "jsonrpc", + "params" + ], + "properties": { + "method": { + "type": "string", + "default": "inds_getTickBriefs", + "description": "Method name" + }, + "id": { + "type": "integer", + "default": 1, + "format": "int32", + "description": "Request ID" + }, + "jsonrpc": { + "type": "string", + "default": "2.0", + "description": "JSON-RPC Version (2.0)" + }, + "params": { + "title": "Parameters", + "type": "array", + "required": [ + "jsonParam" + ], + "properties": { + "jsonParam": { + "type": "integer", + "default": 1, + "description": "A param to include" + } + }, + "default": [ + [ + { + "chain": "avalanche", + "deploy_hash": "0x7ffc56b2bf20f4f3474c1fd503fc3f1fb9066c8b0665d6da11185cac892108a5" + } + ] + ] + } + } + } + } + } + } + } + }, + "/inds_search": { + "post": { + "operationId": "inds_search", + "deprecated": false, + "summary": "Search address or transaction or tick", + "description": "Search address or transaction or tick", + "tags": [ + "JSONRPC" + ], + "parameters": [], + "responses": { + "200": { + "description": "Successful response" + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "method", + "id", + "jsonrpc", + "params" + ], + "properties": { + "method": { + "type": "string", + "default": "inds_search", + "description": "Method name" + }, + "id": { + "type": "integer", + "default": 1, + "format": "int32", + "description": "Request ID" + }, + "jsonrpc": { + "type": "string", + "default": "2.0", + "description": "JSON-RPC Version (2.0)" + }, + "params": { + "title": "Parameters", + "type": "array", + "required": [ + "jsonParam" + ], + "properties": { + "jsonParam": { + "type": "string", + "default": "", + "description": "A param to include" + } + }, + "default": [ + "0x4273173187f1108007b1C1ABE5301eFa03f7fc8A", + "" + ] + } + } + } + } + } + } + } + }, + "/inds_getAllChains": { + "post": { + "operationId": "inds_getAllChains", + "deprecated": false, + "summary": "Get All Chain", + "description": "Get All Chain", + "tags": [ + "JSONRPC" + ], + "parameters": [], + "responses": { + "200": { + "description": "Successful response" + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "method", + "id", + "jsonrpc", + "params" + ], + "properties": { + "method": { + "type": "string", + "default": "inds_getAllChains", + "description": "Method name" + }, + "id": { + "type": "integer", + "default": 1, + "format": "int32", + "description": "Request ID" + }, + "jsonrpc": { + "type": "string", + "default": "2.0", + "description": "JSON-RPC Version (2.0)" + }, + "params": { + "title": "Parameters", + "type": "array", + "required": [ + "jsonParam" + ], + "properties": { + "jsonParam": { + "type": "string", + "default": "", + "description": "A param to include" + } + }, + "default": [[]] } } } diff --git a/example/config_avalanche.json b/example/config_avalanche.json new file mode 100644 index 0000000..1fe1758 --- /dev/null +++ b/example/config_avalanche.json @@ -0,0 +1,42 @@ +{ + "scan": { + "start_block": 39205395, + "block_batch_workers": 1, + "tx_batch_workers": 1, + "delayed_block_num": 10 + }, + "database": { + "type": "mysql", + "dsn": "root:1234567890@tcp(127.0.0.1:3306)/tap_indexer?charset=utf8mb4&parseTime=True&loc=Local&collation=utf8mb4_general_ci", + "enable_log": false + }, + "chain": { + "chain_name": "avalanche", + "rpc": "https://1rpc.io/avax/c", + "username": "", + "password": "" + }, + "log_level": "info", + "notls": true, + "rpclisten": [ + ":6583" + ], + "rpcmaxclients": 10000, + "filters": { + "event_topics": [ + "0xe2750d6418e3719830794d3db788aa72febcd657bcd18ed8f1facdbf61a69a9a", + "0x3efe873bf4d1c1061b9980e7aed9b564e024844522ec8c80aec160809948ef77", + "0x8cdf9e10a7b20e7a9c4e778fc3eb28f2766e438a9856a62eac39fbd2be98cbc2" + ], + "whitelist": { + "ticks": [ + "cczzc" + ] + } + }, + + "profile": { + "enabled": false, + "listen": ":6060" + } +} \ No newline at end of file diff --git a/example/config_eth.json b/example/config_eth.json new file mode 100644 index 0000000..100f5d4 --- /dev/null +++ b/example/config_eth.json @@ -0,0 +1,29 @@ +{ + "scan": { + "start_block": 39205395, + "block_batch_workers": 1, + "tx_batch_workers": 1, + "delayed_block_num": 10 + }, + "database": { + "type": "mysql", + "dsn": "root:1234567890@tcp(127.0.0.1:3306)/tap_indexer?charset=utf8mb4&parseTime=True&loc=Local&collation=utf8mb4_general_ci", + "enable_log": false + }, + "chain": { + "chain_name": "eth", + "rpc": "https://1rpc.io/eth", + "username": "", + "password": "" + }, + "log_level": "info", + "notls": true, + "rpclisten": [ + ":6583" + ], + "rpcmaxclients": 10000, + "profile": { + "enabled": false, + "listen": ":6060" + } +} \ No newline at end of file diff --git a/explorer/indexer.go b/explorer/indexer.go index 8722939..59026cc 100644 --- a/explorer/indexer.go +++ b/explorer/indexer.go @@ -23,6 +23,7 @@ package explorer import ( + "encoding/json" "errors" "fmt" "github.com/alitto/pond" @@ -159,6 +160,9 @@ func (e *Explorer) handleTxs(block *xycommon.RpcBlock, txs []*xycommon.RpcTransa xylog.Logger.Infof("handle txs, parse & async sink cost[%v], txs[%d]", time.Since(startTs), len(txs)) }() + b, _ := json.Marshal(block) + xylog.Logger.Debugf("handleTxs rpc block =%v", string(b)) + blockTxResults := make([]*devents.DBModelEvent, 0, len(txs)) for _, tx := range txs { pt, md := protocol.GetProtocol(e.config, tx) @@ -299,6 +303,7 @@ func (e *Explorer) writeDBAsync(block *xycommon.RpcBlock, txResults []*devents.D //write db async event := &devents.Event{ Chain: e.config.Chain.ChainName, + ChainId: txResults[0].Tx.ChainId, BlockNum: block.Number.Uint64(), BlockTime: block.Time, BlockHash: block.Hash, diff --git a/go.mod b/go.mod index 3d215cc..b893f79 100644 --- a/go.mod +++ b/go.mod @@ -6,10 +6,11 @@ require ( github.com/alitto/pond v1.8.3 github.com/btcsuite/btcd v0.23.5-0.20231215221805-96c9fd8078fd github.com/ethereum/go-ethereum v1.13.8 - github.com/google/uuid v1.3.0 + github.com/google/uuid v1.4.0 github.com/mattn/go-sqlite3 v1.14.19 github.com/shopspring/decimal v1.3.1 github.com/sirupsen/logrus v1.9.2 + github.com/spf13/viper v1.18.2 github.com/stretchr/testify v1.8.4 github.com/wealdtech/go-merkletree v1.0.0 golang.org/x/sync v0.5.0 @@ -33,36 +34,53 @@ require ( github.com/consensys/bavard v0.1.13 // indirect github.com/consensys/gnark-crypto v0.12.1 // indirect github.com/crate-crypto/go-kzg-4844 v0.7.0 // indirect - github.com/davecgh/go-spew v1.1.1 // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/deckarep/golang-set/v2 v2.1.0 // indirect github.com/decred/dcrd/crypto/blake256 v1.0.0 // indirect github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0 // indirect github.com/ethereum/c-kzg-4844 v0.4.0 // indirect + github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/getsentry/sentry-go v0.18.0 // indirect github.com/go-ole/go-ole v1.2.6 // indirect github.com/go-sql-driver/mysql v1.7.1 // indirect github.com/gorilla/websocket v1.5.0 // indirect + github.com/hashicorp/hcl v1.0.0 // indirect github.com/holiman/uint256 v1.2.4 // indirect github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/now v1.1.5 // indirect + github.com/magiconair/properties v1.8.7 // indirect + github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/mmcloughlin/addchain v0.4.0 // indirect - github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/pelletier/go-toml/v2 v2.1.0 // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/prometheus/client_golang v1.14.0 // indirect github.com/prometheus/common v0.39.0 // indirect github.com/prometheus/procfs v0.9.0 // indirect github.com/rogpeppe/go-internal v1.10.0 // indirect + github.com/sagikazarmark/locafero v0.4.0 // indirect + github.com/sagikazarmark/slog-shim v0.1.0 // indirect github.com/shirou/gopsutil v3.21.11+incompatible // indirect + github.com/sourcegraph/conc v0.3.0 // indirect + github.com/spf13/afero v1.11.0 // indirect + github.com/spf13/cast v1.6.0 // indirect + github.com/spf13/pflag v1.0.5 // indirect + github.com/subosito/gotenv v1.6.0 // indirect github.com/supranational/blst v0.3.11 // indirect github.com/syndtr/goleveldb v1.0.1-0.20220614013038-64ee5596c38a // indirect github.com/tklauser/go-sysconf v0.3.12 // indirect github.com/tklauser/numcpus v0.6.1 // indirect github.com/yusufpapurcu/wmi v1.2.2 // indirect + go.uber.org/atomic v1.9.0 // indirect + go.uber.org/multierr v1.9.0 // indirect golang.org/x/crypto v0.17.0 // indirect golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa // indirect golang.org/x/mod v0.14.0 // indirect golang.org/x/sys v0.15.0 // indirect + golang.org/x/text v0.14.0 // indirect golang.org/x/tools v0.15.0 // indirect google.golang.org/protobuf v1.31.0 // indirect + gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect rsc.io/tmplfunc v0.0.3 // indirect + github.com/gookit/goutil v0.6.15 // indirect ) diff --git a/go.sum b/go.sum index 4c30750..fc6d071 100644 --- a/go.sum +++ b/go.sum @@ -12,11 +12,14 @@ github.com/Shopify/goreferrer v0.0.0-20181106222321-ec9c9a553398/go.mod h1:a1uqR github.com/VictoriaMetrics/fastcache v1.12.1 h1:i0mICQuojGDL3KblA7wUNlY5lOK6a4bwt3uRKnkZU40= github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY= +github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= github.com/alitto/pond v1.8.3 h1:ydIqygCLVPqIX/USe5EaV/aSRXTRXDEI9JwuDdu+/xs= github.com/alitto/pond v1.8.3/go.mod h1:CmvIIGd5jKLasGI3D87qDkQxjzChdKMmnXMg3fG6M6Q= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/aymerick/raymond v2.0.3-0.20180322193309-b565731e1464+incompatible/go.mod h1:osfaiScAUVup+UC9Nfq76eWqDhXlp+4UYaA8uhTBO6g= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bits-and-blooms/bitset v1.10.0 h1:ePXTeiPEazB5+opbv5fr8umg2R/1NlzgDsyepwsSr88= github.com/bits-and-blooms/bitset v1.10.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ= @@ -61,6 +64,7 @@ github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b/go.mod h1:Vz9D github.com/cockroachdb/pebble v0.0.0-20230928194634-aa077af62593 h1:aPEJyR4rPBvDmeyi+l/FS/VtA00IWvjeFvjen1m1l1A= github.com/cockroachdb/redact v1.1.3 h1:AKZds10rFSIj7qADf0g46UixK8NNLwWTNdCIGS5wfSQ= github.com/cockroachdb/redact v1.1.3/go.mod h1:BVNblN9mBWFyMyqK1k3AAiSxhvhfK2oOZZ2lK+dpvRg= +github.com/cockroachdb/sentry-go v0.6.1-cockroachdb.2/go.mod h1:8BT+cPK6xvFOcRlk0R8eg+OTkcqI6baNH4xAkpiYVvQ= github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06 h1:zuQyyAKVxetITBuuhv3BI9cMrmStnpT18zmgmTxunpo= github.com/codegangsta/inject v0.0.0-20150114235600-33e0aa1cb7c0/go.mod h1:4Zcjuz89kmFXt9morQgcfYZAYZ5n8WHjt81YYWIwtTM= github.com/consensys/bavard v0.1.13 h1:oLhMLOFGTLdlda/kma4VOJazblc7IM5y5QPd2A/YjhQ= @@ -79,6 +83,8 @@ github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/deckarep/golang-set/v2 v2.1.0 h1:g47V4Or+DUdzbs8FxCCmgb6VYd+ptPAngjM6dtGktsI= github.com/deckarep/golang-set/v2 v2.1.0/go.mod h1:VAky9rY/yGXJOLEDv3OMci+7wtDpOF4IN+y82NBOac4= github.com/decred/dcrd/crypto/blake256 v1.0.0 h1:/8DMNYp9SGi5f0w7uCm6d6M4OU2rGFK09Y2A4Xv7EE0= @@ -105,6 +111,8 @@ github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= +github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= +github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= github.com/gavv/httpexpect v2.0.0+incompatible/go.mod h1:x+9tiU1YnrOvnB725RkpoLv1M62hOWzwo5OXotisrKc= github.com/gballet/go-verkle v0.1.1-0.20231031103413-a67434b50f46 h1:BAIP2GihuqhwdILrV+7GJel5lyPV3u1+PgzrWLc0TkE= github.com/getsentry/sentry-go v0.12.0/go.mod h1:NSap0JBYWzHND8oMbyi0+XZhUalc1TBdRL1M71JZW2c= @@ -114,6 +122,7 @@ github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3/go.mod h1:VJ0WA2NB github.com/gin-gonic/gin v1.4.0/go.mod h1:OW2EZn3DO8Ln9oIKOvM++LBO+5UPHJJDH72/q/3rZdM= github.com/go-check/check v0.0.0-20180628173108-788fd7840127/go.mod h1:9ES+weclKsC9YodN5RgxqK/VD9HM9JsCSh7rNhMZE98= github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= +github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= github.com/go-martini/martini v0.0.0-20170121215854-22fa46961aab/go.mod h1:/P9AEU963A2AYjv4d1V5eVL1CQbEJq6aCNHDDjibzu8= github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= @@ -164,11 +173,15 @@ github.com/google/subcommands v1.2.0/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3 github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4= +github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gookit/goutil v0.6.15/go.mod h1:qdKdYEHQdEtyH+4fNdQNZfJHhI0jUZzHxQVAV3DaMDY= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/holiman/uint256 v1.2.4 h1:jUc4Nk8fm9jZabQuqr2JzednajVmBpC+oiTiXZJEApU= github.com/holiman/uint256 v1.2.4/go.mod h1:EOMSn4q6Nyt9P6efbI3bueV4e1b3dGlUCXeiRV4ng7E= @@ -188,10 +201,13 @@ github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= +github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88/go.mod h1:3w7q1U84EfirKl04SVQ/s7nPm1ZPhiXd34z40TNz36k= github.com/kataras/golog v0.0.10/go.mod h1:yJ8YKCmyL+nWjERB90Qwn+bdyBZsaQwU3bTVFgkFIp8= github.com/kataras/iris/v12 v12.1.8/go.mod h1:LMYy4VlP67TQ3Zgriz8RE2h2kMZV2SgMYbq3UhfoFmE= @@ -204,6 +220,7 @@ github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6 github.com/klauspost/compress v1.8.2/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= github.com/klauspost/compress v1.9.7/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= github.com/klauspost/compress v1.15.15 h1:EF27CXIuDsYJ6mmvtBRlEuB2UVOqHG1tAXgZ7yIO+lw= +github.com/klauspost/compress v1.17.0 h1:Rnbp4K9EjcDuVuHtd0dgA4qNuv9yKDYKK1ulpJwgrqM= github.com/klauspost/cpuid v1.2.1/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= @@ -217,6 +234,8 @@ github.com/labstack/echo/v4 v4.5.0/go.mod h1:czIriw4a0C1dFun+ObrXp7ok03xON0N1awS github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k= github.com/leanovate/gopter v0.2.9 h1:fQjYxZaynp97ozCzfOyOuAGOU4aU/z37zf/tOujFk7c= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= +github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= @@ -234,6 +253,8 @@ github.com/mediocregopher/radix/v3 v3.4.2/go.mod h1:8FL3F6UQRXHXIBSPUs5h0RybMF8i github.com/microcosm-cc/bluemonday v1.0.2/go.mod h1:iVP4YcDBq+n/5fb23BhYFvIMq/leAFZyRl6bYmGDlGc= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mmcloughlin/addchain v0.4.0 h1:SobOdjm2xLj1KkXN5/n0xTIWyZA2+s99UCY1iPfkHRY= github.com/mmcloughlin/addchain v0.4.0/go.mod h1:A86O+tHqZLMNO4w6ZZ4FlVQEadcoqkyU72HC5wJ4RlU= github.com/mmcloughlin/profile v0.1.1/go.mod h1:IhHD7q1ooxgwTgjxQYkACGA77oFTDdFVejUS1/tS/qU= @@ -241,7 +262,9 @@ github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/moul/http2curl v1.0.0/go.mod h1:8UbvGypXm98wA/IqH45anm5Y2Z6ep6O31QGOAZ3H0fQ= +github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg= github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzEE/Zbp4w= github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= @@ -263,7 +286,10 @@ github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7J github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro= +github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4= +github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -271,6 +297,8 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_golang v1.14.0 h1:nJdhIvne2eSX/XRAFV9PcvFFRbrjbcTUj0VP62TMhnw= github.com/prometheus/client_golang v1.14.0/go.mod h1:8vpkKitgIVNcqrRBWh1C4TIUQgYNtG/XQE4E/Zae36Y= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= @@ -286,6 +314,10 @@ github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjR github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ= +github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4= +github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= +github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= github.com/schollz/closestmatch v2.1.0+incompatible/go.mod h1:RtP1ddjLong6gTkbtmuhtR2uUrrJOpYzYRvbcPAid+g= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= github.com/shirou/gopsutil v3.21.11+incompatible h1:+1+c1VGhc88SSonWP6foOcLhvnKlUeu/erjjvaPEYiI= @@ -297,21 +329,37 @@ github.com/sirupsen/logrus v1.9.2 h1:oxx1eChJGI6Uks2ZC4W1zpLlVgqB8ner4EuQwV4Ik1Y github.com/sirupsen/logrus v1.9.2/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= +github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= +github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= +github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0= +github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= +github.com/spf13/viper v1.18.2 h1:LUXCnvUvSM6FXAsj6nnfc8Q2tp1dIgUfY9Kc8GsSOiQ= +github.com/spf13/viper v1.18.2/go.mod h1:EKmWIqdnk5lOcmR72yw6hS+8OPYcwD0jteitLMVB+yk= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= +github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= github.com/supranational/blst v0.3.11 h1:LyU6FolezeWAhvQk0k6O/d49jqgO52MSDDfYgbeoEm4= github.com/supranational/blst v0.3.11/go.mod h1:jZJtfjgudtNl4en1tzwPIV3KjUnQUvG3/j+w+fVonLw= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc= @@ -346,6 +394,10 @@ github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9dec github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yusufpapurcu/wmi v1.2.2 h1:KBNDSne4vP5mbSWnJbO+51IMOXJB67QiYCSBrubbPRg= github.com/yusufpapurcu/wmi v1.2.2/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= +go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= +go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI= +go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ= golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= @@ -450,6 +502,7 @@ golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181221001348-537d06c36207/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -498,6 +551,7 @@ google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp0 google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -508,6 +562,8 @@ gopkg.in/go-playground/assert.v1 v1.2.1 h1:xoYuJVE7KT85PYWrN730RguIQO0ePzVRfFMXa gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE= gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y= gopkg.in/ini.v1 v1.51.1/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= +gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/jsonrpc/cmds.go b/jsonrpc/cmds.go index e4373ec..1fe44e6 100644 --- a/jsonrpc/cmds.go +++ b/jsonrpc/cmds.go @@ -7,13 +7,17 @@ package jsonrpc -import "github.com/uxuycom/indexer/model" +import ( + "github.com/shopspring/decimal" + "github.com/uxuycom/indexer/model" + "time" +) // EmptyCmd defines the empty JSON-RPC command. type EmptyCmd struct{} -// FindAllInscriptionsCmd defines the inscription JSON-RPC command. -type FindAllInscriptionsCmd struct { +// IndsGetAllInscriptionsCmd defines the inscription JSON-RPC command. +type IndsGetAllInscriptionsCmd struct { Limit int `json:"limit"` Offset int `json:"offset"` Chain string `json:"chain"` @@ -23,6 +27,10 @@ type FindAllInscriptionsCmd struct { Sort int `json:"sort"` //SortMode int `json:"sort_mode"` } +type IndsSearchCmd struct { + Keyword string `json:"keyword"` + Chain string `json:"chain"` +} type IndsGetTicksCmd struct { Limit int `json:"limit"` @@ -35,13 +43,54 @@ type IndsGetTicksCmd struct { SortMode int `json:"sort_mode"` } -type FindAllInscriptionsResponse struct { +type IndsGetTickCmd struct { + Chain string + Protocol string + Tick string + DeployHash string +} + +type IndsGetTransactionCmd struct { + Limit int `json:"limit"` + Offset int `json:"offset"` + Address string `json:"address"` + Chain string `json:"chain"` + Protocol string `json:"protocol"` + Tick string `json:"tick"` + SortMode int `json:"sort_mode"` +} + +type IndsGetInscriptionsCmd struct { + Limit int `json:"limit"` + Offset int `json:"offset"` + Chain string `json:"chain"` + Protocol string `json:"protocol"` + Tick string `json:"tick"` + DeployBy string `json:"deploy_by"` + Sort int `json:"sort"` + //SortMode int `json:"sort_mode"` +} + +type IndsGetAllInscriptionsResponse struct { Inscriptions interface{} `json:"inscriptions"` Total int64 `json:"total"` Limit int `json:"limit"` Offset int `json:"offset"` } +type CommonResponse struct { + Data interface{} `json:"data"` + Total int64 `json:"total"` + Limit int `json:"limit"` + Offset int `json:"offset"` + Code int `json:"code"` + Msg int `json:"msg"` +} +type SearchResult struct { + Type string `json:"type"` + Data interface{} `json:"data"` +} + type InscriptionInfo struct { Chain string `json:"chain"` Protocol string `json:"protocol"` @@ -56,10 +105,14 @@ type InscriptionInfo struct { CreatedAt uint32 `json:"created_at"` UpdatedAt uint32 `json:"updated_at"` Decimals int8 `json:"decimals"` + Minted string `json:"minted"` + Holders uint64 `json:"holders"` + TxCnt uint64 `json:"tx_cnt"` + Progress string `json:"progress"` } -// FindInscriptionTickCmd defines the inscription JSON-RPC command. -type FindInscriptionTickCmd struct { +// IndsGetInscriptionTickCmd defines the inscription JSON-RPC command. +type IndsGetInscriptionTickCmd struct { Chain string Protocol string Tick string @@ -69,14 +122,15 @@ type FindInscriptionTickResponse struct { Tick interface{} `json:"tick"` } -// FindUserTransactionsCmd defines the inscription JSON-RPC command. -type FindUserTransactionsCmd struct { +// IndsGetUserTransactionsCmd defines the inscription JSON-RPC command. +type IndsGetUserTransactionsCmd struct { Limit int Offset int Address string Chain string Protocol string Tick string + Key string Event int8 } @@ -111,6 +165,7 @@ type FindUserBalancesCmd struct { Chain string Protocol string Tick string + Key string } type IndsGetBalanceByAddressCmd struct { @@ -120,6 +175,7 @@ type IndsGetBalanceByAddressCmd struct { Chain string Protocol string Tick string + Key string Sort int } @@ -140,6 +196,16 @@ type BalanceInfo struct { TransferType int8 `json:"transfer_type"` } +type TickHolder struct { + Chain string `json:"chain"` + Protocol string `json:"protocol"` + Tick string `json:"tick"` + DeployHash string `json:"deploy_hash"` + Address string `json:"address"` + Balance string `json:"balance"` + TotalSupply string `json:"total_supply"` +} + type BalanceBrief struct { Tick string `json:"tick"` Balance string `json:"balance"` @@ -237,20 +303,46 @@ type TransactionInfo struct { From string `json:"from"` To string `json:"to"` Amount string `json:"amount"` + Op string `json:"op"` +} + +type TransactionResponse struct { + ID uint64 `json:"id"` + Chain string `json:"chain"` // chain name + Protocol string `json:"protocol"` // protocol name + BlockHeight uint64 `json:"block_height"` // block height + PositionInBlock uint64 `json:"position_in_block"` // Position in Block + BlockTime time.Time `json:"block_time"` // block time + TxHash string `json:"tx_hash"` // tx hash + From string `json:"from"` // from address + To string `json:"to"` // to address + Op string `json:"op"` // op code + Tick string `json:"tick"` // inscription code + Amount decimal.Decimal `json:"amt"` // balance + Gas int64 `json:"gas" ` // gas + GasPrice int64 `json:"gas_price"` // gas price + Status int8 `json:"status"` // tx status + CreatedAt time.Time `json:"created_at" ` + UpdatedAt time.Time `json:"updated_at"` } type GetTxByHashResponse struct { IsInscription bool `json:"is_inscription"` Transaction *TransactionInfo `json:"transaction,omitempty"` } +type GetAllChainCmd struct { + Chains []string +} +type GetChainStatCmd struct { +} func init() { // No special flags for commands in this file. flags := UsageFlag(0) - MustRegisterCmd("inscription.All", (*FindAllInscriptionsCmd)(nil), flags) - MustRegisterCmd("inscription.Tick", (*FindInscriptionTickCmd)(nil), flags) - MustRegisterCmd("address.Transactions", (*FindUserTransactionsCmd)(nil), flags) + MustRegisterCmd("inscription.All", (*IndsGetAllInscriptionsCmd)(nil), flags) + MustRegisterCmd("inscription.Tick", (*IndsGetInscriptionTickCmd)(nil), flags) + MustRegisterCmd("address.Transactions", (*IndsGetUserTransactionsCmd)(nil), flags) MustRegisterCmd("address.Balances", (*FindUserBalancesCmd)(nil), flags) MustRegisterCmd("address.Balance", (*FindUserBalanceCmd)(nil), flags) MustRegisterCmd("tick.Holders", (*FindTickHoldersCmd)(nil), flags) @@ -259,12 +351,22 @@ func init() { MustRegisterCmd("transaction.Info", (*GetTxByHashCmd)(nil), flags) MustRegisterCmd("tick.GetBriefs", (*GetTickBriefsCmd)(nil), flags) - //v2 + // v2 MustRegisterCmd("inds_getTicks", (*IndsGetTicksCmd)(nil), flags) - MustRegisterCmd("inds_getTransactionByAddress", (*FindUserTransactionsCmd)(nil), flags) + MustRegisterCmd("inds_getTick", (*IndsGetTickCmd)(nil), flags) + MustRegisterCmd("inds_getTransactionByAddress", (*IndsGetUserTransactionsCmd)(nil), flags) MustRegisterCmd("inds_getBalanceByAddress", (*IndsGetBalanceByAddressCmd)(nil), flags) MustRegisterCmd("inds_getHoldersByTick", (*IndsGetHoldersByTickCmd)(nil), flags) MustRegisterCmd("inds_getLastBlockNumberIndexed", (*LastBlockNumberCmd)(nil), flags) MustRegisterCmd("inds_getTickByCallData", (*TxOperateCmd)(nil), flags) MustRegisterCmd("inds_getTransactionByHash", (*GetTxByHashCmd)(nil), flags) + MustRegisterCmd("inds_getTransactions", (*IndsGetTransactionCmd)(nil), flags) + MustRegisterCmd("inds_getInscriptions", (*IndsGetInscriptionsCmd)(nil), flags) + MustRegisterCmd("inds_getInscriptionTxOperate", (*TxOperateCmd)(nil), flags) + MustRegisterCmd("inds_getAllChains", (*GetAllChainCmd)(nil), flags) + MustRegisterCmd("inds_search", (*IndsSearchCmd)(nil), flags) + MustRegisterCmd("inds_getAddressBalance", (*FindUserBalanceCmd)(nil), flags) + MustRegisterCmd("inds_getTickBriefs", (*GetTickBriefsCmd)(nil), flags) + MustRegisterCmd("index_getInscriptionByTick", (*IndsGetInscriptionTickCmd)(nil), flags) + } diff --git a/jsonrpc/data_handler.go b/jsonrpc/data_handler.go deleted file mode 100644 index 1d52d01..0000000 --- a/jsonrpc/data_handler.go +++ /dev/null @@ -1,146 +0,0 @@ -package jsonrpc - -import ( - "fmt" - "github.com/shopspring/decimal" - "github.com/uxuycom/indexer/model" - "strings" -) - -func findAddressBalances(s *RpcServer, limit, offset int, address, chain, protocol, tick string, sort int) (interface{}, error) { - protocol = strings.ToLower(protocol) - tick = strings.ToLower(tick) - cacheKey := fmt.Sprintf("addr_balances_%d_%d_%s_%s_%s_%s_%d", limit, offset, address, chain, protocol, tick, sort) - if ins, ok := s.cacheStore.Get(cacheKey); ok { - if allIns, ok := ins.(*FindUserBalancesResponse); ok { - return allIns, nil - } - } - - balances, total, err := s.dbc.GetAddressInscriptions(limit, offset, address, chain, protocol, tick, sort) - if err != nil { - return ErrRPCInternal, err - } - - list := make([]*BalanceInfo, 0, len(balances)) - for _, b := range balances { - balance := &BalanceInfo{ - Chain: b.Chain, - Protocol: b.Protocol, - Tick: b.Tick, - Address: b.Address, - Balance: b.Balance.String(), - DeployHash: b.DeployHash, - TransferType: b.TransferType, - } - list = append(list, balance) - } - - resp := &FindUserBalancesResponse{ - Inscriptions: list, - Total: total, - Limit: limit, - Offset: offset, - } - s.cacheStore.Set(cacheKey, resp) - return resp, nil -} - -func findInsciptions(s *RpcServer, limit, offset int, chain, protocol, tick, deployBy string, sort, sortMode int) (interface{}, error) { - protocol = strings.ToLower(protocol) - tick = strings.ToLower(tick) - cacheKey := fmt.Sprintf("all_ins_%d_%d_%s_%s_%s_%s_%d_%d", limit, offset, chain, protocol, tick, deployBy, sort, sortMode) - if ins, ok := s.cacheStore.Get(cacheKey); ok { - if allIns, ok := ins.(*FindAllInscriptionsResponse); ok { - return allIns, nil - } - } - inscriptions, total, err := s.dbc.GetInscriptions(limit, offset, chain, protocol, tick, deployBy, sort, sortMode) - if err != nil { - return ErrRPCInternal, err - } - - result := make([]*model.InscriptionBrief, 0, len(inscriptions)) - - for _, ins := range inscriptions { - brief := &model.InscriptionBrief{ - Chain: ins.Chain, - Protocol: ins.Protocol, - Tick: ins.Name, - DeployBy: ins.DeployBy, - DeployHash: ins.DeployHash, - TotalSupply: ins.TotalSupply.String(), - Holders: ins.Holders, - Minted: ins.Minted.String(), - LimitPerMint: ins.LimitPerMint.String(), - TransferType: ins.TransferType, - Status: model.MintStatusProcessing, - TxCnt: ins.TxCnt, - CreatedAt: uint32(ins.CreatedAt.Unix()), - } - - minted := ins.Minted - totalSupply := ins.TotalSupply - - if totalSupply != decimal.Zero && minted != decimal.Zero { - percentage, _ := minted.Div(totalSupply).Float64() - if percentage >= 1 { - percentage = 1 - } - brief.MintedPercent = fmt.Sprintf("%.4f", percentage) - } - - if ins.Minted.Cmp(ins.TotalSupply) >= 0 { - brief.Status = model.MintStatusAllMinted - } - - result = append(result, brief) - } - - resp := &FindAllInscriptionsResponse{ - Inscriptions: result, - Total: total, - Limit: limit, - Offset: offset, - } - s.cacheStore.Set(cacheKey, resp) - return resp, nil -} - -func findTickHolders(s *RpcServer, limit int, offset int, chain, protocol, tick string, sortMode int) (interface{}, error) { - protocol = strings.ToLower(protocol) - tick = strings.ToLower(tick) - cacheKey := fmt.Sprintf("all_ins_%d_%d_%s_%s_%s_%d", limit, offset, chain, protocol, tick, sortMode) - if ins, ok := s.cacheStore.Get(cacheKey); ok { - if allIns, ok := ins.(*FindTickHoldersResponse); ok { - return allIns, nil - } - } - - holders, total, err := s.dbc.GetHoldersByTick(limit, offset, chain, protocol, tick, sortMode) - if err != nil { - return ErrRPCInternal, err - } - - list := make([]*BalanceInfo, 0, len(holders)) - for _, b := range holders { - balance := &BalanceInfo{ - Chain: b.Chain, - Protocol: b.Protocol, - Tick: b.Tick, - Address: b.Address, - Balance: b.Balance.String(), - } - list = append(list, balance) - } - - resp := &FindTickHoldersResponse{ - Holders: list, - Total: total, - Limit: limit, - Offset: offset, - } - - s.cacheStore.Set(cacheKey, resp) - return resp, nil -} diff --git a/jsonrpc/handler_v2.go b/jsonrpc/handler_v2.go deleted file mode 100644 index db978c4..0000000 --- a/jsonrpc/handler_v2.go +++ /dev/null @@ -1,48 +0,0 @@ -package jsonrpc - -import ( - "errors" - "github.com/uxuycom/indexer/xylog" -) - -var rpcHandlersBeforeInitV2 = map[string]commandHandler{ - "inds_getTicks": indsGetTicks, //handleFindAllInscriptions, - "inds_getTransactionByAddress": handleFindAddressTransactions, - "inds_getBalanceByAddress": indsGetBalanceByAddress, - "inds_getHoldersByTick": indsGetHoldersByTick, - "inds_getLastBlockNumberIndexed": handleGetLastBlockNumber, - "inds_getTickByCallData": handleGetTxOperate, - "inds_getTransactionByHash": handleGetTxByHash, - //"inscription.Tick": handleFindInscriptionTick, - //"address.Balance": handleFindAddressBalance, -} - -func indsGetTicks(s *RpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) { - req, ok := cmd.(*IndsGetTicksCmd) - if !ok { - return ErrRPCInvalidParams, errors.New("invalid params") - } - - xylog.Logger.Infof("find all Inscriptions cmd params:%v", req) - return findInsciptions(s, req.Limit, req.Offset, req.Chain, req.Protocol, req.Tick, req.DeployBy, req.Sort, req.SortMode) -} - -func indsGetBalanceByAddress(s *RpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) { - req, ok := cmd.(*IndsGetBalanceByAddressCmd) - if !ok { - return ErrRPCInvalidParams, errors.New("invalid params") - } - xylog.Logger.Infof("find user balances cmd params:%v", req) - - return findAddressBalances(s, req.Limit, req.Offset, req.Address, req.Chain, req.Protocol, req.Tick, req.Sort) -} - -func indsGetHoldersByTick(s *RpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) { - req, ok := cmd.(*IndsGetHoldersByTickCmd) - if !ok { - return ErrRPCInvalidParams, errors.New("invalid params") - } - xylog.Logger.Infof("find user balances cmd params:%v", req) - - return findTickHolders(s, req.Limit, req.Offset, req.Chain, req.Protocol, req.Tick, req.SortMode) -} diff --git a/jsonrpc/handlers.go b/jsonrpc/handlers.go index dae8db9..f56f41e 100644 --- a/jsonrpc/handlers.go +++ b/jsonrpc/handlers.go @@ -1,446 +1,210 @@ -// Copyright (c) 2023-2024 The UXUY Developer Team -// License: -// MIT License - -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: - -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. - -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -//SOFTWARE - package jsonrpc import ( "errors" - "fmt" - "github.com/uxuycom/indexer/model" - "github.com/uxuycom/indexer/protocol" "github.com/uxuycom/indexer/storage" "github.com/uxuycom/indexer/xylog" - "strings" ) +// v1 version var rpcHandlersBeforeInit = map[string]commandHandler{ - "inscription.All": handleFindAllInscriptions, - "inscription.Tick": handleFindInscriptionTick, - "address.Transactions": handleFindAddressTransactions, - "address.Balances": handleFindAddressBalances, - "address.Balance": handleFindAddressBalance, - "tick.Holders": handleFindTickHolders, - "block.LastNumber": handleGetLastBlockNumber, - "tool.InscriptionTxOperate": handleGetTxOperate, - "transaction.Info": handleGetTxByHash, - "tick.GetBriefs": handleGetTickBriefs, + "inscription.All": indsGetInscriptions, + "inscription.Tick": indsGetInscriptionByTick, + "address.Transactions": indsGetAddressTransactions, + "address.Balances": indsGetBalancesByAddress, + "address.Balance": indsGetAddressBalance, + "tick.Holders": indsGetHoldersByTick, + "block.LastNumber": indsGetLastBlockNumber, + "tool.InscriptionTxOperate": indsGetTxOperate, + "transaction.Info": indsGetTxByHash, + "tick.GetBriefs": indsGetTickBriefs, +} + +// v2 version +var rpcHandlersBeforeInitV2 = map[string]commandHandler{ + "inds_getInscriptions": indsGetInscriptions, + "index_getInscriptionByTick": indsGetInscriptionByTick, + "inds_search": indsSearch, + "inds_getAllChains": indsGetAllChains, + "inds_getTicks": indsGetTicks, + "inds_getTick": indsGetTick, + "inds_getTransactions": indsGetTransactions, + "inds_getTransactionByAddress": indsGetAddressTransactions, + "inds_getTransactionByHash": indsGetTxByHash, + "inds_getBalancesByAddress": indsGetBalancesByAddress, + "inds_getHoldersByTick": indsGetHoldersByTick, + "inds_getLastBlockNumberIndexed": indsGetLastBlockNumber, + "inds_getTickByCallData": indsGetTxOperate, // + "inds_getInscriptionTxOperate": indsGetTxOperate, // + "inds_getAddressBalance": indsGetAddressBalance, + "inds_getTickBriefs": indsGetTickBriefs, + "inds_getChainStat": indsGetChainStat, } -func handleFindAllInscriptions(s *RpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) { - req, ok := cmd.(*FindAllInscriptionsCmd) +func indsGetAllChains(s *RpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) { + req, ok := cmd.(*GetAllChainCmd) if !ok { return ErrRPCInvalidParams, errors.New("invalid params") } - xylog.Logger.Infof("find all Inscriptions cmd params:%v", req) - return findInsciptions(s, req.Limit, req.Offset, req.Chain, req.Protocol, req.Tick, req.DeployBy, req.Sort, storage.OrderByModeDesc) + xylog.Logger.Infof("find all chain cmd params:%v", req) + svr := NewService(s) + return svr.GetAllChain() } -func handleFindInscriptionTick(s *RpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) { - req, ok := cmd.(*FindInscriptionTickCmd) +func indsSearch(s *RpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) { + req, ok := cmd.(*IndsSearchCmd) if !ok { return ErrRPCInvalidParams, errors.New("invalid params") } - xylog.Logger.Infof("find inscriptions tick cmd params:%v", req) - - req.Protocol = strings.ToLower(req.Protocol) - req.Tick = strings.ToLower(req.Tick) + xylog.Logger.Infof("find all txs cmd params:%v", req) + svr := NewService(s) + return svr.Search(req.Keyword, req.Chain) - cacheKey := fmt.Sprintf("tick_%s_%s_%s", req.Chain, req.Protocol, req.Tick) - if ins, ok := s.cacheStore.Get(cacheKey); ok { - if ticks, ok := ins.(*InscriptionInfo); ok { - return ticks, nil - } - } +} - data, err := s.dbc.FindInscriptionByTick(req.Chain, req.Protocol, req.Tick) - if err != nil { - return ErrRPCInternal, err - } - if data == nil { - return ErrRPCRecordNotFound, err - } +func indsGetInscriptions(s *RpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) { - resp := &InscriptionInfo{ - Chain: data.Chain, - Protocol: data.Protocol, - Tick: data.Tick, - Name: data.Name, - LimitPerMint: data.LimitPerMint.String(), - DeployBy: data.DeployBy, - TotalSupply: data.TotalSupply.String(), - DeployHash: data.DeployHash, - DeployTime: uint32(data.DeployTime.Unix()), - TransferType: data.TransferType, - CreatedAt: uint32(data.CreatedAt.Unix()), - UpdatedAt: uint32(data.UpdatedAt.Unix()), - Decimals: data.Decimals, + req, ok := cmd.(*IndsGetInscriptionsCmd) + if !ok { + return ErrRPCInvalidParams, errors.New("invalid params") } - s.cacheStore.Set(cacheKey, resp) - return resp, nil + xylog.Logger.Infof("find all txs cmd params:%v", req) + svr := NewService(s) + return svr.GetInscriptions(req.Limit, req.Offset, req.Chain, req.Protocol, req.Tick, req.DeployBy, req.Sort, + storage.OrderByModeDesc) } -func handleFindAddressTransactions(s *RpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) { - req, ok := cmd.(*FindUserTransactionsCmd) +func indsGetTransactions(s *RpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) { + + req, ok := cmd.(*IndsGetTransactionCmd) if !ok { return ErrRPCInvalidParams, errors.New("invalid params") } - xylog.Logger.Infof("find user transactions cmd params:%v", req) + xylog.Logger.Infof("find all txs cmd params:%v", req) + svr := NewService(s) + return svr.GetTransactions(req.Address, req.Tick, req.Limit, req.Offset, req.SortMode) - req.Protocol = strings.ToLower(req.Protocol) - req.Tick = strings.ToLower(req.Tick) - - cacheKey := fmt.Sprintf("addr_txs_%d_%d_%s_%s_%s_%s_%d", req.Limit, req.Offset, req.Address, req.Chain, req.Protocol, req.Tick, req.Event) - if ins, ok := s.cacheStore.Get(cacheKey); ok { - if allIns, ok := ins.(*FindUserTransactionsResponse); ok { - return allIns, nil - } - } +} - transactions, total, err := s.dbc.GetAddressTxs(req.Limit, req.Offset, req.Address, req.Chain, req.Protocol, req.Tick, req.Event) - if err != nil { - return ErrRPCInternal, err +func indsGetTicks(s *RpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) { + req, ok := cmd.(*IndsGetTicksCmd) + if !ok { + return ErrRPCInvalidParams, errors.New("invalid params") } - txsHashes := make(map[string][]string) - for _, v := range transactions { - txsHashes[v.Chain] = append(txsHashes[v.Chain], v.TxHash) - } + xylog.Logger.Infof("find all Inscriptions cmd params:%v", req) + svr := NewService(s) + return svr.GetInscriptions(req.Limit, req.Offset, req.Chain, req.Protocol, req.Tick, req.DeployBy, req.Sort, + req.SortMode) +} - txMap := make(map[string]*model.Transaction) - for chain, hashes := range txsHashes { - txs, err := s.dbc.GetTxsByHashes(chain, hashes) - if err != nil { - xylog.Logger.Error(err) - continue - } - if len(txs) > 0 { - for _, v := range txs { - key := fmt.Sprintf("%s_%s", v.Chain, v.TxHash) - txMap[key] = v - } - } +func indsGetTick(s *RpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) { + req, ok := cmd.(*IndsGetTickCmd) + if !ok { + return ErrRPCInvalidParams, errors.New("invalid params") } + xylog.Logger.Infof("find Inscription cmd params:%v", req) - list := make([]*AddressTransaction, 0, len(transactions)) - for _, t := range transactions { - key := fmt.Sprintf("%s_%s", t.Chain, t.TxHash) - from := "" - to := "" - if tx, ok := txMap[key]; ok { - from = tx.From - to = tx.To - } + svr := NewService(s) - trans := &AddressTransaction{ - Event: t.Event, - TxHash: t.TxHash, - Address: t.Address, - From: from, - To: to, - Amount: t.Amount.String(), - Tick: t.Tick, - Protocol: t.Protocol, - Operate: t.Operate, - Chain: t.Chain, - Status: t.Status, - CreatedAt: uint32(t.CreatedAt.Unix()), - UpdatedAt: uint32(t.UpdatedAt.Unix()), - } - list = append(list, trans) - } - - resp := &FindUserTransactionsResponse{ - Transactions: list, - Total: total, - Limit: req.Limit, - Offset: req.Offset, - } - s.cacheStore.Set(cacheKey, resp) - return resp, nil + return svr.GetInscription(req.Chain, req.Protocol, req.Tick, req.DeployHash) } -func handleFindAddressBalances(s *RpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) { - req, ok := cmd.(*FindUserBalancesCmd) +func indsGetBalancesByAddress(s *RpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) { + req, ok := cmd.(*IndsGetBalanceByAddressCmd) if !ok { return ErrRPCInvalidParams, errors.New("invalid params") } xylog.Logger.Infof("find user balances cmd params:%v", req) - - return findAddressBalances(s, req.Limit, req.Offset, req.Address, req.Chain, req.Protocol, req.Tick, storage.OrderByModeDesc) + svr := NewService(s) + return svr.GetAddressBalances(req.Limit, req.Offset, req.Address, req.Chain, req.Protocol, req.Tick, req.Key, + req.Sort) } -func handleFindAddressBalance(s *RpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) { - req, ok := cmd.(*FindUserBalanceCmd) +func indsGetHoldersByTick(s *RpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) { + req, ok := cmd.(*IndsGetHoldersByTickCmd) if !ok { return ErrRPCInvalidParams, errors.New("invalid params") } - xylog.Logger.Infof("find user balance cmd params:%v", req) - - req.Protocol = strings.ToLower(req.Protocol) - req.Tick = strings.ToLower(req.Tick) - cacheKey := fmt.Sprintf("addr_balance_%s_%s_%s", req.Chain, req.Protocol, req.Tick) - if ins, ok := s.cacheStore.Get(cacheKey); ok { - if allIns, ok := ins.(*BalanceBrief); ok { - return allIns, nil - } - } - inscription, err := s.dbc.FindInscriptionByTick(req.Chain, req.Protocol, req.Tick) - if err != nil { - return ErrRPCInternal, err - } - if inscription == nil { - return nil, errors.New("Record not found") - } - - resp := &BalanceBrief{ - Tick: inscription.Tick, - TransferType: inscription.TransferType, - DeployHash: inscription.DeployHash, - } + xylog.Logger.Infof("find user balances cmd params:%v", req) + svr := NewService(s) + return svr.GetTickHolders(req.Limit, req.Offset, req.Chain, req.Protocol, req.Tick, req.SortMode) +} - // balance - balance, err := s.dbc.FindUserBalanceByTick(req.Chain, req.Protocol, req.Tick, req.Address) - if err != nil { - return ErrRPCInternal, err - } - if balance == nil { - return nil, errors.New("Record not found") +func indsGetInscriptionByTick(s *RpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) { + req, ok := cmd.(*IndsGetInscriptionTickCmd) + if !ok { + return ErrRPCInvalidParams, errors.New("invalid params") } - resp.Balance = balance.Balance.String() + xylog.Logger.Infof("find inscriptions tick cmd params:%v", req) + svr := NewService(s) + return svr.GetInscriptionByTick(req.Protocol, req.Tick, req.Chain) +} - switch inscription.TransferType { - case model.TransferTypeHash: - // transfer with hash - result, err := s.dbc.GetUtxosByAddress(req.Address, req.Chain, req.Protocol, req.Tick) - if err != nil { - return ErrRPCInternal, err - } - utxos := make([]*UTXOBrief, 0, len(result)) - for _, u := range result { - utxos = append(utxos, &UTXOBrief{ - Tick: u.Tick, - Amount: u.Amount.String(), - RootHash: u.RootHash, - }) - } - resp.Utxos = utxos +func indsGetAddressTransactions(s *RpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) { + req, ok := cmd.(*IndsGetUserTransactionsCmd) + if !ok { + return ErrRPCInvalidParams, errors.New("invalid params") } - s.cacheStore.Set(cacheKey, resp) - return resp, nil + xylog.Logger.Infof("find user transactions cmd params:%v", req) + svr := NewService(s) + return svr.GetAddressTransactions(req.Protocol, req.Tick, req.Chain, req.Limit, req.Offset, req.Address, req.Event) } -func handleFindTickHolders(s *RpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) { - req, ok := cmd.(*FindTickHoldersCmd) +func indsGetTxByHash(s *RpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) { + req, ok := cmd.(*GetTxByHashCmd) if !ok { return ErrRPCInvalidParams, errors.New("invalid params") } - xylog.Logger.Infof("find tick holders cmd params:%v", req) - return findTickHolders(s, req.Limit, req.Offset, req.Chain, req.Protocol, req.Tick, storage.OrderByModeDesc) + xylog.Logger.Infof("get tx by hash cmd params:%v", req) + svr := NewService(s) + return svr.GetTxByHash(req.TxHash, req.Chain) } -func handleGetLastBlockNumber(s *RpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) { +func indsGetLastBlockNumber(s *RpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) { req, ok := cmd.(*LastBlockNumberCmd) if !ok { return ErrRPCInvalidParams, errors.New("invalid params") } xylog.Logger.Infof("get last block number cmd params:%v", req) - chains := strings.Join(req.Chains, "_") - cacheKey := fmt.Sprintf("block_number_%s", chains) - if ins, ok := s.cacheStore.Get(cacheKey); ok { - if allIns, ok := ins.([]*BlockInfo); ok { - return allIns, nil - } - } - result := make([]*BlockInfo, 0) - for _, chain := range req.Chains { - block, err := s.dbc.FindLastBlock(chain) - if err != nil { - return ErrRPCInternal, err - } - blockInfo := &BlockInfo{ - Chain: chain, - BlockNumber: block.BlockNumber, - TimeStamp: uint32(block.BlockTime.Unix()), - BlockTime: block.BlockTime.String(), - } - result = append(result, blockInfo) - } - s.cacheStore.Set(cacheKey, result) - return result, nil + svr := NewService(s) + return svr.GetLastBlockNumber(req.Chains) } -func handleGetTxOperate(s *RpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) { +func indsGetTxOperate(s *RpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) { req, ok := cmd.(*TxOperateCmd) if !ok { return ErrRPCInvalidParams, errors.New("invalid params") } - cacheKey := fmt.Sprintf("tx_operate_%s_%s", req.Chain, req.InputData) - if ins, ok := s.cacheStore.Get(cacheKey); ok { - if allIns, ok := ins.(*TxOperateResponse); ok { - return allIns, nil - } - } - operate := protocol.GetOperateByTxInput(req.Chain, req.InputData, s.dbc) - if operate == nil { - return nil, errors.New("Record not found") - } - var deployHash string - if operate.Protocol != "" && operate.Tick != "" { - inscription, err := s.dbc.FindInscriptionByTick(strings.ToLower(req.Chain), strings.ToLower(string(operate.Protocol)), strings.ToLower(operate.Tick)) - if err != nil { - xylog.Logger.Errorf("the query for the inscription failed. chain:%s protocol:%s tick:%s err=%s", req.Chain, string(operate.Protocol), operate.Tick, err) - } - if inscription != nil { - deployHash = inscription.DeployHash - } - } - - resp := &TxOperateResponse{ - Protocol: operate.Protocol, - Operate: operate.Operate, - Tick: operate.Tick, - DeployHash: deployHash, - } - s.cacheStore.Set(cacheKey, resp) - return resp, nil + svr := NewService(s) + return svr.GetTxOperate(req.Chain, req.InputData) } -func handleGetTxByHash(s *RpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) { - req, ok := cmd.(*GetTxByHashCmd) +func indsGetAddressBalance(s *RpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) { + req, ok := cmd.(*FindUserBalanceCmd) if !ok { return ErrRPCInvalidParams, errors.New("invalid params") } - xylog.Logger.Infof("get tx by hash cmd params:%v", req) - - req.TxHash = strings.ToLower(req.TxHash) - cacheKey := fmt.Sprintf("tx_info_%s_%s", req.Chain, req.TxHash) - if ins, ok := s.cacheStore.Get(cacheKey); ok { - if allIns, ok := ins.(*GetTxByHashResponse); ok { - return allIns, nil - } - } - tx, err := s.dbc.FindTransaction(req.Chain, req.TxHash) - if err != nil { - return nil, err - } - if tx == nil { - return nil, errors.New("Record not found") - } - - resp := &GetTxByHashResponse{} - - // not inscription transaction - if tx == nil { - resp.IsInscription = false - return resp, nil - } - - transInfo := &TransactionInfo{ - Protocol: tx.Protocol, - Tick: tx.Tick, - From: tx.From, - To: tx.To, - } - - inscription, err := s.dbc.FindInscriptionByTick(tx.Chain, tx.Protocol, tx.Tick) - if err != nil { - return ErrRPCInternal, err - } - if inscription == nil { - return nil, errors.New("Record not found") - } - transInfo.DeployHash = inscription.DeployHash - - // get amount from address tx tab - addressTx, err := s.dbc.FindAddressTxByHash(req.Chain, req.TxHash) - if err != nil { - return ErrRPCInternal, err - } - if addressTx == nil { - return nil, errors.New("Record not found") - } - transInfo.Amount = addressTx.Amount.String() - - resp.IsInscription = true - resp.Transaction = transInfo - s.cacheStore.Set(cacheKey, resp) - return resp, nil + xylog.Logger.Infof("find user balance cmd params:%v", req) + svr := NewService(s) + return svr.GetAddressBalance(req.Protocol, req.Chain, req.Tick, req.Address) } -func handleGetTickBriefs(s *RpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) { +func indsGetTickBriefs(s *RpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) { req, ok := cmd.(*GetTickBriefsCmd) if !ok { return ErrRPCInvalidParams, errors.New("invalid params") } xylog.Logger.Infof("get tick briefs cmd params:%v", req) - - deployHashGroups := make(map[string][]string) - key := "" - for _, address := range req.Addresses { - deployHashGroups[address.Chain] = append(deployHashGroups[address.Chain], address.DeployHash) - key += fmt.Sprintf("%s_%s", address.Chain, address.DeployHash) - } - - cacheKey := fmt.Sprintf("tick_briefs_%s", key) - if ins, ok := s.cacheStore.Get(cacheKey); ok { - if allIns, ok := ins.(*GetTickBriefsResp); ok { - return allIns, nil - } - } - - result := make([]*model.InscriptionOverView, 0, len(req.Addresses)) - for chain, groups := range deployHashGroups { - dbTicks, err := s.dbc.GetInscriptionsByChain(chain, groups) - if err != nil { - continue - } - for _, dbTick := range dbTicks { - overview := &model.InscriptionOverView{ - Chain: dbTick.Chain, - Protocol: dbTick.Protocol, - Tick: dbTick.Tick, - Name: dbTick.Name, - LimitPerMint: dbTick.LimitPerMint, - TotalSupply: dbTick.TotalSupply, - DeployBy: dbTick.DeployBy, - DeployHash: dbTick.DeployHash, - DeployTime: dbTick.DeployTime, - TransferType: dbTick.TransferType, - Decimals: dbTick.Decimals, - CreatedAt: dbTick.CreatedAt, - } - stat, _ := s.dbc.FindInscriptionsStatsByTick(dbTick.Chain, dbTick.Protocol, dbTick.Tick) - if stat != nil { - overview.Holders = stat.Holders - overview.Minted = stat.Minted - overview.TxCnt = stat.TxCnt - } - result = append(result, overview) - } + svr := NewService(s) + return svr.GetTickBriefs(req.Addresses) +} +func indsGetChainStat(s *RpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) { + req, ok := cmd.(*GetChainStatCmd) + if !ok { + return ErrRPCInvalidParams, errors.New("invalid params") } - - resp := &GetTickBriefsResp{} - resp.Inscriptions = result - s.cacheStore.Set(cacheKey, resp) - - return resp, nil + xylog.Logger.Infof("get chain stat cmd params:%v", req) + svr := NewService(s) + return svr.GetChainStat() } diff --git a/jsonrpc/openapi.go b/jsonrpc/openapi.go index 05e5484..8acc1d3 100644 --- a/jsonrpc/openapi.go +++ b/jsonrpc/openapi.go @@ -39,6 +39,16 @@ func CreateOpenApi() { _, _ = w.Write(data) }) + http.HandleFunc("/v1/docs/openapi_v2.json", func(w http.ResponseWriter, r *http.Request) { + data, err := os.ReadFile("docs/openapi_v2.json") + if err != nil { + http.Error(w, "Failed to read file", http.StatusInternalServerError) + return + } + w.Header().Set("Content-Type", "application/json") + _, _ = w.Write(data) + }) + // start if err := http.ListenAndServe(":8011", nil); err != nil { log.Fatal(err) diff --git a/jsonrpc/server.go b/jsonrpc/server.go index e1c0ea7..291d53e 100644 --- a/jsonrpc/server.go +++ b/jsonrpc/server.go @@ -18,11 +18,8 @@ import ( "github.com/uxuycom/indexer/config" "github.com/uxuycom/indexer/storage" "io" - "log" "net" "net/http" - "os" - "path/filepath" "regexp" "runtime" "strconv" @@ -49,28 +46,9 @@ var ( ) var ( - cfg = &Config{} + cfg *config.RpcConfig ) -// Config defines the configuration options for btcd. -// -// See loadConfig for details on the configuration load process. -type Config struct { - DebugLevel string `json:"debug_level"` - DisableTLS bool `json:"notls" description:"Disable TLS for the RPC server -- NOTE: This is only allowed if the RPC server is bound to localhost"` - RPCCert string `json:"rpccert" description:"File containing the certificate file"` - RPCKey string `json:"rpckey" description:"File containing the certificate key"` - RPCLimitPass string `json:"rpclimitpass" default-mask:"-" description:"Password for limited RPC connections"` - RPCLimitUser string `json:"rpclimituser" description:"Username for limited RPC connections"` - RPCListeners []string `json:"rpclisten" description:"Add an interface/port to listen for RPC connections (default port: 6583, testnet: 16583)"` - RPCMaxClients int `json:"rpcmaxclients" description:"Max number of RPC clients for standard connections"` - RPCMaxConcurrentReqs int `json:"rpcmaxconcurrentreqs" description:"Max number of concurrent RPC requests that may be processed concurrently"` - RPCMaxWebsockets int `json:"rpcmaxwebsockets" description:"Max number of RPC websocket connections"` - RPCQuirks bool `json:"rpcquirks" description:"Mirror some JSON-RPC quirks of Bitcoin Core -- NOTE: Discouraged unless interoperability issues need to be worked around"` - RPCPass string `json:"rpcpass" default-mask:"-" description:"Password for RPC connections"` - RPCUser string `json:"rpcuser" description:"Username for RPC connections"` -} - type commandHandler func(*RpcServer, interface{}, <-chan struct{}) (interface{}, error) // rpcHandlers maps RPC command strings to appropriate handler functions. @@ -701,16 +679,9 @@ type RpcServerConfig struct { } // NewRPCServer returns a new instance of the RpcServer struct. -func NewRPCServer(dbc *storage.DBClient, cacheConfig *config.CacheConfig) (*RpcServer, error) { - //load cfg - loadCfg() - - //init xylog - //l, err := logrus.ParseLevel(cfg.DebugLevel) - //if err != nil { - // log.Fatalf("debug_level err:%v", err) - //} - //logrus.SetLevel(l) +func NewRPCServer(dbc *storage.DBClient, config *config.RpcConfig) (*RpcServer, error) { + // load cfg + cfg = config // Setup listeners for the configured RPC listen addresses and // TLS settings. @@ -732,11 +703,11 @@ func NewRPCServer(dbc *storage.DBClient, cacheConfig *config.CacheConfig) (*RpcS requestProcessShutdown: make(chan struct{}), quit: make(chan int), dbc: dbc, - cacheConfig: cacheConfig, + cacheConfig: cfg.CacheStore, } - if cacheConfig != nil && cacheConfig.Started { - cacheStore := cache_store.NewCacheStore(cacheConfig.MaxCapacity, cacheConfig.Duration) + if cfg.CacheStore != nil && cfg.CacheStore.Started { + cacheStore := cache_store.NewCacheStore(cfg.CacheStore.MaxCapacity, cfg.CacheStore.Duration) rpc.cacheStore = cacheStore go cacheStore.Clear() } @@ -754,28 +725,6 @@ func NewRPCServer(dbc *storage.DBClient, cacheConfig *config.CacheConfig) (*RpcS return &rpc, nil } -func loadCfg() { - // Default config. - configFileName := "config_jsonrpc.json" - if len(os.Args) > 1 { - configFileName = os.Args[1] - } - - configFileName, _ = filepath.Abs(configFileName) - log.Printf("Loading config: %s", configFileName) - - configFile, err := os.Open(configFileName) - if err != nil { - log.Fatalf("open config file[%s] error[%v]", configFileName, err.Error()) - } - - defer configFile.Close() - jsonParser := json.NewDecoder(configFile) - if err := jsonParser.Decode(cfg); err != nil { - log.Fatal("load config error: ", err.Error()) - } -} - // setupRPCListeners returns a slice of listeners that are configured for use // with the RPC server depending on the configuration settings for listen // addresses and TLS. diff --git a/jsonrpc/service.go b/jsonrpc/service.go new file mode 100644 index 0000000..7e12b6f --- /dev/null +++ b/jsonrpc/service.go @@ -0,0 +1,697 @@ +package jsonrpc + +import ( + "errors" + "fmt" + "github.com/ethereum/go-ethereum/common" + "github.com/shopspring/decimal" + "github.com/uxuycom/indexer/model" + "github.com/uxuycom/indexer/protocol" + "github.com/uxuycom/indexer/xylog" + "strings" +) + +type Service struct { + rpcServer *RpcServer +} + +var service *Service + +func NewService(rpcServer *RpcServer) *Service { + if service != nil { + return service + } + return &Service{ + rpcServer: rpcServer, + } +} + +func (s *Service) GetAddressBalances(limit, offset int, address, chain, protocol, tick string, key string, + sort int) (interface{}, error) { + protocol = strings.ToLower(protocol) + tick = strings.ToLower(tick) + cacheKey := fmt.Sprintf("addr_balances_%d_%d_%s_%s_%s_%s_%d", limit, offset, address, chain, protocol, tick, sort) + if ins, ok := s.rpcServer.cacheStore.Get(cacheKey); ok { + if allIns, ok := ins.(*FindUserBalancesResponse); ok { + return allIns, nil + } + } + + balances, total, err := s.rpcServer.dbc.GetAddressInscriptions(limit, offset, address, chain, protocol, tick, key, sort) + if err != nil { + return ErrRPCInternal, err + } + + list := make([]*BalanceInfo, 0, len(balances)) + for _, b := range balances { + balance := &BalanceInfo{ + Chain: b.Chain, + Protocol: b.Protocol, + Tick: b.Tick, + Address: b.Address, + Balance: b.Balance.String(), + DeployHash: b.DeployHash, + TransferType: b.TransferType, + } + list = append(list, balance) + } + + resp := &FindUserBalancesResponse{ + Inscriptions: list, + Total: total, + Limit: limit, + Offset: offset, + } + s.rpcServer.cacheStore.Set(cacheKey, resp) + return resp, nil +} + +func (s *Service) GetInscriptions(limit, offset int, chain, protocol, tick, deployBy string, sort, + sortMode int) (interface{}, error) { + protocol = strings.ToLower(protocol) + tick = strings.ToLower(tick) + cacheKey := fmt.Sprintf("all_ins_%d_%d_%s_%s_%s_%s_%d_%d", limit, offset, chain, protocol, tick, deployBy, sort, sortMode) + if ins, ok := s.rpcServer.cacheStore.Get(cacheKey); ok { + if allIns, ok := ins.(*IndsGetAllInscriptionsResponse); ok { + return allIns, nil + } + } + inscriptions, total, err := s.rpcServer.dbc.GetInscriptions(limit, offset, chain, protocol, tick, deployBy, sort, sortMode) + if err != nil { + return ErrRPCInternal, err + } + + result := make([]*model.InscriptionBrief, 0, len(inscriptions)) + + for _, ins := range inscriptions { + brief := &model.InscriptionBrief{ + Chain: ins.Chain, + Protocol: ins.Protocol, + Tick: ins.Name, + DeployBy: ins.DeployBy, + DeployHash: ins.DeployHash, + TotalSupply: ins.TotalSupply.String(), + Holders: ins.Holders, + Minted: ins.Minted.String(), + LimitPerMint: ins.LimitPerMint.String(), + TransferType: ins.TransferType, + Status: model.MintStatusProcessing, + TxCnt: ins.TxCnt, + CreatedAt: uint32(ins.CreatedAt.Unix()), + } + + minted := ins.Minted + totalSupply := ins.TotalSupply + + if totalSupply != decimal.Zero && minted != decimal.Zero { + percentage, _ := minted.Div(totalSupply).Float64() + if percentage >= 1 { + percentage = 1 + } + brief.MintedPercent = fmt.Sprintf("%.4f", percentage) + } + + if ins.Minted.Cmp(ins.TotalSupply) >= 0 { + brief.Status = model.MintStatusAllMinted + } + + result = append(result, brief) + } + + resp := &IndsGetAllInscriptionsResponse{ + Inscriptions: result, + Total: total, + Limit: limit, + Offset: offset, + } + s.rpcServer.cacheStore.Set(cacheKey, resp) + return resp, nil +} + +func (s *Service) GetInscription(chain, protocol, tick, deployHash string) (interface{}, error) { + protocol = strings.ToLower(protocol) + tick = strings.ToLower(tick) + + cacheKey := fmt.Sprintf("tick_%s_%s_%s_%s", chain, protocol, tick, deployHash) + if ins, ok := s.rpcServer.cacheStore.Get(cacheKey); ok { + if ticks, ok := ins.(*InscriptionInfo); ok { + return ticks, nil + } + } + + inscription, err := s.rpcServer.dbc.FindInscriptionInfo(chain, protocol, tick, deployHash) + if err != nil { + return ErrRPCInternal, err + } + if inscription == nil { + return ErrRPCRecordNotFound, err + } + + resp := &InscriptionInfo{ + Chain: inscription.Chain, + Protocol: inscription.Protocol, + Tick: inscription.Tick, + Name: inscription.Name, + LimitPerMint: inscription.LimitPerMint.String(), + DeployBy: inscription.DeployBy, + TotalSupply: inscription.TotalSupply.String(), + DeployHash: inscription.DeployHash, + TransferType: inscription.TransferType, + Decimals: inscription.Decimals, + Minted: inscription.Minted.String(), + Holders: inscription.Holders, + TxCnt: inscription.TxCnt, + Progress: inscription.Progress.String(), + DeployTime: uint32(inscription.DeployTime.Unix()), + CreatedAt: uint32(inscription.CreatedAt.Unix()), + UpdatedAt: uint32(inscription.UpdatedAt.Unix()), + } + + s.rpcServer.cacheStore.Set(cacheKey, resp) + return resp, nil +} + +func (s *Service) GetTickHolders(limit int, offset int, chain, protocol, tick string, + sortMode int) (interface{}, + error) { + protocol = strings.ToLower(protocol) + tick = strings.ToLower(tick) + cacheKey := fmt.Sprintf("all_ins_%d_%d_%s_%s_%s_%d", limit, offset, chain, protocol, tick, sortMode) + if ins, ok := s.rpcServer.cacheStore.Get(cacheKey); ok { + if allIns, ok := ins.(*FindTickHoldersResponse); ok { + return allIns, nil + } + } + + // get inscription info + inscription, err := s.rpcServer.dbc.FindInscriptionByTick(chain, protocol, tick) + if err != nil { + return ErrRPCInternal, err + } + if inscription == nil { + return nil, errors.New("Record not found") + } + + holders, total, err := s.rpcServer.dbc.GetHoldersByTick(limit, offset, chain, protocol, tick, sortMode) + if err != nil { + return ErrRPCInternal, err + } + + list := make([]*TickHolder, 0, len(holders)) + for _, holder := range holders { + balance := &TickHolder{ + Chain: holder.Chain, + Protocol: holder.Protocol, + Tick: holder.Tick, + DeployHash: inscription.DeployHash, + Address: holder.Address, + Balance: holder.Balance.String(), + TotalSupply: inscription.TotalSupply.String(), + } + list = append(list, balance) + } + + resp := &FindTickHoldersResponse{ + Holders: list, + Total: total, + Limit: limit, + Offset: offset, + } + + s.rpcServer.cacheStore.Set(cacheKey, resp) + return resp, nil +} + +func (s *Service) GetTransactions(address string, tick string, limit int, offset int, + sortMode int) (interface{}, + error) { + + address = strings.ToLower(address) + tick = strings.ToLower(tick) + + cacheKey := fmt.Sprintf("all_transactions_%d_%d_%s_%s_%d", limit, offset, address, tick, sortMode) + if ins, ok := s.rpcServer.cacheStore.Get(cacheKey); ok { + if transactions, ok := ins.(*CommonResponse); ok { + return transactions, nil + } + } + txs, total, err := s.rpcServer.dbc.GetTransactions(address, tick, limit, offset, sortMode) + if err != nil { + return ErrRPCInternal, err + } + transactions := make([]*TransactionResponse, 0) + for _, v := range txs { + + trs := &TransactionResponse{ + ID: v.ID, + Chain: v.Chain, + Protocol: v.Protocol, + BlockHeight: v.BlockHeight, + PositionInBlock: v.PositionInBlock, + BlockTime: v.BlockTime, + TxHash: common.Bytes2Hex(v.TxHash), + From: v.From, + To: v.To, + Gas: v.Gas, + GasPrice: v.GasPrice, + Status: v.Status, + CreatedAt: v.CreatedAt, + UpdatedAt: v.UpdatedAt, + } + transactions = append(transactions, trs) + } + + resp := &CommonResponse{ + Data: transactions, + Total: total, + Limit: limit, + Offset: offset, + } + s.rpcServer.cacheStore.Set(cacheKey, resp) + return resp, nil +} + +func (s *Service) GetInscriptionsStats(limit int, offset int, sortMode int) (interface{}, + error) { + txs, total, err := s.rpcServer.dbc.GetInscriptionStatsList(limit, offset, sortMode) + if err != nil { + return ErrRPCInternal, err + } + + resp := &CommonResponse{ + Data: txs, + Total: total, + Limit: limit, + Offset: offset, + } + return resp, nil +} + +func (s *Service) Search(keyword, chain string) (interface{}, error) { + + result := &SearchResult{} + if len(keyword) <= 10 { + // Inscription + inscriptions, _ := s.GetInscriptions(10, 0, chain, "", keyword, "", 2, 0) + result.Data = inscriptions + result.Type = "Inscription" + } + if strings.HasPrefix(keyword, "0x") { + if len(keyword) == 42 { + // address + address, _, _ := s.rpcServer.dbc.GetBalancesChainByAddress(10, 0, keyword, chain, "", "") + result.Data = address + result.Type = "Address" + } + if len(keyword) == 66 { + // tx hash + result.Data, _ = s.rpcServer.dbc.FindBalanceByTxHash(keyword) + result.Type = "TxHash" + } + } else { + if len(keyword) == 64 { + // tx hash + result.Data, _ = s.rpcServer.dbc.FindBalanceByTxHash(keyword) + result.Type = "tx" + } else { + // address + address, _, _ := s.rpcServer.dbc.GetBalancesChainByAddress(10, 0, keyword, chain, "", "") + result.Data = address + result.Type = "Address" + } + } + return result, nil +} +func (s *Service) GetAllChain() (interface{}, error) { + chains, err := s.rpcServer.dbc.GetAllChainInfo() + if err != nil { + return ErrRPCInternal, err + } + return chains, nil +} + +func (s *Service) GetInscriptionByTick(protocol string, tick string, chain string) (interface{}, error) { + + protocol = strings.ToLower(protocol) + tick = strings.ToLower(tick) + + cacheKey := fmt.Sprintf("tick_%s_%s_%s", chain, protocol, tick) + if ins, ok := s.rpcServer.cacheStore.Get(cacheKey); ok { + if ticks, ok := ins.(*InscriptionInfo); ok { + return ticks, nil + } + } + + data, err := s.rpcServer.dbc.FindInscriptionByTick(chain, protocol, tick) + if err != nil { + return ErrRPCInternal, err + } + if data == nil { + return ErrRPCRecordNotFound, err + } + + resp := &InscriptionInfo{ + Chain: data.Chain, + Protocol: data.Protocol, + Tick: data.Tick, + Name: data.Name, + LimitPerMint: data.LimitPerMint.String(), + DeployBy: data.DeployBy, + TotalSupply: data.TotalSupply.String(), + DeployHash: data.DeployHash, + DeployTime: uint32(data.DeployTime.Unix()), + TransferType: data.TransferType, + CreatedAt: uint32(data.CreatedAt.Unix()), + UpdatedAt: uint32(data.UpdatedAt.Unix()), + Decimals: data.Decimals, + } + s.rpcServer.cacheStore.Set(cacheKey, resp) + return resp, nil +} + +func (s *Service) GetAddressTransactions(protocol string, tick string, chain string, limit int, + offset int, + address string, event int8) (interface{}, error) { + + protocol = strings.ToLower(protocol) + tick = strings.ToLower(tick) + + cacheKey := fmt.Sprintf("addr_txs_%d_%d_%s_%s_%s_%s_%d", limit, offset, address, chain, tick, + tick, event) + if ins, ok := s.rpcServer.cacheStore.Get(cacheKey); ok { + if allIns, ok := ins.(*FindUserTransactionsResponse); ok { + return allIns, nil + } + } + + transactions, total, err := s.rpcServer.dbc.GetAddressTxs(limit, offset, address, chain, protocol, tick, event) + if err != nil { + return ErrRPCInternal, err + } + + txsHashes := make(map[string][]string) + for _, v := range transactions { + txsHashes[v.Chain] = append(txsHashes[v.Chain], common.Bytes2Hex(v.TxHash)) + } + + txMap := make(map[string]*model.Transaction) + for chain, hashes := range txsHashes { + txs, err := s.rpcServer.dbc.GetTxsByHashes(chain, hashes) + if err != nil { + xylog.Logger.Error(err) + continue + } + if len(txs) > 0 { + for _, v := range txs { + key := fmt.Sprintf("%s_%s", v.Chain, v.TxHash) + txMap[key] = v + } + } + } + list := make([]*AddressTransaction, 0, len(transactions)) + for _, t := range transactions { + key := fmt.Sprintf("%s_%s", t.Chain, t.TxHash) + from := "" + to := "" + if tx, ok := txMap[key]; ok { + from = tx.From + to = tx.To + } + + trans := &AddressTransaction{ + Event: t.Event, + TxHash: common.Bytes2Hex(t.TxHash), + Address: t.Address, + From: from, + To: to, + Amount: t.Amount.String(), + Tick: t.Tick, + Protocol: t.Protocol, + Operate: t.Operate, + Chain: t.Chain, + Status: t.Status, + CreatedAt: uint32(t.CreatedAt.Unix()), + UpdatedAt: uint32(t.UpdatedAt.Unix()), + } + list = append(list, trans) + } + + resp := &FindUserTransactionsResponse{ + Transactions: list, + Total: total, + Limit: limit, + Offset: offset, + } + s.rpcServer.cacheStore.Set(cacheKey, resp) + return resp, nil +} + +func (s *Service) GetTxByHash(txHash string, chain string) (interface{}, error) { + + txHash = strings.ToLower(txHash) + cacheKey := fmt.Sprintf("tx_info_%s_%s", chain, txHash) + if ins, ok := s.rpcServer.cacheStore.Get(cacheKey); ok { + if allIns, ok := ins.(*GetTxByHashResponse); ok { + return allIns, nil + } + } + tx, err := s.rpcServer.dbc.FindTransaction(chain, txHash) + if err != nil { + return nil, err + } + if tx == nil { + return nil, errors.New("Record not found") + } + + resp := &GetTxByHashResponse{} + + // not inscription transaction + if tx == nil { + resp.IsInscription = false + return resp, nil + } + + transInfo := &TransactionInfo{ + Protocol: "", + Tick: "", + From: tx.From, + To: tx.To, + Op: tx.Op, + } + + inscription, err := s.rpcServer.dbc.FindInscriptionByTick(tx.Chain, "", "") + if err != nil { + return ErrRPCInternal, err + } + if inscription == nil { + return nil, errors.New("Record not found") + } + transInfo.DeployHash = inscription.DeployHash + + // get amount from address tx tab + addressTx, err := s.rpcServer.dbc.FindAddressTxByHash(chain, txHash) + if err != nil { + return ErrRPCInternal, err + } + if addressTx == nil { + return nil, errors.New("Record not found") + } + transInfo.Amount = addressTx.Amount.String() + + resp.IsInscription = true + resp.Transaction = transInfo + s.rpcServer.cacheStore.Set(cacheKey, resp) + return resp, nil +} + +func (s *Service) GetLastBlockNumber(chains []string) (interface{}, error) { + + var chainsStr string + if len(chains) > 0 { + chainsStr = strings.Join(chains, "_") + } else { + chainsStr = fmt.Sprintf("%v", len(chains)) + } + xylog.Logger.Infof("get last block chainsStr:%v, chains len:%v", chainsStr, len(chains)) + + cacheKey := fmt.Sprintf("block_number_%s", chainsStr) + if ins, ok := s.rpcServer.cacheStore.Get(cacheKey); ok { + if allIns, ok := ins.([]*BlockInfo); ok { + return allIns, nil + } + } + result := make([]*BlockInfo, 0) + var err error + chs := chains + if len(chains) == 0 { + chs, err = s.rpcServer.dbc.GetAllChainFromBlock() + if err != nil { + chs = []string{} + } + xylog.Logger.Infof("get last block from db chains:%v", chs) + } + for _, chain := range chs { + block, err := s.rpcServer.dbc.FindLastBlock(chain) + if err != nil { + return ErrRPCInternal, err + } + blockInfo := &BlockInfo{ + Chain: chain, + BlockNumber: block.BlockNumber, + TimeStamp: uint32(block.BlockTime.Unix()), + BlockTime: block.BlockTime.String(), + } + result = append(result, blockInfo) + } + s.rpcServer.cacheStore.Set(cacheKey, result) + return result, nil +} + +func (s *Service) GetTxOperate(chain string, inputData string) (interface{}, error) { + cacheKey := fmt.Sprintf("tx_operate_%s_%s", chain, inputData) + if ins, ok := s.rpcServer.cacheStore.Get(cacheKey); ok { + if allIns, ok := ins.(*TxOperateResponse); ok { + return allIns, nil + } + } + operate := protocol.GetOperateByTxInput(chain, inputData, s.rpcServer.dbc) + xylog.Logger.Infof("handleGetTxOperate operate =%v, inputdata=%v, chain=%v", operate, inputData, chain) + if operate == nil { + return nil, errors.New("Record not found") + } + var deployHash string + if operate.Protocol != "" && operate.Tick != "" { + inscription, err := s.rpcServer.dbc.FindInscriptionByTick(strings.ToLower(chain), + strings.ToLower(string(operate.Protocol)), strings.ToLower(operate.Tick)) + if err != nil { + xylog.Logger.Errorf("the query for the inscription failed. chain:%s protocol:%s tick:%s err=%s", chain, + string(operate.Protocol), operate.Tick, err) + } + if inscription != nil { + deployHash = inscription.DeployHash + } + } + + resp := &TxOperateResponse{ + Protocol: operate.Protocol, + Operate: operate.Operate, + Tick: operate.Tick, + DeployHash: deployHash, + } + s.rpcServer.cacheStore.Set(cacheKey, resp) + return resp, nil +} + +func (s *Service) GetAddressBalance(protocol string, chain string, tick string, + address string) (interface{}, error) { + + protocol = strings.ToLower(protocol) + tick = strings.ToLower(tick) + cacheKey := fmt.Sprintf("addr_balance_%s_%s_%s", chain, protocol, tick) + if ins, ok := s.rpcServer.cacheStore.Get(cacheKey); ok { + if allIns, ok := ins.(*BalanceBrief); ok { + return allIns, nil + } + } + inscription, err := s.rpcServer.dbc.FindInscriptionByTick(chain, protocol, tick) + if err != nil { + return ErrRPCInternal, err + } + if inscription == nil { + return nil, errors.New("Record not found") + } + + resp := &BalanceBrief{ + Tick: inscription.Tick, + TransferType: inscription.TransferType, + DeployHash: inscription.DeployHash, + } + + // balance + balance, err := s.rpcServer.dbc.FindUserBalanceByTick(chain, protocol, tick, address) + if err != nil { + return ErrRPCInternal, err + } + if balance == nil { + return nil, errors.New("Record not found") + } + resp.Balance = balance.Balance.String() + + switch inscription.TransferType { + case model.TransferTypeHash: + // transfer with hash + result, err := s.rpcServer.dbc.GetUtxosByAddress(address, chain, protocol, tick) + if err != nil { + return ErrRPCInternal, err + } + utxos := make([]*UTXOBrief, 0, len(result)) + for _, u := range result { + utxos = append(utxos, &UTXOBrief{ + Tick: u.Tick, + Amount: u.Amount.String(), + RootHash: u.RootHash, + }) + } + resp.Utxos = utxos + } + s.rpcServer.cacheStore.Set(cacheKey, resp) + return resp, nil +} + +func (s *Service) GetTickBriefs(addresses []*TickAddress) (interface{}, error) { + + deployHashGroups := make(map[string][]string) + key := "" + for _, address := range addresses { + deployHashGroups[address.Chain] = append(deployHashGroups[address.Chain], address.DeployHash) + key += fmt.Sprintf("%s_%s", address.Chain, address.DeployHash) + } + + cacheKey := fmt.Sprintf("tick_briefs_%s", key) + if ins, ok := s.rpcServer.cacheStore.Get(cacheKey); ok { + if allIns, ok := ins.(*GetTickBriefsResp); ok { + return allIns, nil + } + } + + result := make([]*model.InscriptionOverView, 0, len(addresses)) + for chain, groups := range deployHashGroups { + dbTicks, err := s.rpcServer.dbc.GetInscriptionsByChain(chain, groups) + if err != nil { + continue + } + for _, dbTick := range dbTicks { + overview := &model.InscriptionOverView{ + Chain: dbTick.Chain, + Protocol: dbTick.Protocol, + Tick: dbTick.Tick, + Name: dbTick.Name, + LimitPerMint: dbTick.LimitPerMint, + TotalSupply: dbTick.TotalSupply, + DeployBy: dbTick.DeployBy, + DeployHash: dbTick.DeployHash, + DeployTime: dbTick.DeployTime, + TransferType: dbTick.TransferType, + Decimals: dbTick.Decimals, + CreatedAt: dbTick.CreatedAt, + } + stat, _ := s.rpcServer.dbc.FindInscriptionsStatsByTick(dbTick.Chain, dbTick.Protocol, dbTick.Tick) + if stat != nil { + overview.Holders = stat.Holders + overview.Minted = stat.Minted + overview.TxCnt = stat.TxCnt + } + result = append(result, overview) + } + } + + resp := &GetTickBriefsResp{} + resp.Inscriptions = result + s.rpcServer.cacheStore.Set(cacheKey, resp) + + return resp, nil +} +func (s *Service) GetChainStat() (interface{}, error) { + + return nil, nil +} diff --git a/model/balance.go b/model/balance.go index 06800dc..22b206a 100644 --- a/model/balance.go +++ b/model/balance.go @@ -78,3 +78,13 @@ type BalanceInscription struct { DeployHash string `json:"deploy_hash"` TransferType int8 `json:"transfer_type"` } + +type BalanceChain struct { + Chain string `json:"chain"` + Address string `json:"address"` + Balance decimal.Decimal `json:"balance"` +} + +func (BalanceChain) TableName() string { + return "balances" +} diff --git a/model/block.go b/model/block.go index 62bacea..e9aad20 100644 --- a/model/block.go +++ b/model/block.go @@ -5,6 +5,7 @@ import ( ) type Block struct { + ChainId int64 `json:"chain_id" gorm:"column:chain_id"` Chain string `json:"chain" gorm:"column:chain"` BlockHash string `json:"block_hash" gorm:"column:block_hash"` BlockNumber string `json:"block_number" gorm:"column:block_number"` diff --git a/model/chain.go b/model/chain.go new file mode 100644 index 0000000..253e832 --- /dev/null +++ b/model/chain.go @@ -0,0 +1,54 @@ +// Copyright (c) 2023-2024 The UXUY Developer Team +// License: +// MIT License + +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: + +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. + +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +//SOFTWARE + +package model + +import "time" + +type ChainGroup string + +const ( + EvmChainGroup ChainGroup = "evm" + BtcChainGroup ChainGroup = "btc" +) + +const ( + ChainBTC string = "btc" + ChainAVAX string = "avalanche" +) + +type ChainInfo struct { + ID int64 `gorm:"primaryKey" json:"id"` + ChainId int64 `json:"chain_id" gorm:"column:chain_id"` + Chain string `json:"chain" gorm:"column:chain"` + OuterChain string `json:"outer_chain" gorm:"column:outer_chain"` + Name string `json:"name" gorm:"column:name"` + Logo string `json:"logo" gorm:"column:logo"` + NetworkId int64 `json:"network_id" gorm:"column:network_id"` + Ext string `json:"ext" gorm:"column:ext"` + CreatedAt time.Time `json:"created_at" gorm:"column:created_at"` + UpdatedAt time.Time `json:"updated_at" gorm:"column:updated_at"` +} + +func (ChainInfo) TableName() string { + return "chain_info" +} diff --git a/model/inscription.go b/model/inscription.go index fafd1a4..f0ec981 100644 --- a/model/inscription.go +++ b/model/inscription.go @@ -33,12 +33,6 @@ const ( TransferTypeBalance = 2 ) -type ChainInfo struct { - ChainId string `json:"chain_id"` - ChainName string `json:"chain_name"` - ProtocolOnChain string `json:"protocol_on_chain"` -} - type Inscriptions struct { ID uint32 `gorm:"primaryKey" json:"id"` // ID SID uint32 `json:"sid" gorm:"column:sid"` @@ -83,6 +77,15 @@ func (InscriptionsStats) TableName() string { return "inscriptions_stats" } +type AllChain struct { + Chain string `json:"chain"` + count uint32 `json:"count"` +} + +func (AllChain) TableName() string { + return "inscriptions" +} + type InscriptionOverView struct { ID uint32 `gorm:"primaryKey" json:"id"` Chain string `json:"chain" gorm:"column:chain"` @@ -101,6 +104,7 @@ type InscriptionOverView struct { Holders uint64 `json:"holders" gorm:"column:holders"` Minted decimal.Decimal `gorm:"column:minted;type:decimal(38,18)" json:"minted"` TxCnt uint64 `gorm:"column:tx_cnt" json:"tx_cnt"` + Progress decimal.Decimal `gorm:"column:progress;type:decimal(36,18)" json:"progress"` // mint进度 } type InscriptionBrief struct { diff --git a/model/stat.go b/model/stat.go new file mode 100644 index 0000000..49f342c --- /dev/null +++ b/model/stat.go @@ -0,0 +1,45 @@ +// Copyright (c) 2023-2024 The UXUY Developer Team +// License: +// MIT License + +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: + +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. + +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +//SOFTWARE + +package model + +import ( + "github.com/shopspring/decimal" + "time" +) + +type ChainStatHour struct { + ID uint64 `gorm:"primaryKey" json:"id"` + Chain string `json:"chain" gorm:"column:chain"` + DateHour uint32 `json:"date_hour" gorm:"column:date_hour"` + AddressCount uint32 `json:"address_count" gorm:"column:address_count"` + AddressLastId uint64 `json:"address_last_id" gorm:"column:address_last_id"` + InscriptionsCount uint32 `json:"inscriptions_count" gorm:"column:inscriptions_count"` + BalanceSum decimal.Decimal `json:"balance_amount_sum" gorm:"column:balance_amount_sum;type:decimal(38,18)"` + BalanceLastId uint64 `json:"balance_last_id" gorm:"column:balance_last_id"` + CreatedAt time.Time `json:"created_at" gorm:"column:created_at"` + UpdatedAt time.Time `json:"updated_at" gorm:"column:updated_at"` +} + +func (ChainStatHour) TableName() string { + return "chain_stats_hour" +} diff --git a/model/status.go b/model/status.go index 6bf3492..89aab08 100644 --- a/model/status.go +++ b/model/status.go @@ -25,6 +25,7 @@ package model import "time" type BlockStatus struct { + ChainId int64 `json:"chain_id" gorm:"column:chain_id"` Chain string `json:"chain" gorm:"column:chain"` // chain name BlockHash string `json:"block_hash" gorm:"column:block_hash"` // block hash BlockNumber uint64 `json:"block_number" gorm:"column:block_number"` // block height diff --git a/model/transaction.go b/model/transaction.go index 8583226..b1a9a0a 100644 --- a/model/transaction.go +++ b/model/transaction.go @@ -67,18 +67,18 @@ type TransactionRaw struct { } type AddressTxs struct { - ID uint64 `gorm:"primaryKey" json:"id"` - Event TxEvent `json:"event" gorm:"column:event"` - TxHash string `json:"tx_hash" gorm:"column:tx_hash"` - Address string `json:"address" gorm:"column:address"` - Amount decimal.Decimal `json:"amount" gorm:"column:amount;type:decimal(38,18)"` - Tick string `json:"tick" gorm:"column:tick"` - Protocol string `json:"protocol" gorm:"column:protocol"` - Operate string `json:"operate" gorm:"column:operate"` - //Desc string `json:"desc" gorm:"column:desc"` - Chain string `json:"chain" gorm:"column:chain"` - CreatedAt time.Time `json:"created_at" gorm:"column:created_at"` - UpdatedAt time.Time `json:"updated_at" gorm:"column:updated_at"` + ID uint64 `gorm:"primaryKey" json:"id"` + Event TxEvent `json:"event" gorm:"column:event"` + TxHash []byte `json:"tx_hash" gorm:"column:tx_hash"` + Address string `json:"address" gorm:"column:address"` + RelatedAddress string `json:"related_address" gorm:"column:related_address"` + Amount decimal.Decimal `json:"amount" gorm:"column:amount;type:decimal(38,18)"` + Tick string `json:"tick" gorm:"column:tick"` + Protocol string `json:"protocol" gorm:"column:protocol"` + Operate string `json:"operate" gorm:"column:operate"` + Chain string `json:"chain" gorm:"column:chain"` + CreatedAt time.Time `json:"created_at" gorm:"column:created_at"` + UpdatedAt time.Time `json:"updated_at" gorm:"column:updated_at"` } func (AddressTxs) TableName() string { @@ -95,7 +95,7 @@ type BalanceTxn struct { Amount decimal.Decimal `json:"amount" gorm:"column:amount;type:decimal(38,18)"` Available decimal.Decimal `json:"available" gorm:"column:available;type:decimal(38,18)"` Balance decimal.Decimal `json:"balance" gorm:"column:balance;type:decimal(38,18)"` - TxHash string `json:"tx_hash" gorm:"column:tx_hash"` + TxHash []byte `json:"tx_hash" gorm:"column:tx_hash"` CreatedAt time.Time `json:"created_at" gorm:"column:created_at"` UpdatedAt time.Time `json:"updated_at" gorm:"column:updated_at"` } @@ -105,23 +105,22 @@ func (BalanceTxn) TableName() string { } type Transaction struct { - ID uint64 `gorm:"primaryKey" json:"id"` - Chain string `json:"chain" gorm:"column:chain"` // chain name - Protocol string `json:"protocol" gorm:"column:protocol"` // protocol name - BlockHeight uint64 `json:"block_height" gorm:"column:block_height"` // block height - PositionInBlock uint64 `json:"position_in_block" gorm:"column:position_in_block"` // Position in Block - BlockTime time.Time `json:"block_time" gorm:"column:block_time"` // block time - TxHash string `json:"tx_hash" gorm:"column:tx_hash"` // tx hash - From string `json:"from" gorm:"column:from"` // from address - To string `json:"to" gorm:"column:to"` // to address - Op string `json:"op" gorm:"column:op"` // op code - Tick string `json:"tick" gorm:"column:tick"` // inscription code - Amount decimal.Decimal `json:"amt" gorm:"column:amt;type:decimal(38,18)"` // balance - Gas int64 `json:"gas" gorm:"column:gas"` // gas - GasPrice int64 `json:"gas_price" gorm:"column:gas_price"` // gas price - Status int8 `json:"status" gorm:"column:status"` // tx status - CreatedAt time.Time `json:"created_at" gorm:"column:created_at"` - UpdatedAt time.Time `json:"updated_at" gorm:"column:updated_at"` + ID uint64 `gorm:"primaryKey" json:"id"` + ChainId int64 `json:"chain_id" gorm:"-:all"` + Protocol string `json:"protocol" gorm:"column:protocol"` // protocol name + Chain string `json:"chain" gorm:"column:chain"` // chain name + BlockHeight uint64 `json:"block_height" gorm:"column:block_height"` // block height + PositionInBlock uint64 `json:"position_in_block" gorm:"column:position_in_block"` // Position in Block + BlockTime time.Time `json:"block_time" gorm:"column:block_time"` // block time + TxHash []byte `json:"tx_hash" gorm:"column:tx_hash"` // tx hash + From string `json:"from" gorm:"column:from"` // from address + To string `json:"to" gorm:"column:to"` // to address + Op string `json:"op" gorm:"column:op"` // op code + Gas int64 `json:"gas" gorm:"column:gas"` // gas + GasPrice int64 `json:"gas_price" gorm:"column:gas_price"` // gas price + Status int8 `json:"status" gorm:"column:status"` // tx status + CreatedAt time.Time `json:"created_at" gorm:"column:created_at"` + UpdatedAt time.Time `json:"updated_at" gorm:"column:updated_at"` } func (Transaction) TableName() string { @@ -131,7 +130,7 @@ func (Transaction) TableName() string { type AddressTransaction struct { ID uint64 `gorm:"primaryKey" json:"id"` Event int8 `json:"event" gorm:"column:event"` - TxHash string `json:"tx_hash" gorm:"column:tx_hash"` + TxHash []byte `json:"tx_hash" gorm:"column:tx_hash"` Address string `json:"address" gorm:"column:address"` From string `json:"from" gorm:"column:from"` To string `json:"to" gorm:"column:to"` diff --git a/model/chains.go b/protocol/evm/erc20/instance.go similarity index 81% rename from model/chains.go rename to protocol/evm/erc20/instance.go index 286999b..64d1757 100644 --- a/model/chains.go +++ b/protocol/evm/erc20/instance.go @@ -20,16 +20,19 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE //SOFTWARE -package model +package erc20 -type ChainGroup string - -const ( - EvmChainGroup ChainGroup = "evm" - BtcChainGroup ChainGroup = "btc" +import ( + "github.com/uxuycom/indexer/dcache" + "github.com/uxuycom/indexer/protocol/common" ) -const ( - ChainBTC string = "btc" - ChainAVAX string = "avalanche" -) +type Protocol struct { + *common.Protocol +} + +func NewProtocol(cache *dcache.Manager) *Protocol { + return &Protocol{ + Protocol: common.NewProtocol(cache), + } +} diff --git a/protocol/metadata.go b/protocol/metadata.go index f5dbb58..f923c8d 100644 --- a/protocol/metadata.go +++ b/protocol/metadata.go @@ -30,6 +30,7 @@ import ( "github.com/uxuycom/indexer/devents" "github.com/uxuycom/indexer/model" "github.com/uxuycom/indexer/protocol/avax/asc20" + "github.com/uxuycom/indexer/protocol/types" "strings" ) @@ -70,11 +71,6 @@ func ParseEVMMetaData(chain string, inputData string) (*devents.MetaData, error) return nil, fmt.Errorf("data seprator index failed") } - // max length limit - if len(input) > 256 { - return nil, fmt.Errorf("data character size[%d] > 256", len(input)) - } - //set parse content types contentType := "" if dataPrefixIdx > 5 { @@ -93,6 +89,17 @@ func ParseEVMMetaData(chain string, inputData string) (*devents.MetaData, error) // trim prefix / suffix spaces & case insensitive proto.Protocol = strings.ToLower(strings.TrimSpace(proto.Protocol)) + + maxDataLength := types.DefaultMaxDataLength + if value, ok := types.DefaultMaxDataLengthMap[proto.Protocol]; ok { + maxDataLength = value + } + + // max length limit + if len(input) > maxDataLength { + return nil, fmt.Errorf("data character size[%d] > 256", len(input)) + } + proto.Operate = strings.ToLower(strings.TrimSpace(proto.Operate)) proto.Tick = strings.ToLower(strings.TrimSpace(proto.Tick)) diff --git a/protocol/protocol.go b/protocol/protocol.go index 9d23ce1..4bbc44f 100644 --- a/protocol/protocol.go +++ b/protocol/protocol.go @@ -31,6 +31,7 @@ import ( "github.com/uxuycom/indexer/protocol/avax/asc20" btcBrc20 "github.com/uxuycom/indexer/protocol/btc/brc20" "github.com/uxuycom/indexer/protocol/evm/brc20" + "github.com/uxuycom/indexer/protocol/evm/erc20" "github.com/uxuycom/indexer/protocol/types" "github.com/uxuycom/indexer/storage" "github.com/uxuycom/indexer/xylog" @@ -40,12 +41,14 @@ var ( BTCBrc20Protocol *btcBrc20.Protocol EvmAsc20Protocol *asc20.Protocol EvmBrc20Protocol *brc20.Protocol + EvmErc20Protocol *erc20.Protocol ) func InitProtocols(cache *dcache.Manager) { BTCBrc20Protocol = btcBrc20.NewProtocol(cache) EvmBrc20Protocol = brc20.NewProtocol(cache) EvmAsc20Protocol = asc20.NewProtocol(cache) + EvmErc20Protocol = erc20.NewProtocol(cache) } func GetProtocol(cfg *config.Config, tx *xycommon.RpcTransaction) (types.IProtocol, *devents.MetaData) { @@ -68,6 +71,8 @@ func GetProtocol(cfg *config.Config, tx *xycommon.RpcTransaction) (types.IProtoc switch md.Protocol { case types.ASC20Protocol: return EvmAsc20Protocol, md + case types.ERC20Protocol: + return EvmErc20Protocol, md default: return EvmBrc20Protocol, md } diff --git a/protocol/types/op.go b/protocol/types/op.go index 70a38a4..5259522 100644 --- a/protocol/types/op.go +++ b/protocol/types/op.go @@ -37,4 +37,16 @@ const ( ASC20Protocol = "asc-20" BSC20Protocol = "bsc-20" PRC20Protocol = "prc-20" + ERC20Protocol = "erc-20" + + DefaultMaxDataLength = 256 ) + +// DefaultMaxDataLengthMap max data length config +var DefaultMaxDataLengthMap = map[string]int{ + BRC20Protocol: 256, + ASC20Protocol: 256, + BSC20Protocol: 256, + PRC20Protocol: 256, + ERC20Protocol: 256, +} diff --git a/storage/common.go b/storage/common.go index b299fc6..b9661c8 100644 --- a/storage/common.go +++ b/storage/common.go @@ -32,6 +32,7 @@ import ( "math/big" "reflect" "strings" + "time" ) const ( @@ -237,28 +238,28 @@ func (conn *DBClient) BatchAddTransaction(dbTx *gorm.DB, items []*model.Transact if len(items) < 1 { return nil } - return conn.CreateInBatches(dbTx, items, 1000) + return conn.CreateInBatches(dbTx, items, 5000) } func (conn *DBClient) BatchAddBalanceTx(dbTx *gorm.DB, items []*model.BalanceTxn) error { if len(items) < 1 { return nil } - return conn.CreateInBatches(dbTx, items, 1000) + return conn.CreateInBatches(dbTx, items, 5000) } func (conn *DBClient) BatchAddAddressTx(dbTx *gorm.DB, items []*model.AddressTxs) error { if len(items) < 1 { return nil } - return conn.CreateInBatches(dbTx, items, 1000) + return conn.CreateInBatches(dbTx, items, 5000) } func (conn *DBClient) BatchAddBalances(dbTx *gorm.DB, items []*model.Balances) error { if len(items) < 1 { return nil } - return conn.CreateInBatches(dbTx, items, 1000) + return conn.CreateInBatches(dbTx, items, 2000) } func (conn *DBClient) BatchUpdateBalances(dbTx *gorm.DB, chain string, items []*model.Balances) error { @@ -394,6 +395,36 @@ func (conn *DBClient) GetInscriptions(limit, offset int, chain, protocol, tick, return data, total, nil } +func (conn *DBClient) FindInscriptionInfo(chain, protocol, tick, deployHash string) (*model.InscriptionOverView, error) { + var inscription model.InscriptionOverView + result := conn.SqlDB.Model(&model.Inscriptions{}). + Select("inscriptions.*, inscriptions_stats.*, (inscriptions_stats.minted / inscriptions.total_supply) as progress"). + Joins("left join inscriptions_stats ON inscriptions.chain = inscriptions_stats.chain AND inscriptions.protocol = inscriptions_stats.protocol AND inscriptions.tick = inscriptions_stats.tick") + + if chain != "" { + result = result.Where("inscriptions.chain = ?", chain) + } + if protocol != "" { + result = result.Where("inscriptions.protocol = ?", protocol) + } + if tick != "" { + result = result.Where("inscriptions.tick = ?", tick) + } + if deployHash != "" { + result = result.Where("inscriptions.deploy_hash = ?", deployHash) + } + + err := result.First(&inscription).Error + if err != nil { + if !errors.Is(err, gorm.ErrRecordNotFound) { + return nil, nil + } + return nil, err + } + + return &inscription, nil +} + func (conn *DBClient) GetInscriptionsByIdLimit(chain string, start uint64, limit int) ([]model.Inscriptions, error) { inscriptions := make([]model.Inscriptions, 0) err := conn.SqlDB.Where("chain = ?", chain).Where("id > ?", start).Order("id asc").Limit(limit).Find(&inscriptions).Error @@ -412,6 +443,33 @@ func (conn *DBClient) GetInscriptionStatsByIdLimit(chain string, start uint64, l return stats, nil } +func (conn *DBClient) GetInscriptionStats(chain string, start uint64, limit int) ([]model.InscriptionsStats, error) { + stats := make([]model.InscriptionsStats, 0) + err := conn.SqlDB.Where("chain = ?", chain).Where("id > ?", start).Order("id asc").Limit(limit).Find(&stats).Error + if err != nil { + return nil, err + } + return stats, nil +} + +func (conn *DBClient) GetInscriptionStatsList(limit int, offset int, sort int) ([]model.InscriptionsStats, int64, error) { + stats := make([]model.InscriptionsStats, 0) + query := conn.SqlDB.Model(&model.InscriptionsStats{}) + + var total int64 + query.Count(&total) + + orderBy := " id DESC" + if sort == OrderByModeAsc { + orderBy = " id ASC" + } + err := query.Order(orderBy).Limit(limit).Offset(offset).Find(&stats).Error + if err != nil { + return nil, 0, err + } + return stats, total, nil +} + func (conn *DBClient) GetInscriptionsByAddress(limit, offset int, address string) ([]*model.Balances, error) { balances := make([]*model.Balances, 0) @@ -502,7 +560,32 @@ func (conn *DBClient) GetTxsByHashes(chain string, hashes []string) ([]*model.Tr return txs, nil } -func (conn *DBClient) GetAddressInscriptions(limit, offset int, address, chain, protocol, tick string, sort int) ( +// GetTransactions find all transaction +func (conn *DBClient) GetTransactions(address string, tick string, limit int, offset int, sort int) ([]*model.Transaction, int64, error) { + + txs := make([]*model.Transaction, 0) + query := conn.SqlDB.Model(&model.Transaction{}) + + var total int64 + if len(address) > 0 { + query = query.Where("from = ? or to = ?", address, address) + } + if len(tick) > 0 { + query = query.Where("tick = ?", tick) + } + orderBy := " id DESC" + if sort == OrderByModeAsc { + orderBy = " id ASC" + } + err := query.Order(orderBy).Limit(limit).Offset(offset).Find(&txs).Error + if err != nil { + return nil, 0, err + } + return txs, total, nil +} + +func (conn *DBClient) GetAddressInscriptions(limit, offset int, address, chain, protocol, tick string, + key string, sort int) ( []*model.BalanceInscription, int64, error) { var data []*model.BalanceInscription @@ -520,7 +603,10 @@ func (conn *DBClient) GetAddressInscriptions(limit, offset int, address, chain, query = query.Where("`b`.protocol = ?", protocol) } if tick != "" { - query = query.Where("`b`.tick like ?", "%"+tick+"%") + query = query.Where("`b`.tick = ?", tick) + } + if key != "" { + query = query.Where("`b`.tick like ?", "%"+key+"%") } query = query.Count(&total) @@ -537,13 +623,13 @@ func (conn *DBClient) GetAddressInscriptions(limit, offset int, address, chain, return data, total, nil } -func (conn *DBClient) GetBalancesByAddress(limit, offset int, address, chain, protocol, tick string) ( - []*model.Balances, int64, error) { +func (conn *DBClient) GetBalancesChainByAddress(limit, offset int, address, chain, protocol, tick string) ( + []*model.BalanceChain, int64, error) { - var balances []*model.Balances + var balances []*model.BalanceChain var total int64 - query := conn.SqlDB.Model(&model.Balances{}).Where("`address` = ?", address) + query := conn.SqlDB.Select("chain,address,SUM(balance) as balance").Table("balances").Where("`address` = ?", address) if chain != "" { query = query.Where("`chain` = ?", chain) } @@ -554,7 +640,9 @@ func (conn *DBClient) GetBalancesByAddress(limit, offset int, address, chain, pr query = query.Where("`tick` = ?", tick) } query = query.Count(&total) - err := query.Limit(limit).Offset(offset).Find(&balances).Error + orderBy := "balance DESC" + groupBy := "chain" + err := query.Group(groupBy).Order(orderBy).Limit(limit).Offset(offset).Find(&balances).Error if err != nil { return nil, 0, err } @@ -632,6 +720,25 @@ func (conn *DBClient) FindAddressTxByHash(chain, hash string) (*model.AddressTxs return tx, nil } +func (conn *DBClient) FindBalanceByTxHash(hash string) ([]*model.BalanceTxn, error) { + balances := make([]*model.BalanceTxn, 0) + err := conn.SqlDB.Model(&model.BalanceTxn{}).Where("tx_hash = ? ", hash).Find(balances).Error + if err != nil { + return nil, err + } + return balances, nil +} + +// GetAllChainFromBlock query all chains from block table +func (conn *DBClient) GetAllChainFromBlock() ([]string, error) { + var chains []string + err := conn.SqlDB.Model(&model.Block{}).Distinct().Pluck("chain", &chains).Error + if err != nil { + return nil, err + } + return chains, nil +} + func (conn *DBClient) FindLastBlock(chain string) (*model.Block, error) { data := &model.Block{} err := conn.SqlDB.First(data, "chain = ? ", chain).Error @@ -659,3 +766,71 @@ func (conn *DBClient) FindInscriptionsStatsByTick(chain string, protocol string, return inscriptionStats, nil } + +func (conn *DBClient) FindAllChain() ([]*model.AllChain, error) { + allChain := make([]*model.AllChain, 0) + query := conn.SqlDB.Select("chain,count(*) as count").Table("inscriptions") + groupBy := "chain" + err := query.Group(groupBy).Find(&allChain).Error + if err != nil { + return nil, nil + } + return allChain, nil +} + +func (conn *DBClient) FindLastChainStatHourByChainAndDateHour(chain string, dateHour uint32) (*model.ChainStatHour, error) { + data := &model.ChainStatHour{} + err := conn.SqlDB.Last(data, "chain = ?", chain).Where("date_hour = ?", dateHour).Error + if err != nil { + return nil, err + } + return data, nil +} + +func (conn *DBClient) FindAddressTxByIdAndChainAndLimit(chain string, start uint64, limit int) ([]model.AddressTxs, error) { + txs := make([]model.AddressTxs, 0) + err := conn.SqlDB.Where("id > ?", start).Where("chain = ?", chain).Order("id asc").Limit(limit).Find(&txs).Error + if err != nil { + return nil, err + } + return txs, nil +} + +func (conn *DBClient) FindInscriptionsTxByIdAndChainAndLimit(chain string, nowHour, lastHour time.Time) ([]model.Inscriptions, error) { + inscriptions := make([]model.Inscriptions, 0) + err := conn.SqlDB.Where("chain = ?", chain).Where("deploy_time > ? and deploy_time < ?", lastHour, nowHour).Find(&inscriptions).Error + if err != nil { + return nil, err + } + return inscriptions, nil +} + +func (conn *DBClient) FindBalanceTxByIdAndChainAndLimit(chain string, balanceIndex uint64, limit int) ([]model.BalanceTxn, error) { + balances := make([]model.BalanceTxn, 0) + err := conn.SqlDB.Where("id > ?", balanceIndex).Where("chain = ?", chain).Where("amount > 0").Order("id asc").Limit(limit).Find(&balances).Error + if err != nil { + return nil, err + } + return balances, nil +} +func (conn *DBClient) AddChainStatHour(chainStatHour *model.ChainStatHour) error { + return conn.SqlDB.Create(chainStatHour).Error +} + +func (conn *DBClient) GetAllChainInfo() ([]model.ChainInfo, error) { + chains := make([]model.ChainInfo, 0) + err := conn.SqlDB.Model(&model.ChainInfo{}).Find(&chains).Error + if err != nil { + return nil, err + } + return chains, nil +} + +func (conn *DBClient) GroupChainStatHourBy24Hour(startHour, endHour uint32) ([]model.ChainStatHour, error) { + stats := make([]model.ChainStatHour, 0) + err := conn.SqlDB.Where("date_hour >= ? and date_hour <= ?", startHour, endHour).Find(&stats).Error + if err != nil { + return nil, err + } + return stats, nil +} diff --git a/task/chain_stats_hour_task.go b/task/chain_stats_hour_task.go new file mode 100644 index 0000000..612a59f --- /dev/null +++ b/task/chain_stats_hour_task.go @@ -0,0 +1,155 @@ +// Copyright (c) 2023-2024 The UXUY Developer Team +// License: +// MIT License + +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: + +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. + +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +//SOFTWARE + +package task + +import ( + "github.com/shopspring/decimal" + "github.com/uxuycom/indexer/config" + "github.com/uxuycom/indexer/model" + "github.com/uxuycom/indexer/storage" + "github.com/uxuycom/indexer/xylog" + "strconv" + "sync" + "time" +) + +type ChainStatsTask struct { + Task +} + +const ( + limit = 5000 +) + +func NewChainStatsTask(dbc *storage.DBClient, cfg *config.Config) *ChainStatsTask { + task := &ChainStatsTask{ + Task{ + dbc: dbc, + cfg: cfg, + }, + } + return task +} + +func (t *ChainStatsTask) Exec() { + ticker := time.NewTicker(time.Hour) + defer ticker.Stop() + xylog.Logger.Infof("task starting...") + for { + select { + case <-ticker.C: + xylog.Logger.Infof("Exec ChainStatsTask task!") + chain := t.cfg.Chain.ChainName + // get current hour and last hour + //now, _ := time.ParseInLocation("2006-01-02 15:04:05", "2024-02-21 20:00:00", time.Local) + now := time.Now() + nowHour := now.Truncate(time.Hour) + lastHour := now.Add(-1 * time.Hour).Truncate(time.Hour) + format := lastHour.Format("2006010215") + parseUint, _ := strconv.ParseUint(format, 10, 32) + chainStatHour := &model.ChainStatHour{ + AddressCount: 0, + InscriptionsCount: 0, + BalanceSum: decimal.NewFromInt(0), + Chain: chain, + DateHour: uint32(parseUint), + } + u, _ := strconv.ParseUint(lastHour.Add(-1*time.Hour).Truncate(time.Hour).Format("2006010215"), 10, 32) + chainStat, _ := t.dbc.FindLastChainStatHourByChainAndDateHour(chain, uint32(u)) + if chainStat == nil { + // first stat + chainStat = &model.ChainStatHour{ + AddressLastId: t.cfg.Stat.AddressStartId, + BalanceLastId: t.cfg.Stat.BalanceStartId, + Chain: chain, + } + } + + var wg sync.WaitGroup + wg.Add(2) + go func() { + // address + HandleAddress(t, chainStat, chainStatHour, nowHour, lastHour) + defer wg.Done() + }() + + go func() { + // balance + HandleBalance(t, chainStat, chainStatHour, nowHour, lastHour) + defer wg.Done() + }() + wg.Wait() + + // inscriptions + inscriptions, _ := t.dbc.FindInscriptionsTxByIdAndChainAndLimit(chainStat.Chain, nowHour, lastHour) + chainStatHour.InscriptionsCount = uint32(len(inscriptions)) + // add stat + err := t.dbc.AddChainStatHour(chainStatHour) + if err != nil { + xylog.Logger.Errorf("AddChainStatHour error: %v chainStatHour: %v", err, chainStatHour) + } + } + + } +} +func HandleAddress(t *ChainStatsTask, chainStat, chainStatHour *model.ChainStatHour, + nowHour, lastHour time.Time) *model.ChainStatHour { + + addresses, _ := t.dbc.FindAddressTxByIdAndChainAndLimit(chainStat.Chain, chainStat.AddressLastId, limit) + if len(addresses) > 0 { + for _, a := range addresses { + if a.CreatedAt.After(nowHour) { + return chainStatHour + } + if a.CreatedAt.Before(nowHour) && a.CreatedAt.After(lastHour) { + chainStatHour.AddressCount++ + chainStatHour.AddressLastId = a.ID + } + chainStat.AddressLastId = a.ID + } + return HandleAddress(t, chainStat, chainStatHour, nowHour, lastHour) + } else { + return chainStatHour + } +} + +func HandleBalance(t *ChainStatsTask, chainStat, chainStatHour *model.ChainStatHour, + nowHour, lastHour time.Time) *model.ChainStatHour { + + balances, _ := t.dbc.FindBalanceTxByIdAndChainAndLimit(chainStat.Chain, chainStat.BalanceLastId, limit) + if len(balances) > 0 { + for _, b := range balances { + if b.CreatedAt.After(nowHour) { + return chainStatHour + } + if b.CreatedAt.Before(nowHour) && b.CreatedAt.After(lastHour) { + chainStatHour.BalanceSum = chainStatHour.BalanceSum.Add(b.Amount) + chainStatHour.BalanceLastId = b.ID + } + chainStat.BalanceLastId = b.ID + } + return HandleBalance(t, chainStat, chainStatHour, nowHour, lastHour) + } else { + return chainStatHour + } +} diff --git a/task/task.go b/task/task.go new file mode 100644 index 0000000..87abf50 --- /dev/null +++ b/task/task.go @@ -0,0 +1,55 @@ +// Copyright (c) 2023-2024 The UXUY Developer Team +// License: +// MIT License + +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: + +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. + +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE + +package task + +import ( + "github.com/uxuycom/indexer/config" + "github.com/uxuycom/indexer/storage" + "github.com/uxuycom/indexer/xylog" +) + +type Task struct { + dbc *storage.DBClient + cfg *config.Config + tasks map[string]interface{} +} + +type ITask interface { + Exec() +} + +func InitTask(dbc *storage.DBClient, cfg *config.Config) *Task { + + task := &Task{ + tasks: map[string]interface{}{ + "chain_stats_tak": NewChainStatsTask(dbc, cfg), // add new task here + }, + } + + for k, v := range task.tasks { + xylog.Logger.Infof("tasks %v start!", k) + t := v.(ITask) + go t.Exec() + } + return task +}