From 7c9e3e123e5c8ccb77cd78a4db9f6a96d6d20bb5 Mon Sep 17 00:00:00 2001 From: line-6000 <154492442+line-6000@users.noreply.github.com> Date: Mon, 11 May 2026 13:16:42 +0800 Subject: [PATCH] fix: (update tron logic) --- src/crypto/tron.go | 23 ++++++ src/model/service/task_service.go | 43 +++++++---- src/task/listen_trc20_block.go | 109 +++++++++++++++++++++------- src/task/listen_trc20_block_test.go | 23 ++++++ 4 files changed, 160 insertions(+), 38 deletions(-) create mode 100644 src/task/listen_trc20_block_test.go diff --git a/src/crypto/tron.go b/src/crypto/tron.go index abf36f3..cdc2d18 100644 --- a/src/crypto/tron.go +++ b/src/crypto/tron.go @@ -1,7 +1,9 @@ package crypto import ( + "bytes" "crypto/sha256" + "fmt" "github.com/shengdoushi/base58" ) @@ -28,3 +30,24 @@ func EncodeCheck(input []byte) string { 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 +} diff --git a/src/model/service/task_service.go b/src/model/service/task_service.go index 6c15789..9e24043 100644 --- a/src/model/service/task_service.go +++ b/src/model/service/task_service.go @@ -36,47 +36,64 @@ func ResolveTronNode() (string, string, error) { 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() { 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) - if addr == "" || rawValue == nil || rawValue.Sign() <= 0 { + if tokenSym == "" || addr == "" || rawValue == nil || rawValue.Sign() <= 0 { return } + decimals := token.Decimals + if decimals < 0 { + decimals = 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 { 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 { - log.Sugar.Warnf("[TRC20][%s] lock lookup: %v", addr, err) + log.Sugar.Warnf("[TRC20-%s][%s] lock lookup: %v", tokenSym, addr, err) return } 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 } order, err := data.GetOrderInfoByTradeId(tradeID) 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 } 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 } req := &request.OrderProcessingRequest{ ReceiveAddress: addr, - Token: "USDT", + Token: tokenSym, Network: mdb.NetworkTron, TradeId: tradeID, Amount: amount, @@ -85,15 +102,15 @@ func TryProcessTronTRC20Transfer(toAddr string, rawValue *big.Int, txHash string err = OrderProcessing(req) if err != nil { 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 } - 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 } 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) { diff --git a/src/task/listen_trc20_block.go b/src/task/listen_trc20_block.go index dea5509..0cac2df 100644 --- a/src/task/listen_trc20_block.go +++ b/src/task/listen_trc20_block.go @@ -12,13 +12,13 @@ import ( "time" 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/util/log" ) const ( - // USDT 合约地址 (TRC20 主网) - USDTContractHex = "41a614f803b6fd780986a42c78ec9c7f77e6ded13c" // Base58: TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t // transfer(address,uint256) 方法签名前4字节 TransferMethodID = "a9059cbb" @@ -46,6 +46,31 @@ func HexToTronAddress(hexAddr string) (string, error) { 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 { RawData struct { Number int64 `json:"number"` @@ -89,12 +114,13 @@ type Block struct { Transactions []Transaction `json:"transactions"` } -type USDTTransfer struct { +type TRC20Transfer struct { TxID string From string To string - Raw *big.Int // 原始数值(6 decimals) + Raw *big.Int Status string + Token mdb.ChainToken } type TRXTransfer struct { @@ -156,7 +182,33 @@ func GetBlockByNum(baseURL string, apiKey string, num int64) (*Block, error) { 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 { return nil } @@ -170,9 +222,9 @@ func parseUSDTTransfer(tx Transaction) *USDTTransfer { return nil } - // 检查是否是 USDT 合约 - contractHex := strings.ToLower(strings.TrimPrefix(val.ContractAddress, "0x")) - if contractHex != USDTContractHex { + contractHex := normalizeTronHexAddress(val.ContractAddress) + token, ok := tokenMap[contractHex] + if !ok { return nil } @@ -213,12 +265,13 @@ func parseUSDTTransfer(tx Transaction) *USDTTransfer { status = tx.Ret[0].ContractRet } - return &USDTTransfer{ + return &TRC20Transfer{ TxID: tx.TxID, From: fromAddr, To: toAddr, Raw: amountBig, 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 for _, tx := range block.Transactions { - if t := parseUSDTTransfer(tx); t != nil { + if t := parseTRC20Transfer(tx, tokenMap); t != nil { if t.Status != "SUCCESS" { continue } - service.TryProcessTronTRC20Transfer(t.To, t.Raw, t.TxID, blockTsMs) + service.TryProcessTronTRC20Transfer(t.Token, t.To, t.Raw, t.TxID, blockTsMs) continue } if t := parseTRXTransfer(tx); t != nil { @@ -286,9 +339,9 @@ type Scanner struct { apiKey string lastBlock int64 // 统计 - totalBlocks int64 - totalUSDTTxs int64 - totalTRXTxs int64 + totalBlocks int64 + totalTRC20Txs int64 + totalTRXTxs int64 } func NewScanner() *Scanner { @@ -314,7 +367,7 @@ func (s *Scanner) Init() error { } 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) defer ticker.Stop() @@ -324,7 +377,7 @@ func (s *Scanner) Run() { for { select { 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: s.poll() } @@ -342,6 +395,7 @@ func (s *Scanner) poll() { return } + tokenMap := loadTronTRC20TokenMap() for num := s.lastBlock + 1; num <= latestNum; num++ { var block *Block if num == latestNum { @@ -354,13 +408,13 @@ func (s *Scanner) poll() { } time.Sleep(200 * time.Millisecond) } - processBlock(block) + processBlock(block, tokenMap) s.lastBlock = num s.totalBlocks++ for _, tx := range block.Transactions { - if parseUSDTTransfer(tx) != nil { - s.totalUSDTTxs++ + if parseTRC20Transfer(tx, tokenMap) != nil { + s.totalTRC20Txs++ } else if parseTRXTransfer(tx) != nil { s.totalTRXTxs++ } @@ -369,10 +423,15 @@ func (s *Scanner) poll() { } func StartTronBlockScannerListener() { - scanner := NewScanner() - if err := scanner.Init(); err != nil { - log.Sugar.Errorf("[TRON-BLOCK] init: %v", err) - return + for { + scanner := NewScanner() + if err := scanner.Init(); err != nil { + 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() } diff --git a/src/task/listen_trc20_block_test.go b/src/task/listen_trc20_block_test.go new file mode 100644 index 0000000..6b7dee9 --- /dev/null +++ b/src/task/listen_trc20_block_test.go @@ -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) + } +}