fix: (update tron logic)

This commit is contained in:
line-6000
2026-05-11 13:16:42 +08:00
parent 3e0be9bb20
commit 7c9e3e123e
4 changed files with 160 additions and 38 deletions
+23
View File
@@ -1,7 +1,9 @@
package crypto package crypto
import ( import (
"bytes"
"crypto/sha256" "crypto/sha256"
"fmt"
"github.com/shengdoushi/base58" "github.com/shengdoushi/base58"
) )
@@ -28,3 +30,24 @@ func EncodeCheck(input []byte) string {
return Encode(inputCheck) return Encode(inputCheck)
} }
// DecodeCheck decodes a Base58Check string and validates its 4-byte checksum.
func DecodeCheck(input string) ([]byte, error) {
raw, err := base58.Decode(input, base58.BitcoinAlphabet)
if err != nil {
return nil, err
}
if len(raw) < 4 {
return nil, fmt.Errorf("base58check payload too short")
}
payload := raw[:len(raw)-4]
checksum := raw[len(raw)-4:]
h0 := sha256.Sum256(payload)
h1 := sha256.Sum256(h0[:])
if !bytes.Equal(checksum, h1[:4]) {
return nil, fmt.Errorf("base58check checksum mismatch")
}
return payload, nil
}
+30 -13
View File
@@ -36,47 +36,64 @@ func ResolveTronNode() (string, string, error) {
return resolveTronNode() return resolveTronNode()
} }
func TryProcessTronTRC20Transfer(toAddr string, rawValue *big.Int, txHash string, blockTsMs int64) { func TryProcessTronTRC20Transfer(token mdb.ChainToken, toAddr string, rawValue *big.Int, txHash string, blockTsMs int64) {
tokenSym := strings.ToUpper(strings.TrimSpace(token.Symbol))
defer func() { defer func() {
if err := recover(); err != nil { if err := recover(); err != nil {
log.Sugar.Errorf("[TRC20][%s] TryProcessTronTRC20Transfer panic: %v", toAddr, err) log.Sugar.Errorf("[TRC20-%s][%s] TryProcessTronTRC20Transfer panic: %v", tokenSym, toAddr, err)
} }
}() }()
addr := strings.TrimSpace(toAddr) addr := strings.TrimSpace(toAddr)
if addr == "" || rawValue == nil || rawValue.Sign() <= 0 { if tokenSym == "" || addr == "" || rawValue == nil || rawValue.Sign() <= 0 {
return return
} }
decimals := token.Decimals
if decimals < 0 {
decimals = 0
}
decimalQuant := decimal.NewFromBigInt(rawValue, 0) decimalQuant := decimal.NewFromBigInt(rawValue, 0)
amount := math.MustParsePrecFloat64(decimalQuant.Div(decimal.NewFromInt(1_000_000)).InexactFloat64(), 2) amount := math.MustParsePrecFloat64(decimalQuant.Div(decimal.New(1, int32(decimals))).InexactFloat64(), 2)
if amount <= 0 { if amount <= 0 {
return return
} }
if token.MinAmount > 0 && amount < token.MinAmount {
log.Sugar.Debugf("[TRC20-%s][%s] skip below min amount hash=%s amount=%.2f min=%.2f", tokenSym, addr, txHash, amount, token.MinAmount)
return
}
tradeID, err := data.GetTradeIdByWalletAddressAndAmountAndToken(mdb.NetworkTron, addr, "USDT", amount) tradeID, err := data.GetTradeIdByWalletAddressAndAmountAndToken(mdb.NetworkTron, addr, tokenSym, amount)
if err != nil { if err != nil {
log.Sugar.Warnf("[TRC20][%s] lock lookup: %v", addr, err) log.Sugar.Warnf("[TRC20-%s][%s] lock lookup: %v", tokenSym, addr, err)
return return
} }
if tradeID == "" { if tradeID == "" {
log.Sugar.Debugf("[TRC20][%s] skip unmatched tx hash=%s amount=%.2f", addr, txHash, amount) log.Sugar.Debugf("[TRC20-%s][%s] skip unmatched tx hash=%s amount=%.2f", tokenSym, addr, txHash, amount)
return return
} }
order, err := data.GetOrderInfoByTradeId(tradeID) order, err := data.GetOrderInfoByTradeId(tradeID)
if err != nil { if err != nil {
log.Sugar.Warnf("[TRC20][%s] load order: %v", addr, err) log.Sugar.Warnf("[TRC20-%s][%s] load order: %v", tokenSym, addr, err)
return
}
if strings.ToLower(strings.TrimSpace(order.Network)) != mdb.NetworkTron {
log.Sugar.Warnf("[TRC20-%s][%s] skip trade_id=%s network=%q", tokenSym, addr, tradeID, order.Network)
return
}
if strings.ToUpper(strings.TrimSpace(order.Token)) != tokenSym {
log.Sugar.Warnf("[TRC20-%s][%s] skip trade_id=%s token mismatch order=%s", tokenSym, addr, tradeID, order.Token)
return return
} }
if blockTsMs > 0 && blockTsMs < order.CreatedAt.TimestampMilli() { if blockTsMs > 0 && blockTsMs < order.CreatedAt.TimestampMilli() {
log.Sugar.Warnf("[TRC20][%s] skip tx %s because block time %d is before order create time %d", addr, txHash, blockTsMs, order.CreatedAt.TimestampMilli()) log.Sugar.Warnf("[TRC20-%s][%s] skip tx %s because block time %d is before order create time %d", tokenSym, addr, txHash, blockTsMs, order.CreatedAt.TimestampMilli())
return return
} }
req := &request.OrderProcessingRequest{ req := &request.OrderProcessingRequest{
ReceiveAddress: addr, ReceiveAddress: addr,
Token: "USDT", Token: tokenSym,
Network: mdb.NetworkTron, Network: mdb.NetworkTron,
TradeId: tradeID, TradeId: tradeID,
Amount: amount, Amount: amount,
@@ -85,15 +102,15 @@ func TryProcessTronTRC20Transfer(toAddr string, rawValue *big.Int, txHash string
err = OrderProcessing(req) err = OrderProcessing(req)
if err != nil { if err != nil {
if errors.Is(err, constant.OrderBlockAlreadyProcess) || errors.Is(err, constant.OrderStatusConflict) { if errors.Is(err, constant.OrderBlockAlreadyProcess) || errors.Is(err, constant.OrderStatusConflict) {
log.Sugar.Infof("[TRC20][%s] skip resolved transfer trade_id=%s hash=%s err=%v", addr, tradeID, txHash, err) log.Sugar.Infof("[TRC20-%s][%s] skip resolved transfer trade_id=%s hash=%s err=%v", tokenSym, addr, tradeID, txHash, err)
return return
} }
log.Sugar.Errorf("[TRC20][%s] OrderProcessing trade_id=%s hash=%s: %v", addr, tradeID, txHash, err) log.Sugar.Errorf("[TRC20-%s][%s] OrderProcessing trade_id=%s hash=%s: %v", tokenSym, addr, tradeID, txHash, err)
return return
} }
sendPaymentNotification(order) sendPaymentNotification(order)
log.Sugar.Infof("[TRC20][%s] payment processed trade_id=%s hash=%s", addr, tradeID, txHash) log.Sugar.Infof("[TRC20-%s][%s] payment processed trade_id=%s hash=%s", tokenSym, addr, tradeID, txHash)
} }
func TryProcessTronTRXTransfer(toAddr string, rawSun int64, txHash string, blockTsMs int64) { func TryProcessTronTRXTransfer(toAddr string, rawSun int64, txHash string, blockTsMs int64) {
+84 -25
View File
@@ -12,13 +12,13 @@ import (
"time" "time"
tron "github.com/GMWalletApp/epusdt/crypto" tron "github.com/GMWalletApp/epusdt/crypto"
"github.com/GMWalletApp/epusdt/model/data"
"github.com/GMWalletApp/epusdt/model/mdb"
"github.com/GMWalletApp/epusdt/model/service" "github.com/GMWalletApp/epusdt/model/service"
"github.com/GMWalletApp/epusdt/util/log" "github.com/GMWalletApp/epusdt/util/log"
) )
const ( const (
// USDT 合约地址 (TRC20 主网)
USDTContractHex = "41a614f803b6fd780986a42c78ec9c7f77e6ded13c" // Base58: TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t
// transfer(address,uint256) 方法签名前4字节 // transfer(address,uint256) 方法签名前4字节
TransferMethodID = "a9059cbb" TransferMethodID = "a9059cbb"
@@ -46,6 +46,31 @@ func HexToTronAddress(hexAddr string) (string, error) {
return tron.EncodeCheck(raw), nil return tron.EncodeCheck(raw), nil
} }
func TronAddressToHex(addr string) (string, error) {
addr = strings.TrimSpace(addr)
if addr == "" {
return "", fmt.Errorf("地址为空")
}
raw, err := tron.DecodeCheck(addr)
if err != nil {
return "", err
}
if len(raw) != 21 {
return "", fmt.Errorf("地址长度非法: %d bytes", len(raw))
}
if raw[0] != 0x41 {
return "", fmt.Errorf("非法 TRON 主网地址前缀: 0x%x", raw[0])
}
return strings.ToLower(hex.EncodeToString(raw)), nil
}
func normalizeTronHexAddress(hexAddr string) string {
hexAddr = strings.TrimSpace(hexAddr)
hexAddr = strings.TrimPrefix(hexAddr, "0x")
hexAddr = strings.TrimPrefix(hexAddr, "0X")
return strings.ToLower(hexAddr)
}
type BlockHeader struct { type BlockHeader struct {
RawData struct { RawData struct {
Number int64 `json:"number"` Number int64 `json:"number"`
@@ -89,12 +114,13 @@ type Block struct {
Transactions []Transaction `json:"transactions"` Transactions []Transaction `json:"transactions"`
} }
type USDTTransfer struct { type TRC20Transfer struct {
TxID string TxID string
From string From string
To string To string
Raw *big.Int // 原始数值(6 decimals Raw *big.Int
Status string Status string
Token mdb.ChainToken
} }
type TRXTransfer struct { type TRXTransfer struct {
@@ -156,7 +182,33 @@ func GetBlockByNum(baseURL string, apiKey string, num int64) (*Block, error) {
return &block, json.Unmarshal(b, &block) return &block, json.Unmarshal(b, &block)
} }
func parseUSDTTransfer(tx Transaction) *USDTTransfer { func loadTronTRC20TokenMap() map[string]mdb.ChainToken {
tokens, err := data.ListEnabledChainTokensByNetwork(mdb.NetworkTron)
if err != nil {
log.Sugar.Warnf("[TRON-BLOCK] load chain_tokens: %v", err)
return nil
}
tokenMap := make(map[string]mdb.ChainToken)
for _, token := range tokens {
symbol := strings.ToUpper(strings.TrimSpace(token.Symbol))
contractAddress := strings.TrimSpace(token.ContractAddress)
if symbol == "TRX" || contractAddress == "" {
continue
}
contractHex, err := TronAddressToHex(contractAddress)
if err != nil {
log.Sugar.Warnf("[TRON-BLOCK] skip invalid TRC20 contract symbol=%s address=%s err=%v", symbol, contractAddress, err)
continue
}
tokenMap[contractHex] = token
}
return tokenMap
}
func parseTRC20Transfer(tx Transaction, tokenMap map[string]mdb.ChainToken) *TRC20Transfer {
if len(tokenMap) == 0 {
return nil
}
if len(tx.RawData.Contract) == 0 { if len(tx.RawData.Contract) == 0 {
return nil return nil
} }
@@ -170,9 +222,9 @@ func parseUSDTTransfer(tx Transaction) *USDTTransfer {
return nil return nil
} }
// 检查是否是 USDT 合约 contractHex := normalizeTronHexAddress(val.ContractAddress)
contractHex := strings.ToLower(strings.TrimPrefix(val.ContractAddress, "0x")) token, ok := tokenMap[contractHex]
if contractHex != USDTContractHex { if !ok {
return nil return nil
} }
@@ -213,12 +265,13 @@ func parseUSDTTransfer(tx Transaction) *USDTTransfer {
status = tx.Ret[0].ContractRet status = tx.Ret[0].ContractRet
} }
return &USDTTransfer{ return &TRC20Transfer{
TxID: tx.TxID, TxID: tx.TxID,
From: fromAddr, From: fromAddr,
To: toAddr, To: toAddr,
Raw: amountBig, Raw: amountBig,
Status: status, Status: status,
Token: token,
} }
} }
@@ -262,14 +315,14 @@ func parseTRXTransfer(tx Transaction) *TRXTransfer {
} }
} }
func processBlock(block *Block) { func processBlock(block *Block, tokenMap map[string]mdb.ChainToken) {
blockTsMs := block.BlockHeader.RawData.Timestamp blockTsMs := block.BlockHeader.RawData.Timestamp
for _, tx := range block.Transactions { for _, tx := range block.Transactions {
if t := parseUSDTTransfer(tx); t != nil { if t := parseTRC20Transfer(tx, tokenMap); t != nil {
if t.Status != "SUCCESS" { if t.Status != "SUCCESS" {
continue continue
} }
service.TryProcessTronTRC20Transfer(t.To, t.Raw, t.TxID, blockTsMs) service.TryProcessTronTRC20Transfer(t.Token, t.To, t.Raw, t.TxID, blockTsMs)
continue continue
} }
if t := parseTRXTransfer(tx); t != nil { if t := parseTRXTransfer(tx); t != nil {
@@ -286,9 +339,9 @@ type Scanner struct {
apiKey string apiKey string
lastBlock int64 lastBlock int64
// 统计 // 统计
totalBlocks int64 totalBlocks int64
totalUSDTTxs int64 totalTRC20Txs int64
totalTRXTxs int64 totalTRXTxs int64
} }
func NewScanner() *Scanner { func NewScanner() *Scanner {
@@ -314,7 +367,7 @@ func (s *Scanner) Init() error {
} }
func (s *Scanner) Run() { func (s *Scanner) Run() {
log.Sugar.Info("[TRON-BLOCK] start scanning (USDT TRC20 + TRX)") log.Sugar.Info("[TRON-BLOCK] start scanning (TRC20 + TRX)")
ticker := time.NewTicker(PollInterval) ticker := time.NewTicker(PollInterval)
defer ticker.Stop() defer ticker.Stop()
@@ -324,7 +377,7 @@ func (s *Scanner) Run() {
for { for {
select { select {
case <-statTicker.C: case <-statTicker.C:
log.Sugar.Infof("[TRON-BLOCK] stats blocks=%d usdt=%d trx=%d", s.totalBlocks, s.totalUSDTTxs, s.totalTRXTxs) log.Sugar.Infof("[TRON-BLOCK] stats blocks=%d trc20=%d trx=%d", s.totalBlocks, s.totalTRC20Txs, s.totalTRXTxs)
case <-ticker.C: case <-ticker.C:
s.poll() s.poll()
} }
@@ -342,6 +395,7 @@ func (s *Scanner) poll() {
return return
} }
tokenMap := loadTronTRC20TokenMap()
for num := s.lastBlock + 1; num <= latestNum; num++ { for num := s.lastBlock + 1; num <= latestNum; num++ {
var block *Block var block *Block
if num == latestNum { if num == latestNum {
@@ -354,13 +408,13 @@ func (s *Scanner) poll() {
} }
time.Sleep(200 * time.Millisecond) time.Sleep(200 * time.Millisecond)
} }
processBlock(block) processBlock(block, tokenMap)
s.lastBlock = num s.lastBlock = num
s.totalBlocks++ s.totalBlocks++
for _, tx := range block.Transactions { for _, tx := range block.Transactions {
if parseUSDTTransfer(tx) != nil { if parseTRC20Transfer(tx, tokenMap) != nil {
s.totalUSDTTxs++ s.totalTRC20Txs++
} else if parseTRXTransfer(tx) != nil { } else if parseTRXTransfer(tx) != nil {
s.totalTRXTxs++ s.totalTRXTxs++
} }
@@ -369,10 +423,15 @@ func (s *Scanner) poll() {
} }
func StartTronBlockScannerListener() { func StartTronBlockScannerListener() {
scanner := NewScanner() for {
if err := scanner.Init(); err != nil { scanner := NewScanner()
log.Sugar.Errorf("[TRON-BLOCK] init: %v", err) if err := scanner.Init(); err != nil {
return log.Sugar.Errorf("[TRON-BLOCK] init: %v, retrying...", err)
time.Sleep(10 * time.Second)
continue
}
scanner.Run()
log.Sugar.Warn("[TRON-BLOCK] scanner stopped, restarting...")
time.Sleep(3 * time.Second)
} }
scanner.Run()
} }
+23
View File
@@ -0,0 +1,23 @@
package task
import "testing"
func TestTronAddressToHexConvertsBase58Contract(t *testing.T) {
got, err := TronAddressToHex("TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t")
if err != nil {
t.Fatalf("TronAddressToHex(): %v", err)
}
const want = "41a614f803b6fd780986a42c78ec9c7f77e6ded13c"
if got != want {
t.Fatalf("TronAddressToHex() = %q, want %q", got, want)
}
}
func TestNormalizeTronHexAddress(t *testing.T) {
got := normalizeTronHexAddress("0X41A614F803B6FD780986A42C78EC9C7F77E6DED13C")
const want = "41a614f803b6fd780986a42c78ec9c7f77e6ded13c"
if got != want {
t.Fatalf("normalizeTronHexAddress() = %q, want %q", got, want)
}
}