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
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
|
||
|
|
}
|