Files
m2pool_payment/internal/blockchain/eth/eth_prv.go
2025-11-13 17:08:38 +08:00

1296 lines
39 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package eth
import (
"context"
"database/sql"
"fmt"
"log"
"m2pool-payment/internal/constant"
message "m2pool-payment/internal/msg"
"m2pool-payment/internal/utils"
"math/big"
"strings"
"sync"
"time"
"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
)
func init_USDT() *USDT {
// 构造USDT合约相关
usdt := &USDT{}
usdt.Address = common.HexToAddress(constant.ETH_ERC20_USDT_CONTRACT_ADDRESS) // 解析合约地址
usdt.ABI = func() abi.ABI { a, _ := abi.JSON(strings.NewReader(constant.ETH_ERC20_ABI)); return a }() // 解析合约ABI
usdt.TransferSig = crypto.Keccak256Hash([]byte("Transfer(address,address,uint256)")) // 解析合约transfer函数签名
usdt.LogsChan = make(chan types.Log, 1000) // 初始化合约日志通道
usdt.ListeningAddresses = make(map[string]any)
return usdt
}
func (e *ETHNode) updateNetInfo(height uint64) {
// 创建 WaitGroup
var wg sync.WaitGroup
// 用于保存每个方法的结果
var gasLimit uint64
var suggestGasPrice *big.Int
var maxFeePerGas, maxPriorityFeePerGas *big.Int
var gasLimitErr, suggestGasPriceErr, eip1559GasFeesErr error
// 启动协程并等待所有结果
wg.Add(3)
// 获取 Gas Limit
go func() {
defer wg.Done()
gasLimit, gasLimitErr = e.getGasLimit()
if gasLimitErr != nil {
log.Printf("Failed to get gas limit: %v", gasLimitErr)
} else {
// log.Printf("Gas Limit: %d", gasLimit)
}
}()
// 获取建议 Gas Price
go func() {
defer wg.Done()
suggestGasPrice, suggestGasPriceErr = e.getSuggestGasPrice()
if suggestGasPriceErr != nil {
log.Printf("Failed to get suggested gas price: %v", suggestGasPriceErr)
} else {
// log.Printf("Suggested Gas Price: %v Gwei", new(big.Int).Div(suggestGasPrice, big.NewInt(1000000000)))
}
}()
// 获取 EIP-1559 Gas Fees
go func() {
defer wg.Done()
maxFeePerGas, maxPriorityFeePerGas, eip1559GasFeesErr = e.getEIP1559GasFees()
if eip1559GasFeesErr != nil {
log.Printf("Failed to get EIP-1559 gas fees: %v", eip1559GasFeesErr)
} else {
// log.Printf("EIP-1559 Gas Fees: MaxFeePerGas: %v Gwei, MaxPriorityFeePerGas: %v Gwei",
// new(big.Int).Div(maxFeePerGas, big.NewInt(1000000000)),
// new(big.Int).Div(maxPriorityFeePerGas, big.NewInt(1000000000)))
}
}()
// 等待所有协程完成
wg.Wait()
// 检查是否有任何错误
if gasLimitErr != nil || suggestGasPriceErr != nil || eip1559GasFeesErr != nil {
log.Println("One or more methods failed. Not updating RealData.")
return
}
// 更新 RealData
e.NetInfo.mu.Lock()
defer e.NetInfo.mu.Unlock()
e.NetInfo = &NetInfo{
Height: height,
GasLimit: gasLimit,
GasTipCap: maxPriorityFeePerGas,
GasFeeCap: maxFeePerGas,
GasPrice: suggestGasPrice,
}
// log.Println("✅ RealData updated successfully.")
}
func (e *ETHNode) getGasLimit() (uint64, error) {
// 对于ERC20转账使用固定的gas limit
// 通常ERC20 transfer需要约65,000-100,000 gas
// 这里设置为80,000足够覆盖大部分情况
return 80000, nil
}
func (e *ETHNode) getSuggestGasPrice() (*big.Int, error) {
ctx := context.Background()
gasPrice, err := e.RpcClient.SuggestGasPrice(ctx)
if err != nil {
return nil, fmt.Errorf("get suggest-gasprice error:%v", err)
}
// 设置gas price上限避免在网络拥堵时费用过高
// 这里设置为20 Gwei (20 * 10^9 wei)
maxGasPrice := new(big.Int).SetUint64(20000000000) // 20 Gwei
if gasPrice.Cmp(maxGasPrice) > 0 {
log.Printf("⚠️ 建议gas price过高 (%v wei),使用上限 20 Gwei", new(big.Int).Div(gasPrice, big.NewInt(1e18)))
return maxGasPrice, nil
}
// log.Printf("✅ 使用建议gas price: %v wei", new(big.Int).Div(gasPrice, big.NewInt(1e18)))
return gasPrice, nil
}
// getEIP1559GasFees 获取EIP-1559的gas费用参数
func (e *ETHNode) getEIP1559GasFees() (*big.Int, *big.Int, error) {
ctx := context.Background()
// 获取基础费用
latestBlock, err := e.RpcClient.BlockByNumber(ctx, nil)
if err != nil {
return nil, nil, fmt.Errorf("failed to get latest block: %w", err)
}
baseFee := latestBlock.BaseFee()
if baseFee == nil {
return nil, nil, fmt.Errorf("base fee not available")
}
// 设置优先级费用tip这里设置为2 Gwei
maxPriorityFeePerGas := new(big.Int).SetUint64(2000000000) // 2 Gwei
// 计算最大费用 = 基础费用 + 优先级费用
maxFeePerGas := new(big.Int).Add(baseFee, maxPriorityFeePerGas)
// 设置最大费用上限为30 Gwei
maxFeeLimit := new(big.Int).SetUint64(30000000000) // 30 Gwei
if maxFeePerGas.Cmp(maxFeeLimit) > 0 {
log.Printf("⚠️ 计算的最大费用过高 (%v wei),使用上限 30 Gwei", new(big.Int).Div(maxFeePerGas, big.NewInt(1e18)))
maxFeePerGas = maxFeeLimit
}
// log.Printf("✅ EIP-1559 Gas费用: BaseFee=%v wei, MaxPriorityFee=%v wei, MaxFee=%v wei",
// new(big.Int).Div(baseFee, big.NewInt(1e18)),
// new(big.Int).Div(maxPriorityFeePerGas, big.NewInt(1e18)),
// new(big.Int).Div(maxFeePerGas, big.NewInt(1e18)))
return maxFeePerGas, maxPriorityFeePerGas, nil
}
func (e *ETHNode) getHeight() (uint64, error) {
height, err := e.RpcClient.BlockNumber(e.Ctx)
if err != nil {
return 0, fmt.Errorf("[inital net info]get block height error: %v", err)
}
return height, nil
}
// Helper function to convert []string to []interface{}
func toInterfaceSlice(addresses []string) []interface{} {
result := make([]interface{}, len(addresses))
for i, v := range addresses {
result[i] = v
}
return result
}
func (e *ETHNode) getAddressesPks(addresses []string) (map[string]string, error) {
// If no addresses are provided, return an error
if len(addresses) == 0 {
return nil, fmt.Errorf("no addresses provided")
}
// Build a placeholder string for the IN clause
placeholders := make([]string, len(addresses))
for i := range addresses {
placeholders[i] = "?"
}
inClause := strings.Join(placeholders, ", ")
// Construct the SQL query
str := fmt.Sprintf("SELECT address, private_key FROM eth_balance WHERE address IN (%s);", inClause)
// Execute the query with the addresses
rows, err := e.MysqlDB.Query(str, toInterfaceSlice(addresses)...)
if err != nil {
return nil, err
}
defer rows.Close() // Ensure the rows are closed after processing
// Prepare a map to store the results
result := make(map[string]string)
// Iterate over the rows and populate the map
var storedAddress, privateKey string
for rows.Next() {
err := rows.Scan(&storedAddress, &privateKey)
if err != nil {
return nil, err
}
// Add the address and private key to the map
result[storedAddress] = privateKey
}
// Check if there were any errors during row iteration
if err := rows.Err(); err != nil {
return nil, err
}
return result, nil
}
func (e *ETHNode) getAddressPk(address string) (string, error) {
str := "SELECT address, private_key FROM eth_balance WHERE address = ? LIMIT 1;"
row := e.MysqlDB.QueryRow(str, address)
// Variables to store the result
var storedAddress, privateKey string
// Scan the result into the variables
err := row.Scan(&storedAddress, &privateKey)
if err != nil {
if err == sql.ErrNoRows {
// Return an error if no rows are found
return "", fmt.Errorf("no address found for %s", address)
}
// Return any other error
return "", fmt.Errorf("error scanning row: %v", err)
}
// Return the private key
return privateKey, nil
}
// 检查钱包余额是否足够
// symbol=ETH时target_amount_eth传入实际gas费用+转账金额target_amount_usdt传入0即可
// symbol=USDT时target_amount_eth传入实际gas费用target_amount_usdt传入转账金额+冻结金额
func (e *ETHNode) checkBalance(symbol, address string, target_amount_eth, target_amount_usdt *big.Int) (bool, error) {
e.Wallets[address].mu.Lock()
defer e.Wallets[address].mu.Unlock()
if wallet, ok := e.Wallets[address]; ok {
switch symbol {
case "ETH":
// ETH余额不足支付转账金额+GAS费用
if wallet.eth_balance.balance.Cmp(target_amount_eth) < 0 {
return false, nil
} else {
return true, nil
}
case "USDT":
// gas费不足或USDT余额不足
if wallet.eth_balance.balance.Cmp(target_amount_eth) < 0 || wallet.usdt_balance.balance.Cmp(target_amount_usdt) < 0 {
return false, nil
} else {
return true, nil
}
default:
return false, fmt.Errorf("Symbol(%s) Error", symbol)
}
} else {
return false, fmt.Errorf("Balance(%s) not found", address)
}
}
func (e *ETHNode) loadWallets() error {
// 使用 JOIN 查询 ETH_wallets, ETH_balances 和 USDT_balances筛选 status 为 1 的钱包
query := `
SELECT
ew.address, ew.queue_id, ew.timestamp, ew.sign, ew.status,
eb.used_gas AS eth_used_gas, eb.balance AS eth_balance,
eb.success_tx_hash AS eth_successed_tx_hash, eb.failed_tx_hash AS eth_filed_tx_hash,
ub.freeze_num AS usdt_freeze_num, ub.balance AS usdt_balance,
ub.success_tx_hash AS usdt_successed_tx_hash, ub.failed_tx_hash AS usdt_filed_tx_hash
FROM ETH_wallets ew
LEFT JOIN ETH_balances eb ON ew.address = eb.address
LEFT JOIN USDT_balances ub ON ew.address = ub.address
WHERE ew.status = ?`
rows, err := e.SqliteDB.DB.Query(query, 1)
if err != nil {
return fmt.Errorf("failed to get wallets: %v", err)
}
defer rows.Close()
parseBigInt := func(val sql.NullString) *big.Int {
if val.Valid && strings.TrimSpace(val.String) != "" {
if bi, ok := new(big.Int).SetString(strings.TrimSpace(val.String), 10); ok {
return bi
}
}
return big.NewInt(0)
}
parseHashList := func(val sql.NullString) []string {
if !val.Valid || val.String == "" {
return []string{}
}
parts := strings.Split(val.String, ",")
res := make([]string, 0, len(parts))
for _, p := range parts {
p = strings.TrimSpace(p)
if p != "" {
res = append(res, p)
}
}
return res
}
wallets := make(map[string]*Wallets)
addresses := []string{}
for rows.Next() {
var (
addrStr, queueID, sign sql.NullString
timestamp sql.NullInt64
status sql.NullInt64
ethUsedGas, ethBalance sql.NullString
ethSuccess, ethFailed sql.NullString
usdtFreeze, usdtBalance sql.NullString
usdtSuccess, usdtFailed sql.NullString
)
if err := rows.Scan(
&addrStr, &queueID, &timestamp, &sign, &status,
&ethUsedGas, &ethBalance,
&ethSuccess, &ethFailed,
&usdtFreeze, &usdtBalance,
&usdtSuccess, &usdtFailed,
); err != nil {
return fmt.Errorf("failed to scan row: %v", err)
}
if !addrStr.Valid {
continue
}
addr := strings.ToLower(addrStr.String)
wallet := &Wallets{
address: addr,
queueId: queueID.String,
sign: sign.String,
timestamp: func() uint64 {
if timestamp.Valid {
return uint64(timestamp.Int64)
}
return 0
}(),
status: func() int {
if status.Valid {
return int(status.Int64)
}
return 0
}(),
}
wallet.eth_balance = &eth_balance{
symbol: "ETH",
used_gas: parseBigInt(ethUsedGas),
balance: parseBigInt(ethBalance),
successed_tx_hash: parseHashList(ethSuccess),
failed_tx_hash: parseHashList(ethFailed),
}
wallet.usdt_balance = &usdt_balance{
symbol: "USDT",
freeze_num: parseBigInt(usdtFreeze),
balance: parseBigInt(usdtBalance),
successed_tx_hash: parseHashList(usdtSuccess),
failed_tx_hash: parseHashList(usdtFailed),
}
addresses = append(addresses, addr)
wallets[addr] = wallet
}
if err := rows.Err(); err != nil {
return fmt.Errorf("error occurred while iterating rows: %v", err)
}
pks, err := e.getAddressesPks(addresses)
if err != nil {
return fmt.Errorf("inital balance private key error: %v", err)
}
e.mu.Lock()
e.Wallets = wallets
for address, pk := range pks {
e.Wallets[address].pk = pk
}
e.mu.Unlock()
return nil
}
func (e *ETHNode) loadUnConfirmedTxs() error {
query_str := "SELECT queue_id, tx_type, chain, symbol, from_addr, to_addr, tx_hash, height, amount, status FROM eth_unconfirmed_tx WHERE status = ?"
data, err := e.SqliteDB.Query_(query_str, 2)
if err != nil {
return fmt.Errorf("failed to get columns: %v", err)
}
for _, row := range data {
// 提取各个字段并确保正确转换为目标类型
queueId, ok := row["queue_id"].(string)
if !ok {
return fmt.Errorf("invalid type for queue_id, expected string but got %T", row["queue_id"])
}
// 你可以继续提取其他字段并做类似类型转换
txType, _ := row["tx_type"].(int) // 假设 tx_type 是 int 类型
chain, _ := row["chain"].(string)
symbol, _ := row["symbol"].(string)
fromAddr, _ := row["from_addr"].(string)
toAddr, _ := row["to_addr"].(string)
tx_hash, _ := row["tx_hash"].(string)
height, _ := row["height"].(int) // 假设 height 是 int 类型
amount, _ := row["amount"].(string) // amount 是字符串类型
status, _ := row["status"].(int) // status 是 int 类型
big_amount := new(big.Int)
big_Amount, ok := big_amount.SetString(amount, 10)
if !ok {
return fmt.Errorf("amount to bigInt error: %v", err)
}
e.UnConfirmedTxs.mu.Lock()
// 填充 Transaction 结构体
e.UnConfirmedTxs.Transactions[queueId] = message.Transaction{
QueueId: queueId,
TxType: txType,
Chain: chain,
Symbol: symbol,
From: fromAddr,
To: toAddr,
TxHash: tx_hash,
Height: uint64(height),
Amount: big_Amount,
Status: status,
}
e.UnConfirmedTxs.mu.Unlock()
}
return nil
}
// 获取交易nonce
func (e *ETHNode) getTransactionNonce(address string) (uint64, error) {
nonce, err := e.RpcClient.PendingNonceAt(e.Ctx, common.HexToAddress(address))
if err != nil {
return 0, fmt.Errorf("failed to get nonce: %w", err)
}
return nonce, nil
}
// 构建交易
func (e *ETHNode) contractTx(symbol, from, to string, amount float64) (*types.Transaction, error) {
nonce, err := e.getTransactionNonce(from)
if err != nil {
return nil, fmt.Errorf("failed to get nonce: %v", err)
}
return e.buildContractTxWithNonce(symbol, to, amount, nonce)
}
// 构建交易(指定 nonce
func (e *ETHNode) buildContractTxWithNonce(symbol, to string, amount float64, nonce uint64) (*types.Transaction, error) {
e.mu.Lock()
maxFeePerGas, maxPriorityFeePerGas, gasLimit := e.NetInfo.GasFeeCap, e.NetInfo.GasTipCap, e.NetInfo.GasLimit
if maxFeePerGas == nil || maxPriorityFeePerGas == nil || gasLimit == 0 {
e.mu.Unlock()
return nil, fmt.Errorf("chain data not initialized!")
}
netID := e.NetID
e.mu.Unlock()
addr := common.HexToAddress(to)
eip1559Tx := &types.DynamicFeeTx{
ChainID: netID,
Nonce: nonce,
GasTipCap: maxPriorityFeePerGas,
GasFeeCap: maxFeePerGas,
Gas: gasLimit,
To: &addr,
Data: []byte{},
}
switch symbol {
case "ETH":
eip1559Tx.Value = utils.Float64ToBigIntETH(amount)
case "USDT":
eip1559Tx.Value = big.NewInt(0)
eip1559Tx.To = &e.USDT.Address
data, err := e.USDT.ABI.Pack("transfer", common.HexToAddress(to), utils.Float64ToBigIntUSDT(amount))
if err != nil {
return nil, fmt.Errorf("failed to pack transfer data: %v", err)
}
eip1559Tx.Data = data
default:
return nil, fmt.Errorf("ETH NetWork error symbol: %s", symbol)
}
return types.NewTx(eip1559Tx), nil
}
// 签名交易
func (e *ETHNode) signTx(tx *types.Transaction, from string) (*types.Transaction, error) {
// originalKey := e.decodePrivatekey(from)
// if originalKey == "" {
// return nil, fmt.Errorf("failed to query private key for address: %s", from)
// }
e.mu.Lock()
originalKey := e.Wallets[from].pk
e.mu.Unlock()
privateKey, err := crypto.HexToECDSA(originalKey)
if err != nil {
return nil, fmt.Errorf("failed to parse private key: %v", err)
}
signer := types.LatestSignerForChainID(e.NetID)
return types.SignTx(tx, signer, privateKey)
}
// 发送交易
func (e *ETHNode) sendTransaction(tx *types.Transaction) (string, error) {
// 发送交易
err := e.RpcClient.SendTransaction(e.Ctx, tx)
if err != nil {
return "", fmt.Errorf("failed to send transaction: %v", err)
}
txHash := tx.Hash().Hex()
return txHash, nil
}
// 验证交易
func (e *ETHNode) checkTransaction(tx_hash string) (bool, *big.Int, error) {
receipt, err := e.RpcClient.TransactionReceipt(e.Ctx, common.HexToHash(tx_hash))
if err != nil {
return false, nil, fmt.Errorf("check tx(%s) error: %v", tx_hash, err)
}
if receipt.Status == types.ReceiptStatusSuccessful {
effectiveGasPrice := receipt.EffectiveGasPrice
actual_gas := big.NewInt(int64(receipt.GasUsed))
actualGasCost := new(big.Int).Mul(actual_gas, effectiveGasPrice)
return true, actualGasCost, nil
} else {
return false, nil, nil
}
}
// 完整的执行交易
func (e *ETHNode) handleTx(symbol, from, to string, amount float64) error {
unsignedTx, err := e.contractTx(symbol, from, to, amount)
if err != nil {
return err
}
signedTx, err := e.signTx(unsignedTx, from)
if err != nil {
return err
}
tx_hash, err := e.sendTransaction(signedTx)
if err != nil {
return err
}
var big_amount *big.Int
switch symbol {
case "ETH":
big_amount = utils.Float64ToBigIntETH(amount)
case "USDT":
big_amount = utils.Float64ToBigIntUSDT(amount)
default:
return fmt.Errorf("symbol(%s) error at func handleTx", symbol)
}
e.UnConfirmedTxs.mu.Lock()
e.UnConfirmedTxs.Transactions[tx_hash] = message.Transaction{
Chain: "ETH",
Symbol: symbol,
From: from,
To: to,
TxHash: tx_hash,
Amount: big_amount,
Status: constant.STATUS_UNTRANSFER,
}
e.UnConfirmedTxs.mu.Unlock()
return nil
}
func (e *ETHNode) listenETHTransactions() error {
headers := make(chan *types.Header, 10)
// 负责重连
for {
// 订阅新区块头
sub, err := e.WsClient.SubscribeNewHead(e.Ctx, headers)
if err != nil {
fmt.Println("❌ 订阅ETH交易失败, 5秒后重试:", err)
time.Sleep(5 * time.Second)
continue
}
fmt.Println("✅ ETH交易订阅成功")
// 处理新区块
for {
select {
case err := <-sub.Err():
fmt.Println("⚠️ ETH交易订阅异常准备重连:", err)
sub.Unsubscribe()
time.Sleep(3 * time.Second)
goto reconnect
case header := <-headers:
// 每当有新区块,检查待确认交易
currentHeight := header.Number.Uint64()
e.updateNetInfo(currentHeight)
go e.handleETHEvent(header)
go e.confirm()
case <-e.Ctx.Done():
fmt.Println("🛑 收到停止信号退出ETH交易监听")
sub.Unsubscribe()
return e.Ctx.Err()
}
}
reconnect:
}
}
func (e *ETHNode) handleETHEvent(header *types.Header) {
height := header.Number.Uint64()
// 获取区块中的所有交易
block, err := e.RpcClient.BlockByHash(e.Ctx, header.Hash())
if err != nil {
log.Printf("无法获取区块信息: %v", err)
return
}
// 遍历区块中的每笔交易
for _, tx := range block.Transactions() {
txHash := tx.Hash().Hex()
// 只处理ETH转账Value > 0
if tx.Value().Sign() <= 0 {
continue
}
// 使用 types.Sender 获取发送方地址
signer := types.LatestSignerForChainID(e.NetID)
from, err := types.Sender(signer, tx)
if err != nil {
log.Println("获取发送方地址失败:", err)
continue
}
toAddr := ""
if tx.To() != nil {
toAddr = strings.ToLower(tx.To().Hex())
}
fromAddr := strings.ToLower(from.Hex())
// 获取交易金额
amount := utils.BigIntETHToFloat64(tx.Value())
// toAddr和监听钱包一致表示(充值)
if _, ok := e.Wallets[toAddr]; ok {
msg, err := e.MessageServer.FindTopupMsgWithToaddress("ETH", toAddr)
if err != nil {
log.Printf("❌ 未查找到ETH充值消息中有address(%s)信息", toAddr)
continue
}
resp := message.TopupMsg_resp{
QueueId: msg.QueueId,
Chain: "ETH",
Symbol: "ETH",
FromAddress: fromAddr,
Address: toAddr,
TxHash: txHash,
Amount: amount,
BlockHeight: height,
Status: constant.STATUS_PENDING,
}
go e.asyncSendMsgToListen(resp, 3, 5*time.Second)
e.UnConfirmedTxs.mu.Lock()
e.UnConfirmedTxs.Transactions[txHash] = message.Transaction{
QueueId: msg.QueueId,
TxType: 0,
Chain: "ETH",
Symbol: "ETH",
From: fromAddr,
To: toAddr,
TxHash: txHash,
Height: height,
Amount: tx.Value(),
Status: constant.STATUS_PENDING,
}
e.UnConfirmedTxs.mu.Unlock()
}
// fromAddr和监听钱包一致表示(提现/支付)
if _, ok := e.Wallets[fromAddr]; ok {
var msg any
var ee error
msg, ee = e.MessageServer.FindPayMsgWithToaddress("ETH", fromAddr)
if ee != nil {
msg, ee = e.MessageServer.FindWithdrawMsgWithToaddress("ETH", fromAddr)
if ee != nil {
log.Printf("❌ 未查找到ETH提现和支付消息中有address(%s)信息", fromAddr)
continue
}
}
switch v := msg.(type) {
case message.WithdrawMsg_req:
if v.FromAddress == fromAddr && v.ToAddress == toAddr && v.Amount == amount {
resp := message.WithdrawMsg_resp{
QueueId: v.QueueId,
Chain: "ETH",
Symbol: v.Symbol, // 使用消息中的Symbol应该是ETH
FromAddress: fromAddr,
ToAddress: toAddr,
TxHash: txHash,
Amount: amount,
Fee: v.Fee,
BlockHeight: height,
Status: constant.STATUS_PENDING,
}
go e.asyncSendMsgToListen(resp, 3, 5*time.Second)
e.UnConfirmedTxs.mu.Lock()
e.UnConfirmedTxs.Transactions[txHash] = message.Transaction{
QueueId: v.QueueId,
TxType: 1, // 提现类型
Chain: "ETH",
Symbol: v.Symbol, // 使用消息中的Symbol应该是ETH
From: fromAddr,
To: toAddr,
TxHash: txHash,
Height: height,
Amount: tx.Value(),
Status: constant.STATUS_PENDING,
}
e.UnConfirmedTxs.mu.Unlock()
}
case message.PayMsg_req:
for to, transaction := range v.Transactions {
if v.FromAddress == fromAddr && to == toAddr && transaction.Amount == amount {
resp := transaction
resp.Chain = "ETH"
resp.Symbol = v.Symbol // 使用消息中的Symbol可能是ETH或USDT
resp.Status = constant.STATUS_PENDING
go e.asyncSendMsgToListen(resp, 3, 5*time.Second)
e.UnConfirmedTxs.mu.Lock()
e.UnConfirmedTxs.Transactions[txHash] = message.Transaction{
QueueId: v.QueueId,
TxType: 2, // 支付类型
Chain: "ETH",
Symbol: v.Symbol, // 使用消息中的Symbol
From: fromAddr,
To: toAddr,
TxHash: txHash,
Height: height,
Amount: tx.Value(),
Status: constant.STATUS_PENDING,
}
e.UnConfirmedTxs.mu.Unlock()
}
}
default:
}
}
}
}
func (e *ETHNode) listenUSDTTransactions() error {
// 过滤掉非USDT数据
query := ethereum.FilterQuery{
Addresses: []common.Address{e.USDT.Address},
}
// 负责重连
for {
// 订阅日志
sub, err := e.WsClient.SubscribeFilterLogs(e.Ctx, query, e.USDT.LogsChan)
if err != nil {
fmt.Println("❌ USDT交易订阅失败, 5秒后重试:", err)
time.Sleep(5 * time.Second)
continue
}
fmt.Println("✅ USDT交易订阅成功")
// 处理事件
for {
select {
case err := <-sub.Err():
fmt.Println("⚠️ USDT交易订阅异常准备重连:", err)
sub.Unsubscribe() // 清理旧订阅
time.Sleep(3 * time.Second)
goto reconnect // 跳出内层循环,回到外层重新订阅
case vLog := <-e.USDT.LogsChan:
go e.handleUSDTEvent(vLog) // 事件解析 + 分类传递链消息的通道是vLog而非ch且一次只传递一笔交易
case <-e.Ctx.Done():
fmt.Println("🛑 收到停止信号退出USDT交易监听")
sub.Unsubscribe()
return e.Ctx.Err()
}
}
reconnect:
}
}
func (e *ETHNode) handleUSDTEvent(vLog types.Log) {
from := common.HexToAddress(vLog.Topics[1].Hex())
to := common.HexToAddress(vLog.Topics[2].Hex())
height := vLog.BlockNumber
fromAddr := strings.ToLower(from.Hex())
toAddr := strings.ToLower(to.Hex())
var transferEvent struct{ Value *big.Int }
if err := e.USDT.ABI.UnpackIntoInterface(&transferEvent, "Transfer", vLog.Data); err != nil {
fmt.Println("ABI 解析错误:", err)
return
}
txHash := vLog.TxHash.Hex()
value_float := utils.BigIntUSDTToFloat64(transferEvent.Value)
// toAddr和监听钱包一致表示(充值)
if _, ok := e.Wallets[toAddr]; ok {
msg, err := e.MessageServer.FindTopupMsgWithToaddress("ETH", toAddr)
if err != nil {
log.Printf("❌ 未查找到ETH充值消息中有address(%s)信息", toAddr)
return
}
resp := message.TopupMsg_resp{
QueueId: msg.QueueId,
Chain: "ETH",
Symbol: "USDT",
FromAddress: fromAddr,
Address: toAddr,
TxHash: txHash,
Amount: value_float,
BlockHeight: height,
Status: constant.STATUS_PENDING,
}
go e.asyncSendMsgToListen(resp, 3, 5*time.Second)
e.UnConfirmedTxs.mu.Lock()
e.UnConfirmedTxs.Transactions[txHash] = message.Transaction{
QueueId: msg.QueueId,
TxType: 0,
Chain: "ETH",
Symbol: "USDT",
From: fromAddr,
To: toAddr,
TxHash: txHash,
Height: height,
Amount: transferEvent.Value,
Status: constant.STATUS_PENDING,
}
e.UnConfirmedTxs.mu.Unlock()
}
// fromAddr和监听钱包一致表示(提现/支付)
if _, ok := e.Wallets[fromAddr]; ok {
var msg any
var ee error
msg, ee = e.MessageServer.FindPayMsgWithToaddress("ETH", fromAddr)
if ee != nil {
msg, ee = e.MessageServer.FindWithdrawMsgWithToaddress("ETH", fromAddr)
if ee != nil {
log.Printf("❌ 未查找到ETH提现和支付消息中有address(%s)信息", fromAddr)
return
}
}
switch v := msg.(type) {
case message.WithdrawMsg_req:
if v.FromAddress == fromAddr && v.ToAddress == toAddr && v.Amount == value_float {
resp := message.WithdrawMsg_resp{
QueueId: v.QueueId,
Chain: "ETH",
Symbol: "USDT",
FromAddress: fromAddr,
ToAddress: toAddr,
TxHash: txHash,
Amount: value_float,
Fee: v.Fee,
BlockHeight: height,
Status: constant.STATUS_PENDING,
}
go e.asyncSendMsgToListen(resp, 3, 5*time.Second)
e.UnConfirmedTxs.mu.Lock()
e.UnConfirmedTxs.Transactions[txHash] = message.Transaction{
QueueId: v.QueueId,
TxType: 1, // 提现类型
Chain: "ETH",
Symbol: v.Symbol, // 使用消息中的Symbol应该是USDT
From: fromAddr,
To: toAddr,
TxHash: txHash,
Height: height,
Amount: transferEvent.Value,
Status: constant.STATUS_PENDING,
}
e.UnConfirmedTxs.mu.Unlock()
}
case message.PayMsg_req:
for to, transaction := range v.Transactions {
if v.FromAddress == fromAddr && to == toAddr && transaction.Amount == value_float {
resp := transaction
resp.Chain = "ETH"
resp.Symbol = v.Symbol // 使用消息中的Symbol应该是USDT
resp.Status = constant.STATUS_PENDING
go e.asyncSendMsgToListen(resp, 3, 5*time.Second)
e.UnConfirmedTxs.mu.Lock()
e.UnConfirmedTxs.Transactions[txHash] = message.Transaction{
QueueId: v.QueueId,
TxType: 2, // 支付类型
Chain: "ETH",
Symbol: v.Symbol, // 使用消息中的Symbol应该是USDT
From: fromAddr,
To: toAddr,
TxHash: txHash,
Height: height,
Amount: transferEvent.Value,
Status: constant.STATUS_PENDING,
}
e.UnConfirmedTxs.mu.Unlock()
}
}
default:
}
}
}
func (e *ETHNode) confirm() {
e.mu.Lock()
unconfirmedTxs := e.UnConfirmedTxs
now_height := e.NetInfo.Height
e.mu.Unlock()
var responses []any
e.UnConfirmedTxs.mu.Lock()
for txHash, tx := range unconfirmedTxs.Transactions {
// 高度成熟:当前高度 >= 交易高度 + 确认高度
if now_height >= tx.Height+e.ConfirmHeight {
check_result, actual_gas, err := e.checkTransaction(txHash)
if err != nil {
log.Printf("Transaction(%s) check error: %v, 跳过此交易", txHash, err)
continue
}
var status int
var msg any
var ee error
// 交易检查成功通过queue_id找到对应的消息并根据消息发送回
// 统一处理交易结果
if check_result {
msg, ee = e.MessageServer.FindMsgWithQueueID("ETH", tx.QueueId, tx.TxType)
if ee != nil {
log.Printf("Query Message error: %v, queue_id: %s, tx_type: %d", ee, tx.QueueId, tx.TxType)
status = constant.STATUS_FAILED
// 如果找不到消息,仍然需要处理交易失败的情况
msg = nil
} else {
status = constant.STATUS_SUCCESS
}
} else {
status = constant.STATUS_FAILED
// 交易失败时也需要查找消息以发送响应
msg, ee = e.MessageServer.FindMsgWithQueueID("ETH", tx.QueueId, tx.TxType)
if ee != nil {
log.Printf("Query Message error for failed tx: %v, queue_id: %s, tx_type: %d", ee, tx.QueueId, tx.TxType)
msg = nil
}
}
var float_amount = utils.BigIntToFloat64(tx.Symbol, tx.Amount)
// 如果找不到消息,跳过处理
if msg == nil {
delete(e.UnConfirmedTxs.Transactions, txHash)
continue
}
switch v := msg.(type) {
case message.TopupMsg_req:
if status == constant.STATUS_SUCCESS {
// 确认充值消息通道返回resp并修改钱包、数据库
switch tx.Symbol {
case "ETH":
e.Wallets[tx.To].eth_balance.balance = new(big.Int).Add(e.Wallets[tx.To].eth_balance.balance, tx.Amount)
case "USDT":
e.Wallets[tx.To].usdt_balance.balance = new(big.Int).Add(e.Wallets[tx.To].usdt_balance.balance, tx.Amount)
default:
log.Printf("error symbol(%s): %v, 跳过此交易", tx.Symbol, tx.Symbol)
delete(e.UnConfirmedTxs.Transactions, txHash)
continue
}
}
response := message.TopupMsg_resp{
QueueId: tx.QueueId,
Address: tx.To,
FromAddress: tx.From,
Status: status,
Chain: tx.Chain,
Symbol: tx.Symbol,
Amount: float_amount,
TxHash: txHash,
BlockHeight: tx.Height,
}
responses = append(responses, response) // 将消息提交至responses
case message.WithdrawMsg_req:
if status == constant.STATUS_SUCCESS {
// 将gas费添加到钱包中
e.Wallets[tx.From].eth_balance.used_gas = new(big.Int).Add(e.Wallets[tx.From].eth_balance.used_gas, actual_gas)
switch tx.Symbol {
case "ETH":
e.Wallets[tx.From].eth_balance.balance = new(big.Int).Sub(e.Wallets[tx.From].eth_balance.balance, tx.Amount)
case "USDT":
e.Wallets[tx.From].usdt_balance.balance = new(big.Int).Sub(e.Wallets[tx.From].usdt_balance.balance, tx.Amount)
// 提现USDT时会有一定数量的USDT作为gas费冻结
e.Wallets[tx.From].usdt_balance.freeze_num = new(big.Int).Add(e.Wallets[tx.From].usdt_balance.freeze_num, utils.Float64ToBigIntUSDT(v.Fee))
default:
log.Printf("error symbol(%s): %v, 跳过此交易", tx.Symbol, tx.Symbol)
delete(e.UnConfirmedTxs.Transactions, txHash)
continue
}
}
response := message.WithdrawMsg_resp{
QueueId: tx.QueueId,
Chain: tx.Chain,
Symbol: tx.Symbol,
Status: status,
Amount: float_amount,
Fee: v.Fee,
TxHash: txHash,
FromAddress: tx.From,
ToAddress: tx.To,
BlockHeight: tx.Height,
}
responses = append(responses, response) // 将消息提交至responses
case message.PayMsg_req:
e.Wallets[tx.From].eth_balance.used_gas = new(big.Int).Add(e.Wallets[tx.From].eth_balance.used_gas, actual_gas)
switch tx.Symbol {
case "ETH":
e.Wallets[tx.From].eth_balance.balance = new(big.Int).Sub(e.Wallets[tx.From].eth_balance.balance, tx.Amount)
case "USDT":
e.Wallets[tx.From].usdt_balance.balance = new(big.Int).Sub(e.Wallets[tx.From].usdt_balance.balance, tx.Amount)
// 支付USDT时需要冻结部分USDT作为gas费(暂不执行)
// e.Wallets[tx.From].usdt_balance.freeze_num = new(big.Int).Add(e.Wallets[tx.From].usdt_balance.freeze_num, tx.Amount)
default:
log.Printf("error symbol(%s): %v, 跳过此交易", tx.Symbol, tx.Symbol)
delete(e.UnConfirmedTxs.Transactions, txHash)
continue
}
response := message.PayData{
QueueId: v.QueueId,
Chain: v.Chain,
Symbol: v.Symbol,
TxHash: txHash,
ToAddress: tx.To,
Amount: float_amount,
BlockHeight: tx.Height,
Status: status,
}
responses = append(responses, response)
default:
log.Printf("未知的消息类型: %v, 跳过此交易", v)
delete(e.UnConfirmedTxs.Transactions, txHash)
continue
}
delete(e.UnConfirmedTxs.Transactions, txHash)
}
}
e.UnConfirmedTxs.mu.Unlock()
if len(responses) > 0 {
for _, v := range responses {
go e.asyncSendMsgToListen(v, 3, 5*time.Second)
}
}
}
func (e *ETHNode) handleListen_Topup_req(msg message.TopupMsg_req) {
pk, err := e.getAddressPk(msg.Address)
if err != nil {
log.Printf("Query balance(%s-%s) private_key error: %v", msg.Chain, msg.Address, err)
return
}
// 添加到钱包
e.mu.Lock()
e.Wallets[msg.Address] = &Wallets{
address: msg.Address,
queueId: msg.QueueId,
pk: pk,
eth_balance: &eth_balance{
symbol: "ETH",
used_gas: big.NewInt(0),
balance: big.NewInt(0),
successed_tx_hash: []string{},
failed_tx_hash: []string{},
},
usdt_balance: &usdt_balance{
symbol: "USDT",
freeze_num: big.NewInt(0),
balance: big.NewInt(0),
successed_tx_hash: []string{},
failed_tx_hash: []string{},
},
timestamp: msg.Timestamp,
sign: msg.Sign,
status: constant.STATUS_SUCCESS,
}
e.mu.Unlock()
// 记录到钱包数据库
go func() {
str1 := "INSERT INTO ETH_wallets (address, queue_id, timestamp, sign, status) VALUES (?,?,?,?,?)"
params1 := []any{msg.Address, msg.QueueId, msg.Timestamp, msg.Sign, constant.STATUS_SUCCESS}
str2 := "INSERT INTO ETH_balances (address) VALUES (?)"
params2 := []any{msg.Address}
str3 := "INSERT INTO USDT_balances (address) VALUES (?)"
params3 := []any{msg.Address}
err = e.SqliteDB.ExecuteTransactions([]string{str1, str2, str3}, [][]any{params1, params2, params3})
if err != nil {
log.Printf("Received ListenServer Topup_req msg: insert sqlite3 db error: %v", err)
}
}()
}
func (e *ETHNode) handleListen_Withdraw_req(msg message.WithdrawMsg_req) {
// 先获得当前最高gas费用
e.NetInfo.mu.Lock()
maxGas := e.NetInfo.GasFeeCap
e.NetInfo.mu.Unlock()
var target_amount_eth, target_amount_usdt *big.Int
// 将 msg.Amount 和 msg.Fee 转换为 big.Int只调用一次
amountBigInt := utils.Float64ToBigInt(msg.Symbol, msg.Amount)
feeBigInt := utils.Float64ToBigInt(msg.Symbol, msg.Fee)
switch msg.Symbol {
case "ETH":
// 计算目标金额
target_amount_eth = new(big.Int).Add(maxGas, amountBigInt) // maxGas + msg.Amount
target_amount_eth.Add(target_amount_eth, feeBigInt) // (maxGas + msg.Amount) + msg.Fee
target_amount_usdt = big.NewInt(0)
case "USDT":
target_amount_eth = maxGas
target_amount_usdt = new(big.Int).Add(amountBigInt, feeBigInt)
default:
return
}
// 构建相应通用数据Status根据后续情况变化
result_msg := message.WithdrawMsg_resp{
QueueId: msg.QueueId,
Chain: msg.Chain,
Symbol: msg.Symbol,
Amount: msg.Amount,
Fee: msg.Fee,
FromAddress: msg.FromAddress,
ToAddress: msg.ToAddress,
}
check_result, err := e.checkBalance(msg.Symbol, msg.FromAddress, target_amount_eth, target_amount_usdt)
// 余额校验错误,绕过转账,返回错误响应
if err != nil {
log.Printf("check balance error: %v", err)
result_msg.Status = constant.STATUS_ERROR
go e.asyncSendMsgToListen(result_msg, 3, 5*time.Second)
return
}
// 校验成功
if check_result {
// 开始转账
err := e.Transfer(msg.FromAddress, msg.ToAddress, msg.Symbol, msg.Amount, msg.Fee)
// 转账失败,返回转账失败结果
if err != nil {
log.Printf("withdraw - transfer error: %v", err)
result_msg.Status = constant.STATUS_ERROR
// 提现转账错误
go e.asyncSendMsgToListen(result_msg, 3, 5*time.Second)
return
}
// 转账成功等待chain server listen监听到该笔交易
log.Printf("withdraw - transfer success: QueueId(%s)", msg.QueueId)
} else { // 校验失败
// 提现转账账户余额不足
result_msg.Status = constant.STATUS_BALANCE_NOT_ENOUGH
go e.asyncSendMsgToListen(result_msg, 3, 5*time.Second)
return
}
}
func (e *ETHNode) handleListen_Pay_req(msg message.PayMsg_req) {
// 先获得当前最高gas费用
e.NetInfo.mu.Lock()
maxGas := e.NetInfo.GasFeeCap
e.NetInfo.mu.Unlock()
var target_amount_eth, target_amount_usdt *big.Int
// 将 msg.Amount 和 msg.Fee 转换为 big.Int只调用一次
amountBigInt := utils.Float64ToBigInt(msg.Symbol, msg.TotalAmount)
feeBigInt := utils.Float64ToBigInt(msg.Symbol, msg.TotalFee)
switch msg.Symbol {
case "ETH":
// 计算目标金额
target_amount_eth = new(big.Int).Add(maxGas, amountBigInt) // maxGas + msg.Amount
target_amount_eth.Add(target_amount_eth, feeBigInt) // (maxGas + msg.Amount) + msg.Fee
target_amount_usdt = big.NewInt(0)
case "USDT":
target_amount_eth = maxGas
target_amount_usdt = new(big.Int).Add(amountBigInt, feeBigInt)
default:
return
}
result_msg := message.PayMsg_resp{
QueueId: msg.QueueId,
Chain: msg.Chain,
Symbol: msg.Symbol,
FromAddress: msg.FromAddress,
Transactions: msg.Transactions,
}
check_result, err := e.checkBalance(msg.Symbol, msg.FromAddress, target_amount_eth, target_amount_usdt)
if err != nil {
log.Printf("check balance error: %v", err)
result_msg.PayStatus = constant.STATUS_ERROR
for _, tx := range result_msg.Transactions {
tx.Status = constant.STATUS_ERROR
}
go e.asyncSendMsgToListen(result_msg, 3, 5*time.Second)
return
}
if check_result {
for _, tx := range result_msg.Transactions {
err := e.Transfer(msg.FromAddress, tx.ToAddress, msg.Symbol, tx.Amount, tx.Fee)
if err != nil {
log.Println(err)
tx.Status = constant.STATUS_ERROR
} else {
tx.Status = constant.STATUS_PENDING
}
}
// 此时所有待转账的数据均已进行转账处理
// 转账成功等待chain server listen监听到该笔交易
log.Printf("pay - transfer success: QueueId(%s)", msg.QueueId)
} else {
// 提现转账账户余额不足
result_msg.PayStatus = constant.STATUS_BALANCE_NOT_ENOUGH
go e.asyncSendMsgToListen(result_msg, 3, 5*time.Second)
return
}
}
func (e *ETHNode) handleListen_Remove_req(msg message.RemoveListenMsg_req) {
e.mu.Lock()
delete(e.Wallets, msg.Address)
e.mu.Unlock()
result_msg := message.RemoveListenMsg_resp{
QueueId: msg.QueueId,
MsgType: msg.MsgType,
Chain: msg.Chain,
Symbol: msg.Symbol,
Status: constant.STATUS_SUCCESS,
}
str := "UPDATE ETH_wallets SET status = ? WHERE address = ?"
params := []any{0, msg.Address}
count, err := e.SqliteDB.Update(str, params...)
if err != nil || count != 1 {
log.Printf("Remove address(%s) error: count(%d)", msg.Address, count)
// result_msg.Status = constant.STATUS_FAILED
}
go e.asyncSendMsgToListen(result_msg, 3, 5*time.Second)
}
func (e *ETHNode) asyncSendMsgToListen(msg any, retries int, timeout time.Duration) {
for retries > 0 {
select {
case e.MessageServer.ChFromChainServer["ETH"] <- msg: // 如果通道没有满,就发送消息
// log.Printf("Sent message to rmq_ch_out: %v", msg)
return
case <-time.After(timeout): // 超时控制
log.Printf("Timeout sending message ETH-ChainServer to ListenServer: %v", msg)
retries--
if retries == 0 {
log.Printf("Max retries reached, giving up on sending message: %v", msg)
return
}
// 在超时后进行重试
log.Printf("Retrying sending message: %v, retries left: %d", msg, retries)
}
}
}