diff --git a/api/account_api.go b/api/account_api.go new file mode 100644 index 0000000..eaa6930 --- /dev/null +++ b/api/account_api.go @@ -0,0 +1,44 @@ +package api + +import ( + "github.com/ethereum/go-ethereum/common" + "github.com/gin-gonic/gin" + "github.com/pkg/errors" + "github.com/shopspring/decimal" + "github.com/sirupsen/logrus" +) + +func getAccountInfo(c *gin.Context) (interface{}, error) { + addressInfo, err := getAddressInfo(c) + if err != nil { + return nil, err + } + + addr := common.HexToAddress(addressInfo.address) + balance, err := sdk.Eth.Balance(addr, nil) + if err != nil { + logrus.WithError(err).WithField("address", addressInfo.address).Error("Failed to get balance") + return nil, errors.Errorf("Get balance error, address %v", addressInfo.address) + } + + submitStat, err := db.AddressSubmitStore.Count(&addressInfo.addressId) + if err != nil { + return nil, err + } + + rewardStat, err := db.AddressRewardStore.Count(&addressInfo.addressId) + if err != nil { + return nil, err + } + + accountInfo := AccountInfo{ + Balance: decimal.NewFromBigInt(balance, 0), + FileCount: submitStat.FileCount, + TxCount: submitStat.TxCount, + DataSize: submitStat.DataSize, + StorageFee: submitStat.BaseFee, + RewardCount: rewardStat.RewardCount, + } + + return accountInfo, nil +} diff --git a/api/api.go b/api/api.go index fa4e9c8..b321865 100644 --- a/api/api.go +++ b/api/api.go @@ -5,9 +5,11 @@ import ( "github.com/0glabs/0g-storage-scan/docs" "github.com/0glabs/0g-storage-scan/store" "github.com/Conflux-Chain/go-conflux-util/api" + commonApi "github.com/Conflux-Chain/go-conflux-util/api" viperUtil "github.com/Conflux-Chain/go-conflux-util/viper" "github.com/gin-gonic/gin" "github.com/openweb3/web3go" + "github.com/pkg/errors" "github.com/sirupsen/logrus" swaggerFiles "github.com/swaggo/files" ginSwagger "github.com/swaggo/gin-swagger" @@ -82,6 +84,7 @@ func RegisterRouter(router *gin.Engine) { rewardsRoute.GET("", listRewardsHandler) accountsRoute := apiRoute.Group("/accounts") + accountsRoute.GET(":address", getAccountInfoHandler) accountsRoute.GET(":address/txs", listAddressTxsHandler) accountsRoute.GET(":address/rewards", listAddressRewardsHandler) @@ -208,6 +211,21 @@ func listRewardsHandler(c *gin.Context) { api.Wrap(listStorageRewards)(c) } +// getAccountInfoHandler godoc +// +// @Summary Account's information +// @Description Query account information for specified account +// @Tags account +// @Accept json +// @Produce json +// @Param address path string false "The account address" +// @Success 200 {object} api.BusinessError{Data=AccountInfo} +// @Failure 600 {object} api.BusinessError +// @Router /accounts/{address} [get] +func getAccountInfoHandler(c *gin.Context) { + api.Wrap(getAccountInfo)(c) +} + // listAddressTxsHandler godoc // // @Summary Account's storage transaction list @@ -242,3 +260,21 @@ func listAddressTxsHandler(c *gin.Context) { func listAddressRewardsHandler(c *gin.Context) { api.Wrap(listAddressStorageRewards)(c) } + +func getAddressInfo(c *gin.Context) (*AddressInfo, error) { + address := c.Param("address") + if address == "" { + logrus.Error("Failed to parse nil address") + return nil, errors.Errorf("Biz error, nil address %v", address) + } + + addressInfo, exist, err := db.AddressStore.Get(address) + if err != nil { + return nil, commonApi.ErrInternal(err) + } + if !exist { + return nil, ErrAddressNotFound + } + + return &AddressInfo{address, addressInfo.ID}, nil +} diff --git a/api/reward_api.go b/api/reward_api.go index bd50efd..afa283b 100644 --- a/api/reward_api.go +++ b/api/reward_api.go @@ -2,10 +2,7 @@ package api import ( "github.com/0glabs/0g-storage-scan/store" - commonApi "github.com/Conflux-Chain/go-conflux-util/api" "github.com/gin-gonic/gin" - "github.com/pkg/errors" - "github.com/sirupsen/logrus" ) func listStorageRewards(c *gin.Context) (interface{}, error) { @@ -22,19 +19,11 @@ func listStorageRewards(c *gin.Context) (interface{}, error) { return convertStorageRewards(total, rewards) } func listAddressStorageRewards(c *gin.Context) (interface{}, error) { - address := c.Param("address") - if address == "" { - logrus.Error("Failed to parse nil address") - return nil, errors.Errorf("Biz error, nil address %v", address) - } - addr, exist, err := db.AddressStore.Get(address) + addressInfo, err := getAddressInfo(c) if err != nil { - return nil, commonApi.ErrInternal(err) - } - if !exist { - return RewardList{}, nil + return nil, err } - addrIDPtr := &addr.ID + addrIDPtr := &addressInfo.addressId var param PageParam if err := c.ShouldBind(¶m); err != nil { @@ -87,7 +76,6 @@ func convertStorageRewards(total int64, rewards []store.Reward) (*RewardList, er storageRewards := make([]Reward, 0) for _, r := range rewards { storageReward := Reward{ - RewardSeq: r.PricingIndex, Miner: addrMap[r.MinerID].Address, Amount: r.Amount, BlockNumber: r.BlockNumber, diff --git a/api/tx_api.go b/api/tx_api.go index 067268e..c94e467 100644 --- a/api/tx_api.go +++ b/api/tx_api.go @@ -5,9 +5,6 @@ import ( "strconv" "github.com/0glabs/0g-storage-client/core" - - commonApi "github.com/Conflux-Chain/go-conflux-util/api" - "github.com/0glabs/0g-storage-scan/store" "github.com/ethereum/go-ethereum/common" "github.com/gin-gonic/gin" @@ -96,19 +93,11 @@ func getStorageTx(c *gin.Context) (interface{}, error) { } func listAddressStorageTxs(c *gin.Context) (interface{}, error) { - address := c.Param("address") - if address == "" { - logrus.Error("Failed to parse nil address") - return nil, errors.Errorf("Biz error, nil address %v", address) - } - addr, exist, err := db.AddressStore.Get(address) + addressInfo, err := getAddressInfo(c) if err != nil { - return nil, commonApi.ErrInternal(err) - } - if !exist { - return StorageTxList{}, nil + return nil, err } - addrIDPtr := &addr.ID + addrIDPtr := &addressInfo.addressId var param listAddressStorageTxParam if err := c.ShouldBind(¶m); err != nil { diff --git a/api/types.go b/api/types.go index 36d5c65..ea40e4b 100644 --- a/api/types.go +++ b/api/types.go @@ -5,7 +5,6 @@ import ( "time" "github.com/0glabs/0g-storage-scan/stat" - "github.com/shopspring/decimal" ) @@ -166,10 +165,25 @@ type RewardList struct { // Reward model info // @Description Reward information type Reward struct { - RewardSeq uint64 `json:"rewardSeq"` // Pricing index for reward Miner string `json:"miner"` // Miner address Amount decimal.Decimal `json:"amount"` // The reward amount BlockNumber uint64 `json:"blockNumber"` // The block where the reward event is emitted TxHash string `json:"txHash"` // The transaction where the reward event is emitted Timestamp int64 `json:"timestamp"` // The block time when reward event emits } + +type AddressInfo struct { + address string + addressId uint64 +} + +type AccountInfo struct { + Balance decimal.Decimal `json:"balance"` // The balance in layer 1 + + FileCount uint64 `json:"fileCount"` // Total number of files + TxCount uint64 `json:"txCount"` // Total number of layer1 transaction + DataSize uint64 `json:"dataTotal"` // Total Size of storage data + StorageFee decimal.Decimal `json:"storageFeeTotal"` // Total storage fee + + RewardCount uint64 `json:"rewardCount"` // Total number of distributed reward recodes +} diff --git a/store/store_address_reward.go b/store/store_address_reward.go index 41793b3..11edda0 100644 --- a/store/store_address_reward.go +++ b/store/store_address_reward.go @@ -4,17 +4,18 @@ import ( "time" "github.com/Conflux-Chain/go-conflux-util/store/mysql" + "github.com/pkg/errors" "github.com/shopspring/decimal" "gorm.io/gorm" ) type AddressReward struct { MinerID uint64 `gorm:"primaryKey;autoIncrement:false"` - PricingIndex uint64 `gorm:"primaryKey;autoIncrement:false"` - Amount decimal.Decimal `gorm:"type:decimal(65);not null"` - BlockNumber uint64 `gorm:"not null;index:idx_bn"` + BlockNumber uint64 `gorm:"primaryKey;autoIncrement:false;index:idx_bn"` BlockTime time.Time `gorm:"not null"` TxHash string `gorm:"size:66;not null"` + PricingIndex uint64 `gorm:"not null"` + Amount decimal.Decimal `gorm:"type:decimal(65);not null"` } func (AddressReward) TableName() string { @@ -40,11 +41,13 @@ func (ars *AddressRewardStore) Pop(dbTx *gorm.DB, block uint64) error { } func (ars *AddressRewardStore) List(addressID *uint64, idDesc bool, skip, limit int) (int64, []AddressReward, error) { + if addressID == nil { + return 0, nil, errors.New("nil addressID") + } + dbRaw := ars.DB.Model(&AddressReward{}) var conds []func(db *gorm.DB) *gorm.DB - if addressID != nil { - conds = append(conds, MinerID(*addressID)) - } + conds = append(conds, MinerID(*addressID)) dbRaw.Scopes(conds...) var orderBy string @@ -62,3 +65,25 @@ func (ars *AddressRewardStore) List(addressID *uint64, idDesc bool, skip, limit return total, *list, nil } + +type RewardStatResult struct { + RewardCount uint64 + RewardAmount decimal.Decimal +} + +func (ars *AddressRewardStore) Count(addressID *uint64) (*RewardStatResult, error) { + if addressID == nil { + return nil, errors.New("nil addressID") + } + + var result RewardStatResult + err := ars.DB.Model(&AddressReward{}). + Select(`count(*) as reward_count,IFNULL(sum(amount), 0) as reward_amount`). + Where("miner_id = ?", addressID). + Find(&result).Error + if err != nil { + return nil, err + } + + return &result, nil +} diff --git a/store/store_address_submit.go b/store/store_address_submit.go index a9f350e..1c2ca8d 100644 --- a/store/store_address_submit.go +++ b/store/store_address_submit.go @@ -4,6 +4,7 @@ import ( "time" "github.com/Conflux-Chain/go-conflux-util/store/mysql" + "github.com/pkg/errors" "github.com/shopspring/decimal" "gorm.io/gorm" ) @@ -62,12 +63,13 @@ func (ass *AddressSubmitStore) UpdateByPrimaryKey(dbTx *gorm.DB, s *AddressSubmi func (ass *AddressSubmitStore) List(addressID *uint64, rootHash *string, idDesc bool, skip, limit int) (int64, []AddressSubmit, error) { - dbRaw := ass.DB.Model(&AddressSubmit{}) + if addressID == nil { + return 0, nil, errors.New("nil addressID") + } + dbRaw := ass.DB.Model(&AddressSubmit{}) var conds []func(db *gorm.DB) *gorm.DB - if addressID != nil { - conds = append(conds, SenderID(*addressID)) - } + conds = append(conds, SenderID(*addressID)) if rootHash != nil { conds = append(conds, RootHash(*rootHash)) } @@ -88,3 +90,19 @@ func (ass *AddressSubmitStore) List(addressID *uint64, rootHash *string, idDesc return total, *list, nil } + +func (ass *AddressSubmitStore) Count(addressID *uint64) (*SubmitStatResult, error) { + if addressID == nil { + return nil, errors.New("nil addressID") + } + + var result SubmitStatResult + err := ass.DB.Model(&AddressSubmit{}).Select(`count(submission_index) as file_count, + IFNULL(sum(length), 0) as data_size, IFNULL(sum(fee), 0) as base_fee, count(distinct tx_hash) as tx_count`). + Where("sender_id = ?", addressID).Find(&result).Error + if err != nil { + return nil, err + } + + return &result, nil +} diff --git a/store/store_reward.go b/store/store_reward.go index 1b1e19f..644238c 100644 --- a/store/store_reward.go +++ b/store/store_reward.go @@ -11,13 +11,13 @@ import ( ) type Reward struct { - PricingIndex uint64 `gorm:"primaryKey;autoIncrement:false"` + BlockNumber uint64 `gorm:"primaryKey;autoIncrement:false"` + BlockTime time.Time `gorm:"not null"` + TxHash string `gorm:"size:66;not null"` Miner string `gorm:"-"` MinerID uint64 `gorm:"not null"` + PricingIndex uint64 `gorm:"not null"` Amount decimal.Decimal `gorm:"type:decimal(65);not null"` - BlockNumber uint64 `gorm:"not null;index:idx_bn"` - BlockTime time.Time `gorm:"not null"` - TxHash string `gorm:"size:66;not null"` } func NewReward(blockTime time.Time, log types.Log, filter *nhContract.OnePoolRewardFilterer) (*Reward, error) { @@ -65,9 +65,9 @@ func (rs *RewardStore) List(idDesc bool, skip, limit int) (int64, []Reward, erro var orderBy string if idDesc { - orderBy = "pricing_index DESC" + orderBy = "block_number DESC" } else { - orderBy = "pricing_index ASC" + orderBy = "block_number ASC" } list := new([]Reward)