印度包网
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

458 lines
12 KiB

1 year ago
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
}