Files
m2pool_payment/internal/blockchain/eth/eth_prv.go

1612 lines
51 KiB
Go
Raw Normal View History

2025-11-13 17:08:38 +08:00
package eth
import (
"context"
"database/sql"
"fmt"
"log"
"m2pool-payment/internal/constant"
message "m2pool-payment/internal/msg"
"m2pool-payment/internal/utils"
"math/big"
"os"
2025-11-18 11:10:16 +08:00
"strconv"
2025-11-13 17:08:38 +08:00
"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) initTables() error {
sql_script_path := "../public/eth.sql"
sql_byte, err := os.ReadFile(sql_script_path)
if err != nil {
return fmt.Errorf("open msg-sql file error: %v", err)
}
err = e.SqliteDB.CreateTable(string(sql_byte))
if err != nil {
return fmt.Errorf("exec eth-sql error: %v", err)
}
return nil
}
2025-11-13 17:08:38 +08:00
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 (2 * 10^9 wei)
maxGasPrice := new(big.Int).SetUint64(2000000000) // 2 Gwei
2025-11-13 17:08:38 +08:00
if gasPrice.Cmp(maxGasPrice) > 0 {
log.Printf("⚠️ 建议gas price过高 (%v wei),使用上限 2 Gwei", new(big.Int).Div(gasPrice, big.NewInt(1e18)))
2025-11-13 17:08:38 +08:00
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(2000000) // 0.2 Gwei
2025-11-13 17:08:38 +08:00
// 计算最大费用 = 基础费用 + 优先级费用
maxFeePerGas := new(big.Int).Add(baseFee, maxPriorityFeePerGas)
// 设置最大费用上限为2 Gwei
maxFeeLimit := new(big.Int).SetUint64(2000000000) // 2 Gwei
2025-11-13 17:08:38 +08:00
if maxFeePerGas.Cmp(maxFeeLimit) > 0 {
log.Printf("⚠️ 计算的最大费用过高 (%v wei),使用上限 2 Gwei", new(big.Int).Div(maxFeePerGas, big.NewInt(1e18)))
2025-11-13 17:08:38 +08:00
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 {
2025-11-18 11:10:16 +08:00
log.Printf("需要的USDT%d钱包的USDT%d", target_amount_usdt, e.Wallets[address].usdt_balance.balance)
2025-11-13 17:08:38 +08:00
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,
2025-11-18 11:10:16 +08:00
eb.success_tx_hash AS eth_successed_tx_hash, eb.failed_tx_hash AS eth_failed_tx_hash,
2025-11-13 17:08:38 +08:00
ub.freeze_num AS usdt_freeze_num, ub.balance AS usdt_balance,
2025-11-18 11:10:16 +08:00
ub.success_tx_hash AS usdt_successed_tx_hash, ub.failed_tx_hash AS usdt_failed_tx_hash
2025-11-13 17:08:38 +08:00
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 = ?`
2025-11-18 11:10:16 +08:00
// 执行查询
rows, err := e.MysqlDB.Query(query, 1)
2025-11-13 17:08:38 +08:00
if err != nil {
return fmt.Errorf("failed to get wallets: %v", err)
}
defer rows.Close()
2025-11-18 11:10:16 +08:00
// 解析字段为 big.Int 类型
2025-11-13 17:08:38 +08:00
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
}
}
2025-11-18 11:10:16 +08:00
log.Printf("Invalid or empty string for big.Int: %s", val.String) // 添加调试日志
return big.NewInt(0) // 如果无法转换,返回 0
2025-11-13 17:08:38 +08:00
}
2025-11-18 11:10:16 +08:00
// 解析字符串为交易哈希列表
2025-11-13 17:08:38 +08:00
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
}
2025-11-18 11:10:16 +08:00
// 用来存储钱包信息
2025-11-13 17:08:38 +08:00
wallets := make(map[string]*Wallets)
addresses := []string{}
2025-11-18 11:10:16 +08:00
// 逐行处理查询结果
2025-11-13 17:08:38 +08:00
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
)
2025-11-18 11:10:16 +08:00
// 扫描查询结果到变量
2025-11-13 17:08:38 +08:00
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)
}
2025-11-18 11:10:16 +08:00
// 如果地址字段为空,则跳过该行
2025-11-13 17:08:38 +08:00
if !addrStr.Valid {
continue
}
addr := strings.ToLower(addrStr.String)
2025-11-18 11:10:16 +08:00
// 创建钱包对象并填充数据
2025-11-13 17:08:38 +08:00
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
}(),
}
2025-11-18 11:10:16 +08:00
// 解析 ETH 余额和信息
ethBalanceStr := strings.TrimSpace(ethBalance.String)
log.Printf("Parsed ETH balance: %s for address: %s", ethBalanceStr, addrStr.String) // 调试日志
ethBalanceValue, err := strconv.ParseFloat(ethBalanceStr, 64)
if err != nil {
log.Printf("Failed to parse ETH balance: %v, address: %s", err, addrStr.String)
ethBalanceValue = 0 // 给一个默认值
}
2025-11-13 17:08:38 +08:00
wallet.eth_balance = &eth_balance{
2025-11-18 11:10:16 +08:00
symbol: "ETH",
used_gas: parseBigInt(ethUsedGas),
// balance: big.NewInt(int64(ethBalanceValue)), // 使用 big.Int 存储数值
balance: utils.Float64ToBigInt("ETH", ethBalanceValue),
2025-11-13 17:08:38 +08:00
successed_tx_hash: parseHashList(ethSuccess),
failed_tx_hash: parseHashList(ethFailed),
}
2025-11-18 11:10:16 +08:00
// 解析 USDT 余额和信息
usdtBalanceStr := strings.TrimSpace(usdtBalance.String)
log.Printf("Parsed USDT balance: %s for address: %s", usdtBalanceStr, addrStr.String) // 调试日志
usdtBalanceValue, err := strconv.ParseFloat(usdtBalanceStr, 64)
if err != nil {
log.Printf("Failed to parse USDT balance: %v, address: %s", err, addrStr.String)
usdtBalanceValue = 0 // 给一个默认值
}
2025-11-13 17:08:38 +08:00
wallet.usdt_balance = &usdt_balance{
symbol: "USDT",
freeze_num: parseBigInt(usdtFreeze),
2025-11-18 11:10:16 +08:00
balance: utils.Float64ToBigInt("USDT", usdtBalanceValue), // 使用 big.Int 存储数值
2025-11-13 17:08:38 +08:00
successed_tx_hash: parseHashList(usdtSuccess),
failed_tx_hash: parseHashList(usdtFailed),
}
2025-11-18 11:10:16 +08:00
// 添加钱包地址到列表并将钱包信息存储到 map 中
2025-11-13 17:08:38 +08:00
addresses = append(addresses, addr)
wallets[addr] = wallet
}
2025-11-18 11:10:16 +08:00
// 检查是否有查询错误
2025-11-13 17:08:38 +08:00
if err := rows.Err(); err != nil {
return fmt.Errorf("error occurred while iterating rows: %v", err)
}
2025-11-18 11:10:16 +08:00
// 打印已加载的地址
log.Printf("ETH钱包加载成功%v", addresses)
// 如果有地址,获取钱包的私钥
2025-11-13 17:59:13 +08:00
if len(addresses) > 0 {
pks, err := e.getAddressesPks(addresses)
if err != nil {
2025-11-18 11:10:16 +08:00
return fmt.Errorf("initial balance private key error: %v", err)
2025-11-13 17:59:13 +08:00
}
e.mu.Lock()
e.Wallets = wallets
2025-11-18 11:10:16 +08:00
// 将私钥绑定到钱包对象中
2025-11-13 17:59:13 +08:00
for address, pk := range pks {
e.Wallets[address].pk = pk
}
e.mu.Unlock()
2025-11-13 17:08:38 +08:00
}
2025-11-18 11:10:16 +08:00
// 打印每个钱包的余额
for addr, balance := range e.Wallets {
log.Printf("钱包:%s, ETH余额: %s, USDT余额: %s", addr, balance.eth_balance.balance.String(), balance.usdt_balance.balance.String())
}
2025-11-13 17:08:38 +08:00
return nil
}
func (e *ETHNode) loadUnConfirmedTxs() error {
2025-11-18 11:10:16 +08:00
// 查询语句
query_str := "SELECT queue_id, tx_type, chain, symbol, from_addr, to_addr, tx_hash, height, amount, status FROM ETH_unconfirmed_tx WHERE status = ?"
// 执行查询
rows, err := e.MysqlDB.Query(query_str, 2)
2025-11-13 17:08:38 +08:00
if err != nil {
2025-11-18 11:10:16 +08:00
return fmt.Errorf("failed to query unconfirmed transactions: %v", err)
2025-11-13 17:08:38 +08:00
}
2025-11-18 11:10:16 +08:00
defer rows.Close() // 确保查询结束后关闭 rows
2025-11-13 17:08:38 +08:00
2025-11-18 11:10:16 +08:00
// 遍历查询结果
for rows.Next() {
var queueId, chain, symbol, fromAddr, toAddr, txHash, amount string
var txType, height, status int
// 读取当前行数据
if err := rows.Scan(&queueId, &txType, &chain, &symbol, &fromAddr, &toAddr, &txHash, &height, &amount, &status); err != nil {
return fmt.Errorf("failed to scan row: %v", err)
2025-11-13 17:08:38 +08:00
}
2025-11-18 11:10:16 +08:00
// 将 amount 转换为 big.Int
bigAmount := new(big.Int)
bigAmountParsed, success := bigAmount.SetString(amount, 10)
if !success {
return fmt.Errorf("failed to convert amount '%s' to big.Int", amount)
2025-11-13 17:08:38 +08:00
}
2025-11-18 11:10:16 +08:00
// 锁定并填充数据
2025-11-13 17:08:38 +08:00
e.UnConfirmedTxs.mu.Lock()
e.UnConfirmedTxs.Transactions[queueId] = message.Transaction{
QueueId: queueId,
TxType: txType,
Chain: chain,
Symbol: symbol,
From: fromAddr,
To: toAddr,
2025-11-18 11:10:16 +08:00
TxHash: txHash,
2025-11-13 17:08:38 +08:00
Height: uint64(height),
2025-11-18 11:10:16 +08:00
Amount: bigAmountParsed, // 使用转换后的 big.Int
2025-11-13 17:08:38 +08:00
Status: status,
}
e.UnConfirmedTxs.mu.Unlock()
}
2025-11-18 11:10:16 +08:00
// 检查是否存在查询错误
if err := rows.Err(); err != nil {
return fmt.Errorf("error during row iteration: %v", err)
}
2025-11-13 17:08:38 +08:00
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) handleHistoryETHEvent(height uint64) {
block, err := e.RpcClient.BlockByNumber(e.Ctx, big.NewInt(int64(height)))
if err != nil {
log.Println(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())
if _, ok := e.Wallets[toAddr]; ok {
msg, err := e.MessageServer.FindTopupMsgWithToaddress("ETH", toAddr)
if err != nil {
log.Printf("❌ 未查找到ETH充值消息中有address(%s)信息", toAddr)
continue
}
log.Printf("当前交易txHash:%s, fromAddr: %s, toAddr: %s, amount: %fETH", txHash, fromAddr, toAddr, amount)
log.Println(msg)
}
}
}
2025-11-13 17:08:38 +08:00
func (e *ETHNode) handleETHEvent(header *types.Header) {
height := header.Number.Uint64()
log.Printf("当前区块高度:%d", height)
2025-11-13 17:08:38 +08:00
// 获取区块中的所有交易
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 {
log.Printf("监听地址: %s交易信息%v", toAddr, tx)
2025-11-13 17:08:38 +08:00
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()
2025-11-18 11:10:16 +08:00
go func() {
str := "INSERT INTO ETH_unconfirmed_tx(queue_id, tx_type, chain, symbol, from_addr, to_addr, tx_hash, height, amount) VALUES (?,?,?,?,?,?,?,?,?)"
params := []any{msg.QueueId, 0, "ETH", "ETH", fromAddr, toAddr, txHash, height, amount}
_, err := e.MysqlDB.Insert(str, [][]any{params})
if err != nil {
log.Fatalf("INSERT ETH_unconfirmed_tx table error: %v", err)
return
}
}()
continue
2025-11-13 17:08:38 +08:00
}
// 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: "ETH", // 使用消息中的Symbol应该是ETH
2025-11-13 17:08:38 +08:00
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: "ETH", // 使用消息中的Symbol应该是ETH
2025-11-13 17:08:38 +08:00
From: fromAddr,
To: toAddr,
TxHash: txHash,
Height: height,
Amount: tx.Value(),
Status: constant.STATUS_PENDING,
}
e.UnConfirmedTxs.mu.Unlock()
2025-11-18 11:10:16 +08:00
go func() {
str := "INSERT INTO ETH_unconfirmed_tx(queue_id, tx_type, chain, symbol, from_addr, to_addr, tx_hash, height, amount) VALUES (?,?,?,?,?,?,?,?,?)"
params := []any{v.QueueId, 1, "ETH", "ETH", fromAddr, toAddr, txHash, height, amount}
_, err := e.MysqlDB.Insert(str, [][]any{params})
if err != nil {
log.Fatalf("INSERT ETH_unconfirmed_tx table error: %v", err)
return
}
}()
continue
2025-11-13 17:08:38 +08:00
}
case message.PayMsg_req:
if v.FromAddress == fromAddr && v.ToAddress == toAddr && v.Amount == amount {
resp := message.PayMsg_resp{
QueueId: v.QueueId,
Chain: "ETH",
Symbol: "ETH", // 使用消息中的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: 2, // 提现类型
Chain: "ETH",
Symbol: "ETH", // 使用消息中的Symbol应该是ETH
From: fromAddr,
To: toAddr,
TxHash: txHash,
Height: height,
Amount: tx.Value(),
Status: constant.STATUS_PENDING,
2025-11-13 17:08:38 +08:00
}
e.UnConfirmedTxs.mu.Unlock()
2025-11-18 11:10:16 +08:00
go func() {
str := "INSERT INTO ETH_unconfirmed_tx(queue_id, tx_type, chain, symbol, from_addr, to_addr, tx_hash, height, amount) VALUES (?,?,?,?,?,?,?,?,?)"
params := []any{v.QueueId, 2, "ETH", "ETH", fromAddr, toAddr, txHash, height, amount}
_, err := e.MysqlDB.Insert(str, [][]any{params})
if err != nil {
log.Fatalf("INSERT ETH_unconfirmed_tx table error: %v", err)
return
}
}()
continue
2025-11-13 17:08:38 +08:00
}
2025-11-13 17:08:38 +08:00
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()
2025-11-18 11:10:16 +08:00
go func() {
str := "INSERT INTO ETH_unconfirmed_tx(queue_id, tx_type, chain, symbol, from_addr, to_addr, tx_hash, height, amount) VALUES (?,?,?,?,?,?,?,?,?)"
params := []any{msg.QueueId, 0, "ETH", "USDT", fromAddr, toAddr, txHash, height, value_float}
_, err := e.MysqlDB.Insert(str, [][]any{params})
if err != nil {
log.Fatalf("INSERT ETH_unconfirmed_tx table error: %v", err)
return
}
}()
return
2025-11-13 17:08:38 +08:00
}
// 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: "USDT", // 使用消息中的Symbol应该是USDT
2025-11-13 17:08:38 +08:00
From: fromAddr,
To: toAddr,
TxHash: txHash,
Height: height,
Amount: transferEvent.Value,
Status: constant.STATUS_PENDING,
}
e.UnConfirmedTxs.mu.Unlock()
2025-11-18 11:10:16 +08:00
go func() {
str := "INSERT INTO ETH_unconfirmed_tx(queue_id, tx_type, chain, symbol, from_addr, to_addr, tx_hash, height, amount) VALUES (?,?,?,?,?,?,?,?,?)"
params := []any{v.QueueId, 1, "ETH", "USDT", fromAddr, toAddr, txHash, height, value_float}
_, err := e.MysqlDB.Insert(str, [][]any{params})
if err != nil {
log.Fatalf("INSERT ETH_unconfirmed_tx table error: %v", err)
return
}
}()
return
2025-11-13 17:08:38 +08:00
}
case message.PayMsg_req:
if v.FromAddress == fromAddr && v.ToAddress == toAddr && v.Amount == value_float {
resp := message.PayMsg_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,
2025-11-13 17:08:38 +08:00
}
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: "USDT", // 使用消息中的Symbol应该是USDT
From: fromAddr,
To: toAddr,
TxHash: txHash,
Height: height,
Amount: transferEvent.Value,
Status: constant.STATUS_PENDING,
}
e.UnConfirmedTxs.mu.Unlock()
2025-11-18 11:10:16 +08:00
go func() {
str := "INSERT INTO ETH_unconfirmed_tx(queue_id, tx_type, chain, symbol, from_addr, to_addr, tx_hash, height, amount) VALUES (?,?,?,?,?,?,?,?,?)"
params := []any{v.QueueId, 2, "ETH", "USDT", fromAddr, toAddr, txHash, height, value_float}
_, err := e.MysqlDB.Insert(str, [][]any{params})
if err != nil {
log.Fatalf("INSERT ETH_unconfirmed_tx table error: %v", err)
return
}
}()
return
2025-11-13 17:08:38 +08:00
}
default:
}
}
}
2025-11-18 11:10:16 +08:00
var tableMap = map[string]string{
"ETH": "ETH_balances",
"USDT": "USDT_balances",
}
2025-11-13 17:08:38 +08:00
func (e *ETHNode) confirm() {
2025-11-18 11:10:16 +08:00
2025-11-13 17:08:38 +08:00
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 {
2025-11-18 11:10:16 +08:00
2025-11-13 17:08:38 +08:00
// 高度成熟:当前高度 >= 交易高度 + 确认高度
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
2025-11-18 11:10:16 +08:00
2025-11-13 17:08:38 +08:00
// 如果找不到消息,仍然需要处理交易失败的情况
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
}
2025-11-18 11:10:16 +08:00
2025-11-13 17:08:38 +08:00
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,
}
2025-11-18 11:10:16 +08:00
// 修改钱包表
go func() {
2025-11-18 11:10:16 +08:00
var str string
var params []any
if status == constant.STATUS_SUCCESS {
str = "UPDATE " + tableMap[tx.Symbol] + " SET success_tx_hash = CONCAT(success_tx_hash, ?), balance = balance + ? WHERE address = ?"
params = []any{txHash + ",", float_amount, v.Address}
} else {
str = "UPDATE " + tableMap[tx.Symbol] + " SET failed_tx_hash = CONCAT(failed_tx_hash, ?) WHERE address = ?"
params = []any{txHash + ",", v.Address}
}
_, err := e.MysqlDB.Update(str, params)
if err != nil {
// 更详细的错误日志,包括 QueueId 和 Status
2025-11-18 11:10:16 +08:00
log.Printf("Failed to update success_tx_hash/failed_tx_hash for queue_id %s: %v", v.QueueId, err)
}
}()
// 修改待确认交易表
go func() {
str := "UPDATE ETH_unconfirmed_tx SET status = ? WHERE tx_hash = ?"
params := []any{status, txHash}
_, err := e.MysqlDB.Update(str, params)
if err != nil {
log.Printf("Failed to update ETH_unconfirmed_tx: %v", err)
}
}()
2025-11-13 17:08:38 +08:00
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,
}
2025-11-18 11:10:16 +08:00
// 修改钱包表
go func() {
var str, str1 string
var params, params1 []any
txHashWithComma := txHash + ","
if status == constant.STATUS_SUCCESS {
if tableMap[tx.Symbol] == "ETH_balances" {
str = "UPDATE ETH_balances SET success_tx_hash = CONCAT(success_tx_hash, ?), balance = balance - ?, used_gas = used_gas + ? WHERE address = ?"
params = []any{txHashWithComma, float_amount, utils.BigIntETHToFloat64(actual_gas), v.FromAddress}
str1 = ""
params1 = []any{}
} else {
str = "UPDATE USDT_balances SET success_tx_hash = CONCAT(success_tx_hash, ?), balance = balance - ?, freeze_num = freeze_num + ? WHERE address = ?"
params = []any{txHashWithComma, float_amount, v.Fee, v.FromAddress}
str1 = "UPDATE ETH_balances SET used_gas = used_gas + ? WHERE address = ?"
params1 = []any{utils.BigIntETHToFloat64(actual_gas), v.FromAddress}
}
} else {
str = "UPDATE " + tableMap[tx.Symbol] + " SET failed_tx_hash = CONCAT(failed_tx_hash, ?) WHERE address = ?"
params = []any{txHashWithComma, v.FromAddress}
str1 = ""
params1 = []any{}
}
err := e.MysqlDB.ExecuteTransactions([]string{str, str1}, [][]any{params, params1})
if err != nil {
// 更详细的错误日志,包括 QueueId 和 Status
log.Printf("Failed to update success_tx_hash/failed_tx_hash for queue_id %s: %v", v.QueueId, err)
}
}()
// 修改待确认交易表
go func() {
str := "UPDATE ETH_unconfirmed_tx SET status = ? WHERE tx_hash = ?"
params := []any{status, txHash}
_, err := e.MysqlDB.Update(str, params)
if err != nil {
log.Printf("Failed to update ETH_unconfirmed_tx: %v", err)
}
}()
2025-11-13 17:08:38 +08:00
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.PayMsg_resp{
QueueId: tx.QueueId,
Chain: tx.Chain,
Symbol: tx.Symbol,
Status: status,
Amount: float_amount,
Fee: v.Fee,
2025-11-13 17:08:38 +08:00
TxHash: txHash,
FromAddress: tx.From,
2025-11-13 17:08:38 +08:00
ToAddress: tx.To,
BlockHeight: tx.Height,
}
2025-11-18 11:10:16 +08:00
// 修改钱包表
go func() {
var str, str1 string
var params, params1 []any
txHashWithComma := txHash + ","
if status == constant.STATUS_SUCCESS {
if tableMap[tx.Symbol] == "ETH_balances" {
str = "UPDATE ETH_balances SET success_tx_hash = CONCAT(success_tx_hash, ?), balance = balance - ?, used_gas = used_gas + ? WHERE address = ?"
params = []any{txHashWithComma, float_amount, utils.BigIntETHToFloat64(actual_gas), v.FromAddress}
str1 = ""
params1 = []any{}
} else {
str = "UPDATE USDT_balances SET success_tx_hash = CONCAT(success_tx_hash, ?), balance = balance - ?, freeze_num = freeze_num + ? WHERE address = ?"
params = []any{txHashWithComma, float_amount, v.Fee, v.FromAddress}
str1 = "UPDATE ETH_balances SET used_gas = used_gas + ? WHERE address = ?"
params1 = []any{utils.BigIntETHToFloat64(actual_gas), v.FromAddress}
}
} else {
str = "UPDATE " + tableMap[tx.Symbol] + " SET failed_tx_hash = CONCAT(failed_tx_hash, ?) WHERE address = ?"
params = []any{txHashWithComma, v.FromAddress}
str1 = ""
params1 = []any{}
}
err := e.MysqlDB.ExecuteTransactions([]string{str, str1}, [][]any{params, params1})
if err != nil {
// 更详细的错误日志,包括 QueueId 和 Status
log.Printf("Failed to update success_tx_hash/failed_tx_hash for queue_id %s: %v", v.QueueId, err)
}
}()
// 修改待确认交易表
go func() {
str := "UPDATE ETH_unconfirmed_tx SET status = ? WHERE tx_hash = ?"
params := []any{status, txHash}
_, err := e.MysqlDB.Update(str, params)
if err != nil {
log.Printf("Failed to update ETH_unconfirmed_tx: %v", err)
}
}()
responses = append(responses, response) // 将消息提交至responses
2025-11-13 17:08:38 +08:00
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)
2025-11-13 17:59:13 +08:00
go e.asyncSendMsgToListen(message.TopupMsg_resp{
QueueId: msg.QueueId,
Chain: msg.Chain,
Symbol: msg.Symbol,
Address: msg.Address,
Status: msg.Status,
}, 3, 5*time.Second)
2025-11-13 17:08:38 +08:00
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}
2025-11-18 11:10:16 +08:00
err = e.MysqlDB.ExecuteTransactions([]string{str1, str2, str3}, [][]any{params1, params2, params3})
2025-11-13 17:08:38 +08:00
if err != nil {
log.Printf("Received ListenServer Topup_req msg: insert sqlite3 db error: %v", err)
}
go e.asyncSendMsgToListen(message.UpdateReqState{
QueueId: msg.QueueId,
MsgType: 0,
Status: constant.STATUS_SUCCESS,
}, 3, 5*time.Second)
2025-11-13 17:08:38 +08:00
}()
}
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,
}
2025-11-18 11:10:16 +08:00
2025-11-13 17:08:38 +08:00
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监听到该笔交易
go e.asyncSendMsgToListen(message.UpdateReqState{
QueueId: msg.QueueId,
MsgType: 1,
Status: constant.STATUS_SUCCESS,
}, 3, 5*time.Second)
2025-11-13 17:08:38 +08:00
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.Amount)
feeBigInt := utils.Float64ToBigInt(msg.Symbol, msg.Fee)
2025-11-13 17:08:38 +08:00
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根据后续情况变化
2025-11-13 17:08:38 +08:00
result_msg := message.PayMsg_resp{
QueueId: msg.QueueId,
Chain: msg.Chain,
Symbol: msg.Symbol,
Amount: msg.Amount,
Fee: msg.Fee,
FromAddress: msg.FromAddress,
ToAddress: msg.ToAddress,
2025-11-13 17:08:38 +08:00
}
check_result, err := e.checkBalance(msg.Symbol, msg.FromAddress, target_amount_eth, target_amount_usdt)
// 余额校验错误,绕过转账,返回错误响应
2025-11-13 17:08:38 +08:00
if err != nil {
log.Printf("check balance error: %v", err)
result_msg.Status = constant.STATUS_ERROR
2025-11-13 17:08:38 +08:00
go e.asyncSendMsgToListen(result_msg, 3, 5*time.Second)
return
}
// 校验成功
2025-11-13 17:08:38 +08:00
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
2025-11-13 17:08:38 +08:00
}
// 转账成功等待chain server listen监听到该笔交易
go e.asyncSendMsgToListen(message.UpdateReqState{
QueueId: msg.QueueId,
MsgType: 1,
Status: constant.STATUS_SUCCESS,
}, 3, 5*time.Second)
log.Printf("withdraw - transfer success: QueueId(%s)", msg.QueueId)
} else { // 校验失败
2025-11-13 17:08:38 +08:00
// 提现转账账户余额不足
result_msg.Status = constant.STATUS_BALANCE_NOT_ENOUGH
2025-11-13 17:08:38 +08:00
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}
2025-11-18 11:10:16 +08:00
_, err := e.MysqlDB.Update(str, params)
if err != nil {
log.Printf("Remove address(%s) error: %v", msg.Address, err)
2025-11-13 17:08:38 +08:00
// 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)
}
}
}