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

1143 lines
32 KiB
Go
Raw Normal View History

2025-10-16 18:54:27 +08:00
package eth
import (
"context"
"fmt"
"log"
"m2pool-payment/internal/db"
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"
"github.com/ethereum/go-ethereum/ethclient"
)
const erc20ABI = `
[
{
"constant": true,
"inputs": [{"name": "_owner", "type": "address"}],
"name": "balanceOf",
"outputs": [{"name": "balance", "type": "uint256"}],
"type": "function"
},
{
"constant": false,
"inputs": [
{"name": "_to", "type": "address"},
{"name": "_value", "type": "uint256"}
],
"name": "transfer",
"outputs": [{"name": "", "type": "bool"}],
"type": "function"
},
{
"anonymous": false,
"inputs": [
{"indexed": true, "name": "from", "type": "address"},
{"indexed": true, "name": "to", "type": "address"},
{"indexed": false,"name": "value","type": "uint256"}
],
"name": "Transfer",
"type": "event"
}
]
`
2025-10-31 13:46:58 +08:00
// USDT 合约地址(主网)
const USDTAddress = "0xdAC17F958D2ee523a2206206994597C13D831ec7"
// 状态码常量
const (
STATUS_FAILED = 0 // 失败
STATUS_SUCCESS = 1 // 成功
STATUS_PENDING = 2 // 待确认
STATUS_VERIFY_FAILED = 3 // 验证失败
)
2025-10-16 18:54:27 +08:00
type ETHNode struct {
2025-10-31 13:46:58 +08:00
decodeKey string // 私钥解密密钥,仅针对普通钱包
NetID *big.Int // 网络ID主网为1其他ID可通过rpc.NetworkID方法获取
Config message.ETHConfig
ConfirmHeight uint64
WsClient *ethclient.Client
RpcClient *ethclient.Client
Db db.MySQLPool
// {"address": message.TopupMsg_req{}, ...},仅针对充值
TopupMsg map[string]*message.TopupMsg_req
// {"fromAddress": message.WithdrawMsg_req{}, ...},仅针对提现
WithdrawMsg map[string]*message.WithdrawMsg_req
// {"fromAddress": message.PayMsg_req{}, ...}, 仅针对支付
PayMsg map[string]*message.PayMsg_req
// {"tx_hash": message.Transaction}
UnConfirmTxs map[string]*message.Transaction
LogsChan chan *types.Header
USDT *USDT
RealData *RealData
Ctx context.Context
Cancel context.CancelFunc
mu sync.Mutex
2025-10-16 18:54:27 +08:00
}
type USDT struct {
2025-10-31 13:46:58 +08:00
Address common.Address // USDT合约地址
ListeningAddresses map[string]any // 监听的USDT转账消息
ABI abi.ABI // USDT ABI
TransferSig common.Hash // USDT函数签名
LogsChan chan types.Log
}
type RealData struct {
mu sync.Mutex
Heihgt uint64
GasLimit uint64
GasTipCap *big.Int
GasFeeCap *big.Int
GasPrice *big.Int // 老版本转账使用的gas
2025-10-16 18:54:27 +08:00
}
func NewETHNode(cfg message.ETHConfig, decodeKey string) (*ETHNode, error) {
// 连入ETH节点的ws
ws_client, err := ethclient.Dial(cfg.WsURL)
if err != nil {
return nil, fmt.Errorf("failed to connect to Ethereum node: %w", err)
}
// 连入ETH节点的rpc
rpc_client, err := ethclient.Dial(cfg.RpcURL)
if err != nil {
return nil, fmt.Errorf("failed to connect to Ethereum node rpc: %w", err)
}
// 创建可取消的 context
ctx, cancel := context.WithCancel(context.Background())
// 获得net_id
netId, err := rpc_client.NetworkID(ctx)
if err != nil {
cancel()
return nil, fmt.Errorf("failed to connect to get node net_id: %w", err)
}
// 构造USDT合约相关
usdt := &USDT{}
usdt.Address = common.HexToAddress("0xdAC17F958D2ee523a2206206994597C13D831ec7") // 解析合约地址
usdt.ABI = func() abi.ABI { a, _ := abi.JSON(strings.NewReader(erc20ABI)); return a }() // 解析合约ABI
usdt.TransferSig = crypto.Keccak256Hash([]byte("Transfer(address,address,uint256)")) // 解析合约transfer函数签名
usdt.LogsChan = make(chan types.Log, 1000) // 初始化合约日志通道
2025-10-31 13:46:58 +08:00
usdt.ListeningAddresses = make(map[string]any)
2025-10-16 18:54:27 +08:00
// 初始化数据库
dbConn, err := db.NewMySQLPool(cfg.DbConfig)
if err != nil {
cancel()
return nil, fmt.Errorf("mysql connect error: %w", err)
}
2025-10-31 13:46:58 +08:00
// 初始化结构
topup := make(map[string]*message.TopupMsg_req)
withdraw := make(map[string]*message.WithdrawMsg_req)
pay := make(map[string]*message.PayMsg_req)
unConfirmTxs := make(map[string]*message.Transaction)
logsChan := make(chan *types.Header, 1000)
// 启动时初始化实时数据
realData := &RealData{}
2025-10-16 18:54:27 +08:00
return &ETHNode{
2025-10-31 13:46:58 +08:00
decodeKey: decodeKey,
NetID: netId,
Config: cfg,
ConfirmHeight: cfg.ConfirmHeight,
WsClient: ws_client,
RpcClient: rpc_client,
Db: *dbConn,
TopupMsg: topup,
WithdrawMsg: withdraw,
PayMsg: pay,
UnConfirmTxs: unConfirmTxs,
LogsChan: logsChan,
USDT: usdt,
RealData: realData,
Ctx: ctx,
Cancel: cancel,
2025-10-16 18:54:27 +08:00
}, nil
}
// ============================ 抽象接口 ============================
2025-10-31 13:46:58 +08:00
func (e *ETHNode) AddAddress(msg any) error {
switch v := msg.(type) {
case message.TopupMsg_req:
e.mu.Lock()
addr := strings.ToLower(v.Address)
e.TopupMsg[addr] = &v
e.mu.Unlock()
case message.WithdrawMsg_req:
e.mu.Lock()
addr := strings.ToLower(v.ToAddress)
e.WithdrawMsg[addr] = &v
e.mu.Unlock()
case message.PayMsg_req:
e.mu.Lock()
addr := strings.ToLower(v.FromAddress)
e.PayMsg[addr] = &v
e.mu.Unlock()
default:
return fmt.Errorf("unsupported message type: %T", msg)
}
2025-10-27 16:27:33 +08:00
return nil
2025-10-16 18:54:27 +08:00
}
2025-10-31 13:46:58 +08:00
func (e *ETHNode) RemoveAddress(msg any) error {
switch v := msg.(type) {
case message.RemoveListenMsg_req:
e.mu.Lock()
addr := strings.ToLower(v.Address)
delete(e.TopupMsg, addr)
e.mu.Unlock()
case message.WithdrawMsg_req:
e.mu.Lock()
addr := strings.ToLower(v.ToAddress)
delete(e.WithdrawMsg, addr)
e.mu.Unlock()
case message.PayMsg_req:
e.mu.Lock()
addr := strings.ToLower(v.FromAddress)
delete(e.PayMsg, addr)
e.mu.Unlock()
default:
return fmt.Errorf("unsupported message type: %T", msg)
}
2025-10-27 16:27:33 +08:00
return nil
2025-10-16 18:54:27 +08:00
}
2025-10-31 13:46:58 +08:00
func (e *ETHNode) Listen(ch chan any) {
log.Println("✅ 开始监听 ETH 和 USDT 转账事件...")
go func() {
err := e.listenETHTransactions(ch)
2025-10-16 18:54:27 +08:00
if err != nil {
2025-10-31 13:46:58 +08:00
log.Fatalf("Listen ETH Transactions Error: %v", err)
2025-10-16 18:54:27 +08:00
}
2025-10-31 13:46:58 +08:00
}()
go func() {
err := e.listenUSDTTransactions(ch)
if err != nil {
log.Fatalf("Listen USDT Transactions Error: %v", err)
}
}()
2025-10-16 18:54:27 +08:00
}
2025-10-31 13:46:58 +08:00
// 转账
func (e *ETHNode) Transfer(msg any) error {
switch v := msg.(type) {
case message.WithdrawMsg_req:
// 1. 校验余额
verifyResult, err := e.verifyBalance(v.Symbol, v.FromAddress, v.Amount)
if err != nil || !verifyResult {
log.Printf("address (%s) balance verification failed: %v", v.FromAddress, err)
return err
}
// 2. 构建未签名交易
unSignTx, err := e.contractTx(v.Symbol, v.FromAddress, v.ToAddress, v.Amount)
2025-10-16 18:54:27 +08:00
if err != nil {
2025-10-31 13:46:58 +08:00
log.Printf("failed to create contract transaction: %v", err)
return err
}
// 3. 签名交易
signedTx, err := e.signTx(unSignTx, v.FromAddress)
if err != nil {
log.Printf("failed to sign transaction: %v", err)
return err
}
txHash := signedTx.Hash().Hex()
// 4. 发送交易并存入交易池
if err := e.sendTransaction(signedTx, txHash, v.Symbol, v.FromAddress, v.ToAddress, v.Amount); err != nil {
log.Printf("failed to send transaction: %v", err)
}
// 5. 将tx_hash添加到消息中
if wm, ok := e.WithdrawMsg[v.FromAddress]; ok {
wm.TxHash = txHash
e.WithdrawMsg[v.FromAddress] = wm
} else {
// 如果不存在原始消息,创建一条新的记录并写回 map根据实际结构字段调整
v.TxHash = txHash
e.WithdrawMsg[v.FromAddress] = &v
}
case message.PayMsg_req:
// 1. 校验余额
verifyResult, err := e.verifyBalance(v.Symbol, v.FromAddress, v.TotalAmount)
if err != nil || !verifyResult {
log.Printf("address (%s) balance verification failed: %v", v.FromAddress, err)
return err
}
// 2. 预先获取起始 nonce 避免并发冲突
startNonce, err := e.getTransactionNonce(v.FromAddress)
if err != nil {
log.Printf("failed to get start nonce: %v", err)
return err
}
// 3. 按顺序发送多笔交易(注意:顺序发送可以避免 nonce 冲突,但可能较慢)
i := 0
for toAddr, tx := range v.Trasnactions {
// 构建未签名交易(使用递增的 nonce
unSignTx, err := e.buildContractTxWithNonce(v.Symbol, v.FromAddress, tx.ToAddress, tx.Amount, startNonce+uint64(i))
if err != nil {
log.Printf("failed to create contract transaction: %v", err)
i++
continue
}
// 签名交易
signedTx, err := e.signTx(unSignTx, v.FromAddress)
if err != nil {
log.Printf("failed to sign transaction: %v", err)
i++
continue
}
txHash := signedTx.Hash().Hex()
// 发送交易并存入交易池
if err := e.sendTransaction(signedTx, txHash, v.Symbol, v.FromAddress, tx.ToAddress, tx.Amount); err != nil {
log.Printf("failed to send transaction: %v", err)
i++
continue
}
// 将tx_hash添加到消息中
e.mu.Lock()
tx.TxHash = txHash
e.PayMsg[v.FromAddress].Trasnactions[toAddr] = tx
e.mu.Unlock()
i++
2025-10-16 18:54:27 +08:00
}
}
return nil
}
2025-10-27 16:27:33 +08:00
func (e *ETHNode) Stop() {
2025-10-31 13:46:58 +08:00
if e.Cancel != nil {
e.Cancel()
}
log.Println("🛑 停止监听...")
2025-10-27 16:27:33 +08:00
}
2025-10-16 18:54:27 +08:00
// ============================ rpc节点方法 ============================
2025-10-31 13:46:58 +08:00
func (e *ETHNode) getETHBalance(address string) (*big.Int, error) {
2025-10-16 18:54:27 +08:00
account := common.HexToAddress(address)
ctx := context.Background()
balance, err := e.RpcClient.BalanceAt(ctx, account, nil) // nil表示最新高度
if err != nil {
return nil, fmt.Errorf("failed to get eth balance:%w", err)
}
// fBalance := new(big.Float).SetInt(balance)
// ethValue := new(big.Float).Quo(fBalance, big.NewFloat(1e18)) // 转 ETH
// value, _ := ethValue.Float64() // 转 float64
return balance, nil
}
2025-10-31 13:46:58 +08:00
func (e *ETHNode) getUSDTBalance(address string) (*big.Int, error) {
2025-10-16 18:54:27 +08:00
// 统一转换为小写common.HexToAddress会自动处理但为了一致性显式转换
address = strings.ToLower(address)
contractAddress := e.USDT.Address
accountAddress := common.HexToAddress(address)
data, err := e.USDT.ABI.Pack("balanceOf", accountAddress)
if err != nil {
2025-10-31 13:46:58 +08:00
return nil, fmt.Errorf("failed to pack balanceOf data: %w", err)
2025-10-16 18:54:27 +08:00
}
msg := ethereum.CallMsg{
To: &contractAddress,
Data: data,
}
// 使用 CallContract 方法查询合约余额
res, err := e.RpcClient.CallContract(e.Ctx, msg, nil)
if err != nil {
2025-10-31 13:46:58 +08:00
return nil, fmt.Errorf("failed to get contract balance: %w", err)
2025-10-16 18:54:27 +08:00
}
// 解析返回的字节为 *big.Int
outputs, err := e.USDT.ABI.Unpack("balanceOf", res)
if err != nil || len(outputs) == 0 {
2025-10-31 13:46:58 +08:00
return nil, fmt.Errorf("failed to unpack balanceOf result: %w", err)
2025-10-16 18:54:27 +08:00
}
balance, ok := outputs[0].(*big.Int)
if !ok {
2025-10-31 13:46:58 +08:00
return nil, fmt.Errorf("unexpected type for balanceOf result")
}
// bal := utils.BigIntUSDTToFloat64(balance)
return balance, nil
}
func (e *ETHNode) getTransactionNonce(address string) (uint64, error) {
nonce, err := e.RpcClient.PendingNonceAt(e.Ctx, common.HexToAddress(address))
if err != nil {
log.Fatalf("failed to get nonce: %v", err)
return 0, err
2025-10-16 18:54:27 +08:00
}
2025-10-31 13:46:58 +08:00
return nonce, nil
2025-10-16 18:54:27 +08:00
}
2025-10-27 16:27:33 +08:00
func (e *ETHNode) getGasLimit() (uint64, error) {
// 对于ERC20转账使用固定的gas limit
// 通常ERC20 transfer需要约65,000-100,000 gas
// 这里设置为80,000足够覆盖大部分情况
return 80000, nil
2025-10-16 18:54:27 +08:00
}
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)
}
2025-10-27 16:27:33 +08:00
// 设置gas price上限避免在网络拥堵时费用过高
// 这里设置为20 Gwei (20 * 10^9 wei)
maxGasPrice := new(big.Int).SetUint64(20000000000) // 20 Gwei
if gasPrice.Cmp(maxGasPrice) > 0 {
2025-10-31 13:46:58 +08:00
log.Printf("⚠️ 建议gas price过高 (%v wei),使用上限 20 Gwei", new(big.Int).Div(gasPrice, big.NewInt(1e18)))
2025-10-27 16:27:33 +08:00
return maxGasPrice, nil
}
2025-10-31 13:46:58 +08:00
// log.Printf("✅ 使用建议gas price: %v wei", new(big.Int).Div(gasPrice, big.NewInt(1e18)))
2025-10-16 18:54:27 +08:00
return gasPrice, nil
}
2025-10-31 13:46:58 +08:00
func (e *ETHNode) checkTransaction(tx_hash string) (bool, error) {
receipt, err := e.RpcClient.TransactionReceipt(e.Ctx, common.HexToHash(tx_hash))
if err != nil {
return false, fmt.Errorf("check tx(%s) error: %v", tx_hash, err)
}
if receipt.Status == types.ReceiptStatusSuccessful {
return true, nil
} else {
return false, nil
}
}
2025-10-27 16:27:33 +08:00
// 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 {
2025-10-31 13:46:58 +08:00
log.Printf("⚠️ 计算的最大费用过高 (%v wei),使用上限 30 Gwei", new(big.Int).Div(maxFeePerGas, big.NewInt(1e18)))
2025-10-27 16:27:33 +08:00
maxFeePerGas = maxFeeLimit
}
2025-10-31 13:46:58 +08:00
// 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)))
2025-10-27 16:27:33 +08:00
return maxFeePerGas, maxPriorityFeePerGas, nil
}
2025-10-31 13:46:58 +08:00
// ============================ 业务逻辑 ============================
// 监听ETH转账
func (e *ETHNode) listenETHTransactions(ch chan any) error {
fmt.Println("🔍 开始ETH交易...")
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()
go e.updateRealData(currentHeight)
go e.handleETHEvent(header, ch)
go e.confirm(ch)
case <-e.Ctx.Done():
fmt.Println("🛑 收到停止信号退出ETH交易监听")
sub.Unsubscribe()
return e.Ctx.Err()
}
}
reconnect:
}
}
// 监听USDT转账
func (e *ETHNode) listenUSDTTransactions(ch chan any) error {
fmt.Println("🔍 开始USDT交易...")
2025-10-16 18:54:27 +08:00
// 过滤掉非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 {
2025-10-31 13:46:58 +08:00
fmt.Println("❌ USDT交易订阅失败, 5秒后重试:", err)
2025-10-16 18:54:27 +08:00
time.Sleep(5 * time.Second)
continue
}
2025-10-31 13:46:58 +08:00
fmt.Println("✅ USDT交易订阅成功")
2025-10-16 18:54:27 +08:00
// 处理事件
for {
2025-10-27 16:27:33 +08:00
2025-10-16 18:54:27 +08:00
select {
case err := <-sub.Err():
2025-10-31 13:46:58 +08:00
fmt.Println("⚠️ USDT交易订阅异常准备重连:", err)
2025-10-16 18:54:27 +08:00
sub.Unsubscribe() // 清理旧订阅
time.Sleep(3 * time.Second)
goto reconnect // 跳出内层循环,回到外层重新订阅
case vLog := <-e.USDT.LogsChan:
2025-10-31 13:46:58 +08:00
go e.handleUSDTEvent(vLog, ch) // 事件解析 + 分类传递链消息的通道是vLog而非ch且一次只传递一笔交易
2025-10-16 18:54:27 +08:00
case <-e.Ctx.Done():
2025-10-31 13:46:58 +08:00
fmt.Println("🛑 收到停止信号退出USDT交易监听")
2025-10-16 18:54:27 +08:00
sub.Unsubscribe()
return e.Ctx.Err()
}
}
reconnect:
}
}
2025-10-31 13:46:58 +08:00
func (e *ETHNode) updateRealData(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)))
2025-10-16 18:54:27 +08:00
}
2025-10-31 13:46:58 +08:00
}()
// 等待所有协程完成
wg.Wait()
// 检查是否有任何错误
if gasLimitErr != nil || suggestGasPriceErr != nil || eip1559GasFeesErr != nil {
log.Println("One or more methods failed. Not updating RealData.")
return
2025-10-21 10:58:58 +08:00
}
2025-10-31 13:46:58 +08:00
// 更新 RealData
e.RealData.mu.Lock()
defer e.RealData.mu.Unlock()
e.RealData = &RealData{
Heihgt: height,
GasLimit: gasLimit,
GasTipCap: maxPriorityFeePerGas,
GasFeeCap: maxFeePerGas,
GasPrice: suggestGasPrice,
2025-10-16 18:54:27 +08:00
}
2025-10-31 13:46:58 +08:00
// log.Println("✅ RealData updated successfully.")
2025-10-16 18:54:27 +08:00
}
2025-10-31 13:46:58 +08:00
func (e *ETHNode) handleETHEvent(header *types.Header, ch chan any) {
height := header.Number.Uint64()
2025-10-16 18:54:27 +08:00
2025-10-31 13:46:58 +08:00
// 获取区块中的所有交易
block, err := e.RpcClient.BlockByHash(e.Ctx, header.Hash())
if err != nil {
log.Printf("无法获取区块信息: %v", err)
return
}
2025-10-16 18:54:27 +08:00
2025-10-31 13:46:58 +08:00
// 遍历区块中的每笔交易
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)
2025-10-16 18:54:27 +08:00
if err != nil {
2025-10-31 13:46:58 +08:00
log.Println("获取发送方地址失败:", err)
2025-10-16 18:54:27 +08:00
continue
}
2025-10-31 13:46:58 +08:00
toAddr := ""
if tx.To() != nil {
toAddr = strings.ToLower(tx.To().Hex())
}
fromAddr := strings.ToLower(from.Hex())
2025-10-16 18:54:27 +08:00
2025-10-31 13:46:58 +08:00
// 获取交易金额
amount := utils.BigIntETHToFloat64(tx.Value())
2025-10-16 18:54:27 +08:00
2025-10-31 13:46:58 +08:00
// 处理充值
for k, v := range e.TopupMsg {
if k == toAddr {
// 锁定并更新未确认的交易
2025-10-16 18:54:27 +08:00
e.mu.Lock()
2025-10-31 13:46:58 +08:00
e.UnConfirmTxs[txHash] = &message.Transaction{
From: fromAddr,
To: toAddr,
Height: height,
TxHash: txHash,
Symbol: v.Symbol,
Value: amount,
Status: STATUS_PENDING,
}
e.TopupMsg[k].Status = STATUS_PENDING
2025-10-16 18:54:27 +08:00
e.mu.Unlock()
2025-10-31 13:46:58 +08:00
// 创建待确认充值消息
topup_unconfirm_msg_resp := message.TopupMsg_resp{
QueueId: v.QueueId,
Address: v.Address,
Status: STATUS_PENDING,
Chain: v.Chain,
Amount: amount,
TxHash: txHash,
BlockHeight: height,
2025-10-16 18:54:27 +08:00
}
2025-10-31 13:46:58 +08:00
// 异步发送消息到通道
go func(msg message.TopupMsg_resp) {
select {
case ch <- msg:
log.Printf("✅ 待确认充值消息已发送")
default:
log.Printf("⚠️ 通道阻塞,待确认消息发送失败")
}
}(topup_unconfirm_msg_resp)
2025-10-16 18:54:27 +08:00
}
}
2025-10-31 13:46:58 +08:00
// 处理提现
for k, v := range e.WithdrawMsg {
if strings.EqualFold(v.FromAddress, fromAddr) && strings.EqualFold(v.ToAddress, toAddr) && v.Amount == amount {
e.mu.Lock()
e.UnConfirmTxs[txHash] = &message.Transaction{
From: fromAddr,
To: toAddr,
Height: height,
TxHash: txHash,
Symbol: v.Symbol,
Value: amount,
Status: STATUS_PENDING,
}
e.WithdrawMsg[k].Status = STATUS_PENDING
e.mu.Unlock()
}
2025-10-16 18:54:27 +08:00
}
2025-10-31 13:46:58 +08:00
// 处理支付
for k, v := range e.PayMsg {
if strings.EqualFold(v.FromAddress, fromAddr) {
for i, pay := range v.Trasnactions {
if strings.EqualFold(pay.ToAddress, toAddr) && pay.Amount == amount {
e.mu.Lock()
e.UnConfirmTxs[txHash] = &message.Transaction{
From: fromAddr,
To: toAddr,
Height: height,
TxHash: txHash,
Symbol: v.Symbol,
Value: amount,
Status: STATUS_PENDING,
}
e.PayMsg[k].Trasnactions[i].Status = STATUS_PENDING
e.mu.Unlock()
}
}
}
}
2025-10-16 18:54:27 +08:00
}
}
2025-10-31 13:46:58 +08:00
func (e *ETHNode) handleUSDTEvent(vLog types.Log, ch chan any) {
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)
2025-10-16 18:54:27 +08:00
return
}
2025-10-31 13:46:58 +08:00
tx_hash := vLog.TxHash.Hex()
value_float := utils.BigIntUSDTToFloat64(transferEvent.Value)
var status int = 2
// 分别验证3组消息
// 充值
for k, v := range e.TopupMsg {
if k == toAddr {
e.mu.Lock()
e.UnConfirmTxs[tx_hash] = &message.Transaction{
From: fromAddr,
To: toAddr,
Height: height,
TxHash: tx_hash,
Symbol: v.Symbol,
Value: value_float,
Status: status,
2025-10-16 18:54:27 +08:00
}
2025-10-31 13:46:58 +08:00
e.mu.Unlock()
topup_unconfirm_msg_resp := message.TopupMsg_resp{
QueueId: v.QueueId,
Address: v.Address,
Status: status,
Symbol: v.Symbol,
Chain: v.Chain,
Amount: value_float,
TxHash: tx_hash,
BlockHeight: height,
}
// 异步发送
go func(msg message.TopupMsg_resp) {
select {
case ch <- msg:
log.Printf("✅ 待确认充值消息已发送")
default:
log.Printf("⚠️ 通道阻塞,待确认消息发送失败")
2025-10-16 18:54:27 +08:00
}
2025-10-31 13:46:58 +08:00
}(topup_unconfirm_msg_resp)
}
}
// 提现
for k, v := range e.WithdrawMsg {
if k == fromAddr && v.ToAddress == toAddr && v.Amount == value_float {
e.mu.Lock()
e.UnConfirmTxs[tx_hash] = &message.Transaction{
From: fromAddr,
To: toAddr,
Height: height,
TxHash: tx_hash,
Symbol: v.Symbol,
Value: value_float,
Status: status,
2025-10-16 18:54:27 +08:00
}
2025-10-31 13:46:58 +08:00
e.WithdrawMsg[k].Status = STATUS_PENDING
e.mu.Unlock()
}
}
// 充值
for k, v := range e.PayMsg {
if k == fromAddr {
for i, pay := range v.Trasnactions {
if pay.ToAddress == toAddr && pay.Amount == value_float {
e.mu.Lock()
e.UnConfirmTxs[tx_hash] = &message.Transaction{
From: fromAddr,
To: toAddr,
Height: height,
TxHash: tx_hash,
Symbol: v.Symbol,
Value: value_float,
Status: status,
2025-10-16 18:54:27 +08:00
}
2025-10-31 13:46:58 +08:00
e.PayMsg[k].Trasnactions[i].Status = STATUS_PENDING
e.mu.Unlock()
2025-10-16 18:54:27 +08:00
}
2025-10-31 13:46:58 +08:00
2025-10-16 18:54:27 +08:00
}
}
2025-10-31 13:46:58 +08:00
}
2025-10-16 18:54:27 +08:00
}
func (e *ETHNode) decodePrivatekey(address string) string {
// 统一转换为小写
address = strings.ToLower(address)
// 查询加密后的私钥
querySql := "SELECT `private_key` FROM eth_balance WHERE address = ? LIMIT 1;"
log.Println("查询私钥的钱包地址:", address)
var encryptedKey string
err := e.Db.QueryRow(querySql, address).Scan(&encryptedKey)
if err != nil {
log.Println("❌ 查询私钥失败:", err)
return ""
}
// 使用key解密
privateKey := encryptedKey // 实际使用时替换成具体的解密代码
return privateKey
}
2025-10-31 13:46:58 +08:00
// 验证余额
func (e *ETHNode) verifyBalance(symbol, from string, amount float64) (bool, error) {
var amount_b *big.Int
e.mu.Lock()
maxGas := e.RealData.GasFeeCap
if maxGas == nil {
return false, fmt.Errorf("chain data not initialized, maxGas = nil")
2025-10-16 18:54:27 +08:00
}
2025-10-31 13:46:58 +08:00
e.mu.Unlock()
switch symbol {
case "ETH":
amount_b = utils.Float64ToBigIntETH(amount)
totalAmount := new(big.Int).Add(amount_b, maxGas)
ethBalance, err := e.getETHBalance(from)
if err != nil {
return false, fmt.Errorf("get (%s) eth-balance error: %v", from, err)
}
if ethBalance.Cmp(totalAmount) == -1 {
return false, nil // 余额不足
}
return true, nil
case "USDT":
amount_b = utils.Float64ToBigIntUSDT(amount)
usdtBalance, err := e.getUSDTBalance(from)
if err != nil {
return false, fmt.Errorf("get (%s) usdt-balance error: %v", from, err)
}
if usdtBalance.Cmp(amount_b) == -1 {
return false, nil // 余额不足
}
return true, nil
default:
return false, fmt.Errorf("ETH NetWork error symbol: %s", symbol)
2025-10-16 18:54:27 +08:00
}
2025-10-31 13:46:58 +08:00
}
// 构建交易
func (e *ETHNode) contractTx(symbol, from, to string, amount float64) (*types.Transaction, error) {
nonce, err := e.getTransactionNonce(from)
2025-10-16 18:54:27 +08:00
if err != nil {
2025-10-31 13:46:58 +08:00
return nil, fmt.Errorf("failed to get nonce: %v", err)
2025-10-16 18:54:27 +08:00
}
2025-10-31 13:46:58 +08:00
return e.buildContractTxWithNonce(symbol, from, to, amount, nonce)
}
// 构建交易(指定 nonce
func (e *ETHNode) buildContractTxWithNonce(symbol, from, to string, amount float64, nonce uint64) (*types.Transaction, error) {
e.mu.Lock()
maxFeePerGas, maxPriorityFeePerGas, gasLimit := e.RealData.GasFeeCap, e.RealData.GasTipCap, e.RealData.GasLimit
if maxFeePerGas == nil || maxPriorityFeePerGas == nil || gasLimit == 0 {
e.mu.Unlock()
return nil, fmt.Errorf("chain data not initialized!")
2025-10-16 18:54:27 +08:00
}
2025-10-31 13:46:58 +08:00
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{},
2025-10-16 18:54:27 +08:00
}
2025-10-27 16:27:33 +08:00
2025-10-31 13:46:58 +08:00
switch symbol {
case "ETH":
eip1559Tx.Value = utils.Float64ToBigIntETH(amount)
case "USDT":
eip1559Tx.Value = big.NewInt(0)
data, err := e.USDT.ABI.Pack("transfer", common.HexToAddress(to), utils.Float64ToBigIntUSDT(amount))
2025-10-27 16:27:33 +08:00
if err != nil {
2025-10-31 13:46:58 +08:00
return nil, fmt.Errorf("failed to pack transfer data: %v", err)
2025-10-27 16:27:33 +08:00
}
2025-10-31 13:46:58 +08:00
eip1559Tx.Data = data
default:
return nil, fmt.Errorf("ETH NetWork error symbol: %s", symbol)
}
2025-10-27 16:27:33 +08:00
2025-10-31 13:46:58 +08:00
return types.NewTx(eip1559Tx), nil
}
2025-10-27 16:27:33 +08:00
2025-10-31 13:46:58 +08:00
// 签名交易
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)
}
2025-10-27 16:27:33 +08:00
2025-10-31 13:46:58 +08:00
privateKey, err := crypto.HexToECDSA(originalKey)
if err != nil {
return nil, fmt.Errorf("failed to parse private key: %v", err)
}
2025-10-27 16:27:33 +08:00
2025-10-31 13:46:58 +08:00
signer := types.LatestSignerForChainID(e.NetID)
return types.SignTx(tx, signer, privateKey)
}
2025-10-27 16:27:33 +08:00
2025-10-31 13:46:58 +08:00
// 发送交易并存入待确认交易池
func (e *ETHNode) sendTransaction(tx *types.Transaction, txHash, symbol, from, to string, amount float64) error {
// 发送交易
// err := e.RpcClient.SendTransaction(e.Ctx, tx)
// if err != nil {
// return fmt.Errorf("failed to send transaction: %v", err)
// }
2025-10-27 16:27:33 +08:00
2025-10-31 13:46:58 +08:00
// 获取当前区块高度
e.mu.Lock()
height := e.RealData.Heihgt
e.mu.Unlock()
2025-10-27 16:27:33 +08:00
2025-10-31 13:46:58 +08:00
// 将交易存入待交易池
e.mu.Lock()
e.UnConfirmTxs[txHash] = &message.Transaction{
Symbol: symbol,
From: from,
To: to,
Height: height,
TxHash: txHash,
Value: amount,
Status: STATUS_PENDING,
}
e.mu.Unlock()
return nil
}
2025-10-27 16:27:33 +08:00
2025-10-31 13:46:58 +08:00
// 确认信息并返回
func (e *ETHNode) confirm(ch chan any) {
e.mu.Lock()
height := e.RealData.Heihgt
e.mu.Unlock()
var needSendMsg []any
var toDeleteTxs []string // 用于记录需要从 UnConfirmTxs 中删除的交易
for k, v := range e.UnConfirmTxs {
if v.Height+e.ConfirmHeight >= height {
tx_result, err := e.checkTransaction(k)
if err != nil {
log.Printf("❌ check tx(%s) error: %v", k, err)
continue
}
2025-10-27 16:27:33 +08:00
2025-10-31 13:46:58 +08:00
e.mu.Lock()
if tx_result {
// 交易成功
// 判断是否在充值消息中
for toAddr, topup_msg := range e.TopupMsg {
if strings.EqualFold(toAddr, v.To) {
msg := message.TopupMsg_resp{
QueueId: topup_msg.QueueId,
Address: v.To,
Status: STATUS_SUCCESS,
Chain: topup_msg.Chain,
Symbol: topup_msg.Symbol,
Amount: v.Value,
TxHash: v.TxHash,
BlockHeight: height,
}
needSendMsg = append(needSendMsg, msg)
break // 充值成功后不删除,可能还有后续充值
}
}
2025-10-27 16:27:33 +08:00
2025-10-31 13:46:58 +08:00
// 判断是否在提现消息中
for key, withdraw_msg := range e.WithdrawMsg {
if v.TxHash == withdraw_msg.TxHash {
msg := message.WithdrawMsg_resp{
QueueId: withdraw_msg.QueueId,
Chain: withdraw_msg.Chain,
Symbol: withdraw_msg.Symbol,
Status: STATUS_SUCCESS,
Amount: v.Value,
TxHash: v.TxHash,
FromAddress: v.From,
ToAddress: v.To,
BlockHeight: v.Height,
}
needSendMsg = append(needSendMsg, msg)
// 删除提现消息
e.RemoveAddress(withdraw_msg)
delete(e.WithdrawMsg, key)
break
}
}
2025-10-27 16:27:33 +08:00
2025-10-31 13:46:58 +08:00
// 判断是否在支付消息中
for _, pay_msg := range e.PayMsg {
for payKey, pay := range pay_msg.Trasnactions {
if v.TxHash == pay.TxHash {
pay_msg.Trasnactions[payKey].Status = STATUS_SUCCESS
}
}
}
} else {
// 交易失败
// 判断是否在充值消息中
for toAddr, topup_msg := range e.TopupMsg {
if strings.EqualFold(toAddr, v.To) {
msg := message.TopupMsg_resp{
QueueId: topup_msg.QueueId,
Address: v.To,
Status: STATUS_FAILED,
Chain: topup_msg.Chain,
Symbol: topup_msg.Symbol,
Amount: v.Value,
TxHash: v.TxHash,
BlockHeight: height,
}
needSendMsg = append(needSendMsg, msg)
// 充值失败,删除该监听地址
delete(e.TopupMsg, toAddr)
break
}
}
2025-10-27 16:27:33 +08:00
2025-10-31 13:46:58 +08:00
// 判断是否在提现消息中
for key, withdraw_msg := range e.WithdrawMsg {
if v.TxHash == withdraw_msg.TxHash {
msg := message.WithdrawMsg_resp{
QueueId: withdraw_msg.QueueId,
Chain: withdraw_msg.Chain,
Symbol: withdraw_msg.Symbol,
Status: STATUS_FAILED,
Amount: v.Value,
TxHash: v.TxHash,
FromAddress: v.From,
ToAddress: v.To,
BlockHeight: v.Height,
}
needSendMsg = append(needSendMsg, msg)
// 删除提现消息
e.RemoveAddress(withdraw_msg)
delete(e.WithdrawMsg, key)
break
}
}
2025-10-27 16:27:33 +08:00
2025-10-31 13:46:58 +08:00
// 判断是否在支付消息中
for _, pay_msg := range e.PayMsg {
for payKey, pay := range pay_msg.Trasnactions {
if v.TxHash == pay.TxHash {
pay_msg.Trasnactions[payKey].Status = STATUS_FAILED
}
}
}
}
e.mu.Unlock()
2025-10-27 16:27:33 +08:00
2025-10-31 13:46:58 +08:00
// 标记为删除
toDeleteTxs = append(toDeleteTxs, k)
}
2025-10-27 16:27:33 +08:00
}
2025-10-31 13:46:58 +08:00
// 删除已确认的交易
if len(toDeleteTxs) > 0 {
e.mu.Lock()
for _, txHash := range toDeleteTxs {
delete(e.UnConfirmTxs, txHash)
}
e.mu.Unlock()
2025-10-16 18:54:27 +08:00
}
2025-10-27 16:27:33 +08:00
2025-10-31 13:46:58 +08:00
// 检查支付消息是否需要发送完整响应
e.mu.Lock()
var payMsgsToDelete []string
for key, pay_msg := range e.PayMsg {
allConfirmed := true
for _, tx := range pay_msg.Trasnactions {
if tx.Status == STATUS_PENDING {
allConfirmed = false
break
}
}
if allConfirmed {
// 所有交易都已确认,发送完整响应
needSendMsg = append(needSendMsg, pay_msg)
payMsgsToDelete = append(payMsgsToDelete, key)
}
}
// 删除已全部确认的支付消息
for _, key := range payMsgsToDelete {
e.RemoveAddress(e.PayMsg[key])
delete(e.PayMsg, key)
}
e.mu.Unlock()
2025-10-27 16:27:33 +08:00
2025-10-31 13:46:58 +08:00
// 异步发送所有响应消息
if len(needSendMsg) != 0 {
for _, data := range needSendMsg {
go func(msg any) {
select {
case ch <- msg:
log.Printf("✅ confirm message sent: %+v", msg)
default:
log.Printf("⚠️ 通道阻塞,待确认消息发送失败")
}
}(data)
}
}
2025-10-16 18:54:27 +08:00
}