package blockpay import ( "encoding/hex" "errors" "fmt" "math/rand" "server/call" c "server/common" "server/config" "server/db" "server/util" "sync" "time" "github.com/fbsobreira/gotron-sdk/pkg/client" "github.com/fbsobreira/gotron-sdk/pkg/client/transaction" "github.com/fbsobreira/gotron-sdk/pkg/common" "github.com/fbsobreira/gotron-sdk/pkg/proto/api" "github.com/fbsobreira/gotron-sdk/pkg/proto/core" "github.com/fbsobreira/gotron-sdk/pkg/store" "github.com/liangdas/mqant/log" "google.golang.org/grpc" "gorm.io/gorm" ) const ( ScanInterval = 10 // 每10秒扫描一次block ScanNum = 5 // 每次扫描的block数 ExpireTime = 30 * 60 // 订单超时时间设置为30分钟 MaxLocalAddress = 5 // 本地最大存储5个官方地址 ExtractEnergy = 1000000 // 额外预估的能量 FeeLimitMultiple = 5 // feelimit设置为预估的倍数 EnergyRate = 420 // 能量价值 TronAddr = "grpc.trongrid.io:50051" TestTronAddr = "grpc.nile.trongrid.io:50051" TronUSDTAddr = "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t" TronSmartContractLen = 68 MyAddr = "TCFpT5AsYREPWSdGmgNY4ePJFBduTe43QW" // 用于计算能量时用一下 ) var ( tc *client.GrpcClient // tron client ti int64 // 当前tron扫描到的块高度 listenMap = map[string][]*OneListen{} // 监听地址的map,key为地址,value为监听的次数 mux = new(sync.RWMutex) OfficialAddrs = make([]string, MaxLocalAddress) ) type OneListen struct { OrderID string // 订单号 Expire int64 // 该监听超时时间 Amount int64 // 充值金额 } func InitTron() { // 加载本地官方地址 for i := 1; i <= 5; i++ { str, err := store.AddressFromAccountName(fmt.Sprintf("official%d", i)) if err == nil { OfficialAddrs[i] = str } } // 从数据库中加载tron相关数据 ti = call.GetConfigTron().CurrentBlock // 拉取当前任然活跃需要监听的订单 list := []c.RechargeOrder{} db.Mysql().QueryAll(fmt.Sprintf("create_time >= %d and event = %d and pay_source = %d and status = %d", time.Now().Add(-30*time.Minute).Unix(), c.CurrencyEventReCharge, c.PaySourceBlockPay, c.StatusROrderCreate), "", &c.RechargeOrder{}, &list) mux.Lock() for _, v := range list { listens, ok := listenMap[v.PayAccount] one := &OneListen{OrderID: v.OrderID, Expire: v.CreateTime + ExpireTime, Amount: v.Amount} if !ok { listenMap[v.PayAccount] = []*OneListen{one} } else { listens = append(listens, one) listenMap[v.PayAccount] = listens } } mux.Unlock() addr := TronAddr if !config.GetBase().Release { addr = TestTronAddr ti = call.GetConfigTron().CurrentBlockTest } tc = client.NewGrpcClient(addr) if err := tc.Start(grpc.WithInsecure()); err != nil { log.Error("err:%v", err) } time.AfterFunc(ScanInterval*time.Second, func() { ScanTronBlock() }) WithdrawTimer() } // ScanTronBlock 扫描块 func ScanTronBlock() { defer func() { time.AfterFunc(ScanInterval*time.Second, func() { ScanTronBlock() }) }() now := time.Now().Unix() // 首先清理过期监听 mux.Lock() for k, v := range listenMap { listens := []*OneListen{} for _, l := range v { if l.Expire <= now { // 已过期 orderID := l.OrderID util.Go(func() { db.Mysql().Update(&c.RechargeOrder{OrderID: orderID, Status: c.StatusROrderCreate, Event: int(c.CurrencyEventReCharge)}, map[string]interface{}{"status": c.StatusROrderFail}) }) continue } listens = append(listens, l) } if len(listens) == 0 { delete(listenMap, k) continue } if len(listens) != len(v) { listenMap[k] = listens } } mux.Unlock() list, err := tc.GetBlockByLatestNum(ScanNum) if err != nil { log.Error("scan err:%v", err) return } blocks := list.Block if len(blocks) == 0 { return } log.Debug("start scanning current block %d", ti+1) // 已扫描的块高度没有达到最近的10个块,则需要重新扫描块 if blocks[0].BlockHeader.RawData.Number > ti { start := ti + 1 end := blocks[0].BlockHeader.RawData.Number - 1 diff := end - start // 块数过多,分批扫描 if diff > 700 { start = blocks[0].BlockHeader.RawData.Number - 700 // 最大扫描30分钟内的块 } for begin := start; begin < end; begin += 100 { e := begin + 99 if e > end { e = end } if e == begin { e++ } blocksEx, err := tc.GetBlockByLimitNext(begin, e) if err != nil { log.Error("scan err:%v", err) return } checkBlocks(blocksEx.Block) } } checkBlocks(blocks) ti = blocks[len(blocks)-1].BlockHeader.RawData.Number // 扫描完成后更新当前扫描块 if config.GetBase().Release { db.Mysql().Update(&c.ConfigTron{ID: call.GetConfigTron().ID}, map[string]interface{}{"current_block": ti}) } else { db.Mysql().Update(&c.ConfigTron{ID: call.GetConfigTron().ID}, map[string]interface{}{"current_block_test": ti}) } } func checkBlocks(blocks []*api.BlockExtention) { // 读取监听map mux.Lock() defer mux.Unlock() log.Debug("scanning blocks %d-%d", blocks[0].BlockHeader.RawData.Number, blocks[len(blocks)-1].BlockHeader.RawData.Number) for _, v := range blocks { n := v.BlockHeader.RawData.Number if n <= ti { // log.Debug("block %d scan already", n) continue } for _, j := range v.Transactions { if j.Result.Code != api.Return_SUCCESS { continue } for _, k := range j.Transaction.RawData.Contract { toAddr, amount := scanContracts(k) if toAddr == "" { continue } listens, ok := listenMap[toAddr] if !ok { continue } var one *OneListen index := -1 for i, v := range listens { if v.Amount/c.DecimalDigits == amount/1e6 { index = i one = v break } } if one == nil { continue } // 监听命中,发货 if len(listens) == 1 { delete(listenMap, toAddr) } else { listens = append(listens[:index], listens[index+1:]...) listenMap[toAddr] = listens } txid := hex.EncodeToString(j.Txid) paySuccess(one.OrderID, toAddr, txid, amount) break } } } } // 提取contract中的toAddr,amount func scanContracts(k *core.Transaction_Contract) (toAddr string, amount int64) { if config.GetBase().Release { if k.Type != core.Transaction_Contract_TriggerSmartContract { return } sc := new(core.TriggerSmartContract) if err := k.Parameter.UnmarshalTo(sc); err != nil { log.Error("TriggerSmartContract UnmarshalTo err:%e", err) return } if len(sc.Data) != TronSmartContractLen { return } // 只监听usdt事件 contractAddress := common.EncodeCheck(sc.ContractAddress) if contractAddress != TronUSDTAddr { return } sc.Data[15] = 65 // 地址前端补41 // 取15到36位,拿到to addr toAddr = common.EncodeCheck(sc.Data[15:36]) // 取36到68位,拿到amount amount = convertToInt(sc.Data[36:]) } else { if k.Type != core.Transaction_Contract_TransferContract { return } tc := new(core.TransferContract) if err := k.Parameter.UnmarshalTo(tc); err != nil { log.Error("TransferContract UnmarshalTo err:%e", err) return } toAddr = common.EncodeCheck(tc.ToAddress) amount = tc.Amount } return } // 充值成功发货 func paySuccess(orderID, toAddr, txid string, amount int64) { or := &c.RechargeOrder{OrderID: orderID} log.Debug("pay success:%s,txid:%s,orderID:%s", toAddr, txid, orderID) util.Go(func() { db.Mysql().Get(or) if or.Status != c.StatusROrderCreate { return } or.APIPayID = txid call.RechargeCallback(or, true, "", toAddr) // 确认交易后,转账至官方账号,正式服出于成本考虑,后台操作扫描全服玩家。 if config.GetBase().Release { // 支付成功,刷新玩家地址余额 db.Mysql().Update(&c.TronData{UID: or.UID}, map[string]interface{}{"usdt": gorm.Expr("usdt + ?", amount)}) } else { if _, err := transferTRX(toAddr, "", orderID, amount, 2); err != nil { log.Error("transferTrx err:%e", err) } } }) } func convertToInt(b []byte) int64 { var ret int64 index := 0 for i := len(b) - 1; i >= 0; i-- { var tmp int64 = 1 for j := 0; j < index; j++ { tmp *= 256 } index++ ret += tmp * int64(b[i]) } return ret } func getRandomOfficiallAddr() string { ret := []string{} for _, v := range OfficialAddrs { if v != "" { ret = append(ret, v) } } if len(ret) == 0 { return "" } return ret[rand.Intn(len(ret))] } // 转trx // status 1玩家退出打u 2系统自动从玩家地址转u回主地址 3后台转账操作 func transferTRX(fromAddr, toAddr, orderID string, amount int64, status int) (txid string, err error) { defer func() { // 此时Tx才上链 record := &c.ESTron{ OrderID: orderID, TxID: txid, From: fromAddr, To: toAddr, Amount: amount, Time: time.Now().Unix(), Type: 1, Success: err == nil, Status: status, } db.ES().InsertToESGO(c.ESIndexTron, record) }() if toAddr == "" { toAddr = getRandomOfficiallAddr() if toAddr == "" { err = errors.New("no pay addrs") return } } if fromAddr == "" { fromAddr = getRandomOfficiallAddr() if fromAddr == "" { err = errors.New("no pay addrs") return } } tx, err1 := tc.Transfer(fromAddr, toAddr, amount) if err1 != nil { log.Error("err:%v", err1) err = err1 return } kss, accc, err1 := store.UnlockedKeystore(fromAddr, "") if err1 != nil { log.Error("err:%v", err1) err = err1 return } ctrll := transaction.NewController(tc, kss, accc, tx.Transaction) if err1 := ctrll.ExecuteTransaction(); err1 != nil { log.Error("err:%v", err1) err = err1 return } txid = hex.EncodeToString(tx.GetTxid()) return } // 转usdt // status 1玩家退出打u 2系统自动从玩家地址转u回主地址 3后台转账操作 func transferUSDT(fromAddr, toAddr, orderID string, amount int64, status int) (txid string, err error) { defer func() { // 此时Tx才上链 record := &c.ESTron{ OrderID: orderID, TxID: txid, From: fromAddr, To: toAddr, Amount: amount, Time: time.Now().Unix(), Type: 2, Success: err == nil, Status: status, } db.ES().InsertToESGO(c.ESIndexTron, record) }() if toAddr == "" { toAddr = getRandomOfficiallAddr() if toAddr == "" { err = errors.New("no pay addrs") return } } if fromAddr == "" { fromAddr = getRandomOfficiallAddr() if fromAddr == "" { err = errors.New("no pay addrs") return } } tx, err := tc.TriggerConstantContract(fromAddr, TronUSDTAddr, "transfer(address,uint256)", fmt.Sprintf("[{\"address\":\"%s\"},{\"uint256\":\"%d\"}]", toAddr, amount)) if err != nil { log.Error("err:%e", err) return "", err } base := tx.EnergyUsed*EnergyRate + ExtractEnergy tx.Transaction.RawData.FeeLimit = base * FeeLimitMultiple // 所需能量 need := base ac, _ := tc.GetAccount(fromAddr) if ac != nil { need -= ac.Balance } if status == 2 && need > 0 { _, err = transferTRX("", fromAddr, orderID, need, status) if err != nil { return "", err } } kss, accc, err1 := store.UnlockedKeystore(fromAddr, "") if err1 != nil { log.Error("err:%e", err1) err = err1 return } ctrll := transaction.NewController(tc, kss, accc, tx.Transaction) err = ctrll.ExecuteTransaction() if err != nil { log.Error("err:%e", err) return } txid = hex.EncodeToString(tx.GetTxid()) log.Debug("txResult:%v", ctrll.Result) return } // 获取转U所需能量消耗 func getEnergyUse(amount int64, toAddr string) int64 { if toAddr == "" { toAddr = MyAddr } if amount == 0 { amount = 1e6 } addr := getRandomOfficiallAddr() tc, err := tc.TriggerConstantContract(addr, TronUSDTAddr, "transfer(address,uint256)", fmt.Sprintf("[{\"address\":\"%s\"},{\"uint256\":\"%d\"}]", toAddr, amount)) if err != nil { return 0 } return tc.EnergyUsed*EnergyRate + ExtractEnergy }