diff --git a/nft_http/controllers/explorer_controller.go b/nft_http/controllers/explorer_controller.go new file mode 100644 index 00000000..1d83845d --- /dev/null +++ b/nft_http/controllers/explorer_controller.go @@ -0,0 +1,158 @@ +package controllers + +import ( + "fmt" + "github.com/astaxie/beego/logs" + "github.com/ethereum/go-ethereum/common" + "math/big" + "poly-bridge/models" + + "github.com/astaxie/beego" +) + +type ExplorerController struct { + beego.Controller +} + +func (c *ExplorerController) Transactions() { + var req TransactionBriefsReq + if !input(&c.Controller, &req) { + return + } + + relations := make([]*TransactionBriefRelation, 0) + limit := req.PageSize + offset := req.PageSize * req.PageNo + db.Raw("select wp.*, tr.amount as token_id, tr.asset as src_asset "+ + "from wrapper_transactions wp "+ + "left join src_transfers as tr on wp.hash=tr.tx_hash "+ + "where wp.standard=? "+ + "order by wp.time desc "+ + "limit ? offset ?", models.TokenTypeErc721, limit, offset). + Find(&relations) + + transactionNum := txCounter.Number() + totalPage := (int(transactionNum) + req.PageSize - 1) / req.PageSize + totalCnt := int(transactionNum) + + list := make([]*TransactionBriefRsp, 0) + for _, v := range relations { + tk := selectNFTAsset(v.SrcAsset) + data := new(TransactionBriefRsp).instance(tk.TokenBasicName, v) + list = append(list, data) + } + + resp := new(TransactionBriefsRsp).instance(req.PageSize, req.PageNo, totalPage, totalCnt, list) + output(&c.Controller, resp) +} + +func (c *ExplorerController) TransactionsOfAddress() { + var req TransactionBriefsOfAddressReq + if !input(&c.Controller, &req) { + return + } + + relations := make([]*TransactionBriefRelation, 0) + limit := req.PageSize + offset := req.PageSize * req.PageNo + db.Raw("select wp.*, tr.amount as token_id, tr.asset as src_asset "+ + "from wrapper_transactions wp "+ + "left join src_transfers as tr on wp.hash=tr.tx_hash "+ + "where wp.standard=? and (wp.user in ? or wp.dst_user in ?) "+ + "order by wp.time desc "+ + "limit ? offset ?", + models.TokenTypeErc721, req.Addresses, req.Addresses, limit, offset). + Find(&relations) + + var transactionNum int64 + db.Model(&models.SrcTransfer{}). + Joins("inner join wrapper_transactions on src_transfers.tx_hash = wrapper_transactions.hash"). + Where("src_transfers.standard = ? and (`from` in ? or src_transfers.dst_user in ?)", models.TokenTypeErc721, req.Addresses, req.Addresses). + Count(&transactionNum) + totalPage := (int(transactionNum) + req.PageSize - 1) / req.PageSize + totalCnt := int(transactionNum) + + list := make([]*TransactionBriefRsp, 0) + for _, v := range relations { + tk := selectNFTAsset(v.SrcAsset) + data := new(TransactionBriefRsp).instance(tk.TokenBasicName, v) + list = append(list, data) + } + + resp := new(TransactionBriefsRsp).instance(req.PageSize, req.PageNo, totalPage, totalCnt, list) + output(&c.Controller, resp) +} + +func (c *ExplorerController) TransactionDetail() { + var req TransactionDetailReq + if !input(&c.Controller, &req) { + return + } + + relation := new(TransactionDetailRelation) + res := db.Table("src_transactions"). + Select("src_transactions.hash as src_hash, poly_transactions.hash as poly_hash, dst_transactions.hash as dst_hash, src_transactions.chain_id as chain_id, src_transfers.asset as token_hash"). + Where("src_transactions.hash = ?", req.Hash). + Joins("left join src_transfers on src_transactions.hash = src_transfers.tx_hash"). + Joins("left join poly_transactions on src_transactions.hash = poly_transactions.src_hash"). + Joins("left join dst_transactions on poly_transactions.hash = dst_transactions.poly_hash"). + Preload("WrapperTransaction"). + Preload("SrcTransaction"). + Preload("SrcTransaction.SrcTransfer"). + Preload("PolyTransaction"). + Preload("DstTransaction"). + Preload("DstTransaction.DstTransfer"). + Order("src_transactions.time desc"). + Find(relation) + + if res.RowsAffected == 0 { + output(&c.Controller, nil) + return + } + + data := new(TransactionDetailRsp).instance(relation) + fillMetaInfo(data) + + output(&c.Controller, data) +} + +func fillMetaInfo(data *TransactionDetailRsp) { + if data.Transaction == nil { + fmt.Println("----------1") + return + } + + chainId := data.Transaction.SrcChainId + sdk := selectNode(chainId) + wrapper := selectWrapper(chainId) + if wrapper == emptyAddr { + return + } + if data.SrcTransaction == nil { + return + } + + nftAsset := selectNFTAsset(data.SrcTransaction.AssetHash) + tokenId, ok := string2Big(data.Transaction.TokenId) + if !ok { + return + } + nftAddr := common.HexToAddress(nftAsset.Hash) + urlList, err := sdk.GetTokensById(wrapper, nftAddr, []*big.Int{tokenId}) + if err != nil { + logs.Error("fillMetaInfo err: %v", err) + return + } + if len(urlList) == 0 { + return + } + url := urlList[tokenId] + + item, err := getProfileItemWithTokenId(nftAsset.TokenBasicName, tokenId, url) + if err != nil { + logs.Error("fillMetaInfo err: %v", err) + return + } + + data.Meta = item +} diff --git a/nft_http/controllers/item_controller.go b/nft_http/controllers/item_controller.go index e7b23995..49726392 100644 --- a/nft_http/controllers/item_controller.go +++ b/nft_http/controllers/item_controller.go @@ -90,11 +90,12 @@ func (c *ItemController) Items() { empty() return } - profile, err := fetcher.Fetch(nftAsset.TokenBasicName, &mcm.FetchRequestParams{ - TokenId: models.NewBigInt(tokenId), - Url: url, - }) - item := new(Item).instance(nftAsset.TokenBasicName, tokenId, profile) + item, err := getProfileItemWithTokenId(nftAsset.TokenBasicName, tokenId, url) + if err != nil { + logs.Error("getProfileWithTokenId err: %v", err) + empty() + return + } data := new(ItemsOfAddressRsp).instance(req.PageSize, req.PageNo, totalPage, totalCnt, []*Item{item}) output(&c.Controller, data) return @@ -120,6 +121,18 @@ func (c *ItemController) Items() { output(&c.Controller, data) } +func getProfileItemWithTokenId(assetName string, tokenId *big.Int, url string) (*Item, error) { + profile, err := fetcher.Fetch(assetName, &mcm.FetchRequestParams{ + TokenId: models.NewBigInt(tokenId), + Url: url, + }) + if err != nil { + return nil, err + } + item := new(Item).instance(assetName, tokenId, profile) + return item, nil +} + func getProfileItemsWithChainData(data map[*big.Int]string, nftAsset *models.Token) []*Item { assetName := nftAsset.TokenBasicName profileReqs := make([]*mcm.FetchRequestParams, 0) @@ -168,4 +181,4 @@ func getProfileItemsWithChainData(data map[*big.Int]string, nftAsset *models.Tok func string2Big(str string) (*big.Int, bool) { return new(big.Int).SetString(str, 10) -} \ No newline at end of file +} diff --git a/nft_http/controllers/params.go b/nft_http/controllers/params.go index c54206e9..89250691 100644 --- a/nft_http/controllers/params.go +++ b/nft_http/controllers/params.go @@ -230,37 +230,198 @@ func (s *WrapperTransactionRsp) instance(transaction *models.WrapperTransaction) return s } -type WrapperTransactionsReq struct { +type TransactionBriefsReq struct { PageSize int PageNo int } -type WrapperTransactionsRsp struct { +type TransactionBriefsOfAddressReq struct { + PageSize int + PageNo int + Addresses []string +} + +type TransactionBriefRelation struct { + models.WrapperTransaction + SrcAsset string + TokenId string +} + +type TransactionBriefRsp struct { + Hash string + Status uint64 + BlockHeight uint64 + SrcChainId uint64 + DstChainId uint64 + Time uint64 + TokenId string + AssetName string + From string + To string +} + +func (s *TransactionBriefRsp) instance(assetName string, r *TransactionBriefRelation) *TransactionBriefRsp { + s.AssetName = assetName + s.Hash = r.Hash + s.Status = r.Status + s.BlockHeight = r.BlockHeight + s.SrcChainId = r.SrcChainId + s.DstChainId = r.DstChainId + s.Time = r.Time + s.TokenId = r.TokenId + s.From = r.User + s.To = r.DstUser + return s +} + +type TransactionBriefsRsp struct { PageSize int PageNo int TotalPage int TotalCount int - Transactions []*WrapperTransactionRsp + Transactions []*TransactionBriefRsp } -func (s *WrapperTransactionsRsp) instance( +func (s *TransactionBriefsRsp) instance( pageSize, pageNo, totalPage, totalCount int, - transactions []*models.WrapperTransaction, -) *WrapperTransactionsRsp { + txs []*TransactionBriefRsp, +) *TransactionBriefsRsp { s.PageSize = pageSize s.PageNo = pageNo - s.TotalCount = totalCount s.TotalPage = totalPage - s.Transactions = make([]*WrapperTransactionRsp, 0) + s.TotalCount = totalCount + s.Transactions = txs + return s +} - if transactions == nil { - return s +type TransactionDetailRelation struct { + SrcHash string + WrapperTransaction *models.WrapperTransaction `gorm:"foreignKey:SrcHash;references:Hash"` + SrcTransaction *models.SrcTransaction `gorm:"foreignKey:SrcHash;references:Hash"` + PolyHash string + PolyTransaction *models.PolyTransaction `gorm:"foreignKey:PolyHash;references:Hash"` + DstHash string + DstTransaction *models.DstTransaction `gorm:"foreignKey:DstHash;references:Hash"` +} + +type SideChainRsp struct { + Hash string + ChainId uint64 + Asset string + AssetHash string + BlockHeight uint64 + Time uint64 + Fee string + Status uint64 + From string + To string +} + +type PolyChainRsp struct { + Hash string + Time uint64 + BlockHeight uint64 + Status uint64 + From string + To string +} + +type TransactionDetailReq struct { + Hash string +} + +type TransactionDetailRsp struct { + Transaction *TransactionBriefRsp + SrcTransaction *SideChainRsp + DstTransaction *SideChainRsp + PolyTransaction *PolyChainRsp + Meta *Item +} + +func (s *TransactionDetailRsp) instance(r *TransactionDetailRelation) *TransactionDetailRsp { + if r == nil { + return nil + } + + s.Transaction = new(TransactionBriefRsp) + s.SrcTransaction = new(SideChainRsp) + s.DstTransaction = new(SideChainRsp) + s.PolyTransaction = new(PolyChainRsp) + + s.Transaction.Hash = r.WrapperTransaction.Hash + s.Transaction.Status = r.WrapperTransaction.Status + s.Transaction.BlockHeight = r.WrapperTransaction.BlockHeight + s.Transaction.SrcChainId = r.WrapperTransaction.SrcChainId + s.Transaction.DstChainId = r.WrapperTransaction.DstChainId + s.Transaction.Time = r.WrapperTransaction.Time + s.Transaction.From = r.WrapperTransaction.User + s.Transaction.To = r.WrapperTransaction.DstUser + + + if r.SrcTransaction != nil { + + if r.SrcTransaction.SrcTransfer != nil { + token := selectNFTAsset(r.SrcTransaction.SrcTransfer.Asset) + + s.SrcTransaction.From = r.SrcTransaction.SrcTransfer.From + s.SrcTransaction.To = r.SrcTransaction.SrcTransfer.To + s.SrcTransaction.Asset = token.TokenBasicName + s.SrcTransaction.AssetHash = token.Hash + s.Transaction.AssetName = token.TokenBasicName + s.Transaction.TokenId = r.SrcTransaction.SrcTransfer.Amount.String() + } + + s.SrcTransaction.Hash = r.SrcTransaction.Hash + s.SrcTransaction.BlockHeight = r.SrcTransaction.Height + s.SrcTransaction.Time = r.SrcTransaction.Time + s.SrcTransaction.Status = r.SrcTransaction.State + s.SrcTransaction.ChainId = r.SrcTransaction.ChainId + + feeToken := feeTokens[s.SrcTransaction.ChainId] + precision := decimal.NewFromInt(basedef.Int64FromFigure(int(feeToken.TokenBasic.Precision))) + { + bbb := decimal.NewFromBigInt(&r.SrcTransaction.Fee.Int, 0) + feeAmount := bbb.Div(precision) + s.SrcTransaction.Fee = feeAmount.String() + } + } + + + if r.DstTransaction != nil { + if r.DstTransaction.DstTransfer != nil { + token := selectNFTAsset(r.DstTransaction.DstTransfer.Asset) + + s.DstTransaction.From = r.DstTransaction.DstTransfer.From + s.DstTransaction.To = r.DstTransaction.DstTransfer.To + s.DstTransaction.Asset = token.TokenBasicName + s.DstTransaction.AssetHash = token.Hash + + s.Transaction.AssetName = token.TokenBasicName + } + + s.DstTransaction.Hash = r.DstTransaction.Hash + s.DstTransaction.BlockHeight = r.DstTransaction.Height + s.DstTransaction.Time = r.DstTransaction.Time + s.DstTransaction.Status = r.DstTransaction.State + s.DstTransaction.ChainId = r.DstTransaction.ChainId + + feeToken := feeTokens[s.DstTransaction.ChainId] + precision := decimal.NewFromInt(basedef.Int64FromFigure(int(feeToken.TokenBasic.Precision))) + { + bbb := decimal.NewFromBigInt(&r.SrcTransaction.Fee.Int, 0) + feeAmount := bbb.Div(precision) + s.SrcTransaction.Fee = feeAmount.String() + } } - for _, v := range transactions { - tx := new(WrapperTransactionRsp).instance(v) - s.Transactions = append(s.Transactions, tx) + if r.PolyTransaction != nil { + s.PolyTransaction.Hash = r.PolyTransaction.Hash + s.PolyTransaction.BlockHeight = r.PolyTransaction.Height + s.PolyTransaction.Time = r.PolyTransaction.Time + s.PolyTransaction.Status = r.PolyTransaction.State + s.PolyTransaction.From = s.Transaction.From + s.PolyTransaction.To = s.Transaction.To } return s } diff --git a/nft_http/controllers/transaction_controller.go b/nft_http/controllers/transaction_controller.go index 9095af46..2bb0b985 100644 --- a/nft_http/controllers/transaction_controller.go +++ b/nft_http/controllers/transaction_controller.go @@ -26,30 +26,6 @@ type TransactionController struct { beego.Controller } -func (c *TransactionController) Transactions() { - var req WrapperTransactionsReq - if !input(&c.Controller, &req) { - return - } - - transactions := make([]*models.WrapperTransaction, 0) - db.Where("standard = ?", models.TokenTypeErc721). - Limit(req.PageSize). - Offset(req.PageSize * req.PageNo). - Order("time asc"). - Find(&transactions) - - var transactionNum int64 - db.Model(&models.WrapperTransaction{}). - Where("standard = ?", models.TokenTypeErc721). - Count(&transactionNum) - - totalPage := (int(transactionNum) + req.PageSize - 1) / req.PageSize - totalCnt := int(transactionNum) - data := new(WrapperTransactionsRsp).instance(req.PageSize, req.PageNo, totalPage, totalCnt, transactions) - output(&c.Controller, data) -} - func (c *TransactionController) TransactionsOfAddress() { var req models.TransactionsOfAddressReq diff --git a/nft_http/controllers/utils.go b/nft_http/controllers/utils.go index 0235f9a1..bcd65706 100644 --- a/nft_http/controllers/utils.go +++ b/nft_http/controllers/utils.go @@ -25,6 +25,7 @@ import ( "poly-bridge/conf" "poly-bridge/models" "poly-bridge/nft_http/meta" + "time" "github.com/astaxie/beego" "github.com/astaxie/beego/logs" @@ -36,10 +37,12 @@ import ( var ( db *gorm.DB + txCounter *TransactionCounter sdks = make(map[uint64]*chainsdk.EthereumSdkPro) assets = make([]*models.Token, 0) wrapperAddrs = make(map[uint64]common.Address) fetcher *meta.StoreFetcher + feeTokens = make(map[uint64]*models.Token) ) func NewDB(cfg *conf.DBConfig) *gorm.DB { @@ -57,66 +60,37 @@ func NewDB(cfg *conf.DBConfig) *gorm.DB { panic(err) } - // todo(fuk): delete after debug - err = db.Debug().AutoMigrate( - &models.Chain{}, - &models.WrapperTransaction{}, - &models.ChainFee{}, - &models.TokenBasic{}, - &models.Token{}, - &models.PriceMarket{}, - &models.TokenMap{}, - &models.SrcTransaction{}, - &models.SrcTransfer{}, - &models.PolyTransaction{}, - &models.DstTransaction{}, - &models.DstTransfer{}, - &models.NFTProfile{}, - ) - if err != nil { - panic(err) - } - - //db.Model(&models.Token{}). - // Where("standard = ?", models.TokenTypeErc721). - // Preload("TokenBasic"). - // Find(&assets) - db.Where("standard = ? and property=?", models.TokenTypeErc721, 1). Preload("TokenBasic"). - //Preload("TokenMaps"). - //Preload("TokenMaps.DstToken"). Find(&assets) - for _, v := range assets { - logs.Info("load asset %s, chainid %d", v.TokenBasicName, v.ChainId) + logs.Info("load asset %s, chainid %d, hash %s", v.TokenBasicName, v.ChainId, v.Hash) + } + + feeTokenList := make([]*models.Token, 0) + db.Where("hash=?", nativeHash). + Preload("TokenBasic"). + Find(&feeTokenList) + for _, v := range feeTokenList { + feeTokens[v.ChainId] = v + logs.Info("load chainid %d feeToken %s", v.ChainId, v.TokenBasicName) } return db } func Initialize(c *conf.Config) { - //var err error - //Logger := logger.Default - //if c.DBConfig.Debug { - // Logger = Logger.LogMode(logger.Info) - //} - //link := fmt.Sprintf("%s:%s@tcp(%s)/%s?charset=utf8", - // c.DBConfig.User, - // c.DBConfig.Password, - // c.DBConfig.URL, - // c.DBConfig.Scheme, - //) - //if db, err = gorm.Open(mysql.Open(link), &gorm.Config{Logger: Logger}); err != nil { - // panic(err) - //} - db = NewDB(c.DBConfig) for _, v := range c.ChainListenConfig { - pro := chainsdk.NewEthereumSdkPro(v.GetNodesUrl(), v.ListenSlot, v.ChainId) - sdks[v.ChainId] = pro - wrapperAddrs[v.ChainId] = common.HexToAddress(v.NFTWrapperContract) - logs.Info("load chain id %d, contract %s", v.ChainId, v.NFTWrapperContract) + urls := v.GetNodesUrl() + if len(urls) > 0 { + pro := chainsdk.NewEthereumSdkPro(v.GetNodesUrl(), v.ListenSlot, v.ChainId) + sdks[v.ChainId] = pro + wrapperAddrs[v.ChainId] = common.HexToAddress(v.NFTWrapperContract) + logs.Info("load chain id %d, contract %s", v.ChainId, v.NFTWrapperContract) + } else { + logs.Warn("chain %s node is empty", v.ChainName) + } } storeFetcher, err := meta.NewStoreFetcher(db, 1000) @@ -134,6 +108,37 @@ func Initialize(c *conf.Config) { storeFetcher.Register(fetcherTyp, assetName, baseUri) } fetcher = storeFetcher + + txCounter = NewTransactionCounter() +} + +type TransactionCounter struct { + Count int64 + LastTime int64 +} + +func NewTransactionCounter() *TransactionCounter { + s := new(TransactionCounter) + s.refresh() + return s +} + +func (s *TransactionCounter) refresh() { + db.Model(&models.WrapperTransaction{}). + Where("standard = ?", models.TokenTypeErc721). + Count(&s.Count) + + s.LastTime = time.Now().Unix() +} + +func (s *TransactionCounter) Number() int64 { + now := time.Now().Unix() + if now-s.LastTime < 120 { + return s.Count + } + + s.refresh() + return s.Count } func selectNode(chainID uint64) *chainsdk.EthereumSdkPro { diff --git a/nft_http/router.go b/nft_http/router.go index 44759e7f..eb2f3c3a 100644 --- a/nft_http/router.go +++ b/nft_http/router.go @@ -25,17 +25,25 @@ import ( func init() { ns := beego.NewNamespace("/nft/v1", beego.NSRouter("/", &controllers.InfoController{}, "*:Get"), + beego.NSRouter("/assetshow/", &controllers.InfoController{}, "post:Home"), beego.NSRouter("/asset/", &controllers.AssetController{}, "post:Asset"), beego.NSRouter("/assets/", &controllers.AssetController{}, "post:Assets"), + //beego.NSRouter("/assetbasics/", &controllers.AssetController{}, "post:AssetBasics"), //beego.NSRouter("/assetmap/", &controllers.AssetMapController{}, "post:AssetMap"), //beego.NSRouter("/assetmapreverse/", &controllers.AssetMapController{}, "post:AssetMapReverse"), + beego.NSRouter("/items/", &controllers.ItemController{}, "post:Items"), beego.NSRouter("/getfee/", &controllers.FeeController{}, "post:GetFee"), - //beego.NSRouter("/transactions/", &controllers.TransactionController{}, "post:Transactions"), + + beego.NSRouter("/exp_rtransactions/", &controllers.ExplorerController{}, "post:Transactions"), + beego.NSRouter("/exp_transactionsofaddress/", &controllers.ExplorerController{}, "post:TransactionsOfAddress"), + beego.NSRouter("/exp_transactionofhash/", &controllers.ExplorerController{}, "post:TransactionDetail"), + beego.NSRouter("/transactionsofaddress/", &controllers.TransactionController{}, "post:TransactionsOfAddress"), beego.NSRouter("/transactionofhash/", &controllers.TransactionController{}, "post:TransactionOfHash"), + //beego.NSRouter("/transactionsofstate/", &controllers.TransactionController{}, "post:TransactionsOfState"), ) beego.AddNamespace(ns)