mirror of
https://github.com/GMWalletApp/epusdt.git
synced 2026-07-07 18:26:16 +00:00
fix: (update tron logic)
This commit is contained in:
@@ -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
|
||||||
|
}
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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 {
|
||||||
@@ -287,7 +340,7 @@ type Scanner struct {
|
|||||||
lastBlock int64
|
lastBlock int64
|
||||||
// 统计
|
// 统计
|
||||||
totalBlocks int64
|
totalBlocks int64
|
||||||
totalUSDTTxs int64
|
totalTRC20Txs int64
|
||||||
totalTRXTxs int64
|
totalTRXTxs int64
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -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() {
|
||||||
|
for {
|
||||||
scanner := NewScanner()
|
scanner := NewScanner()
|
||||||
if err := scanner.Init(); err != nil {
|
if err := scanner.Init(); err != nil {
|
||||||
log.Sugar.Errorf("[TRON-BLOCK] init: %v", err)
|
log.Sugar.Errorf("[TRON-BLOCK] init: %v, retrying...", err)
|
||||||
return
|
time.Sleep(10 * time.Second)
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
scanner.Run()
|
scanner.Run()
|
||||||
|
log.Sugar.Warn("[TRON-BLOCK] scanner stopped, restarting...")
|
||||||
|
time.Sleep(3 * time.Second)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user