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.
457 lines
12 KiB
457 lines
12 KiB
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 |
|
}
|
|
|