diff --git a/go.mod b/go.mod index dd39cdc..132084e 100644 --- a/go.mod +++ b/go.mod @@ -39,6 +39,7 @@ require ( github.com/go-ole/go-ole v1.3.0 // indirect github.com/gorilla/websocket v1.4.2 // indirect github.com/holiman/uint256 v1.3.2 // indirect + github.com/mitchellh/mapstructure v1.5.0 github.com/rabbitmq/amqp091-go v1.10.0 github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible // indirect github.com/supranational/blst v0.3.16-0.20250831170142-f48500c1fdbe // indirect diff --git a/go.sum b/go.sum index 8dac093..05d20ca 100644 --- a/go.sum +++ b/go.sum @@ -119,6 +119,8 @@ github.com/minio/sha256-simd v1.0.0 h1:v1ta+49hkWZyvaKwrQB8elexRqm6Y0aMLjCNsrYxo github.com/minio/sha256-simd v1.0.0/go.mod h1:OuYzVNI5vcoYIAmbIvHPl3N3jUzVedXbKy5RFepssQM= github.com/mitchellh/mapstructure v1.4.1 h1:CpVNEelQCZBooIPDn+AR3NpivK/TIKU8bDxdASFVQag= github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/pointerstructure v1.2.0 h1:O+i9nHnXS3l/9Wu7r4NrEdwA2VFTicjUEN1uBnDo34A= github.com/mitchellh/pointerstructure v1.2.0/go.mod h1:BRAsLI5zgXmw97Lf6s25bs8ohIXc3tViBH44KcwB2g4= github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4= diff --git a/internal/blockchain/blockchain.go b/internal/blockchain/blockchain.go index a38ff64..d05f9f4 100644 --- a/internal/blockchain/blockchain.go +++ b/internal/blockchain/blockchain.go @@ -1,21 +1,22 @@ +// 区块链网络抽象接口统一实现 package blockchain import ( "fmt" + "log" "sync" ) type IChainServer interface { - AddAddress(msg any) error - RemoveAddress(msg any) error - Listen(ch chan any) - Transfer(msg any) error + Listen() error + ListenMsg() + Transfer(from, to, symbol string, amount, fee float64) error Stop() } type BlockChainServer struct { mu sync.Mutex - chains map[string]IChainServer // "ETH", "TRON", "BTC" + chains map[string]IChainServer } func NewBlockChainServer() *BlockChainServer { @@ -30,40 +31,41 @@ func (b *BlockChainServer) RegisterChain(name string, chain IChainServer) { b.chains[name] = chain } -func (b *BlockChainServer) AddAddress(chain string, msg any) error { - if srv, ok := b.chains[chain]; ok { - srv.AddAddress(msg) - fmt.Printf("✅ 添加监听地址: chain=%s, msg=%v\n", chain, msg) - return nil - } else { - return fmt.Errorf("⚠️ 链未注册: %s\n", chain) - } -} - -func (b *BlockChainServer) RemoveAddress(chain string, msg any) error { - if srv, ok := b.chains[chain]; ok { - srv.RemoveAddress(msg) - fmt.Printf("🗑️ 移除监听地址: chain=%s, msg=%s\n", chain, msg) - return nil - } else { - return fmt.Errorf("⚠️ 链未注册: %s\n", chain) - } -} - func (b *BlockChainServer) Listen(chain string, ch chan any) error { if srv, ok := b.chains[chain]; ok { go func() { - srv.Listen(ch) + err := srv.Listen() + if err != nil { + log.Printf("Start Listen error: %v", err) + return + } }() return nil } return fmt.Errorf("链未注册: %s", chain) } -func (b *BlockChainServer) Transfer(chain string, msg any) error { +func (b *BlockChainServer) ListenMsg(chain string) error { if srv, ok := b.chains[chain]; ok { - fmt.Printf("💸 %s发起转账: %+v\n", chain, msg) - return srv.Transfer(msg) + go func() { + srv.ListenMsg() + }() + return nil + } + return fmt.Errorf("链未注册: %s", chain) +} + +func (b *BlockChainServer) Transfer(chain, from, to, symbol string, amount, fee float64) error { + if srv, ok := b.chains[chain]; ok { + fmt.Printf("💸 发起转账: Chain(%s)-Symbol(%s), From(%s), To(%s), Amount(%f)\n", chain, symbol, from, to, amount) + go func() { + err := srv.Transfer(from, to, symbol, amount, fee) + if err != nil { + log.Printf("Transfer Error: %v\n Transfer has Exited", err) + return + } + }() + return nil } return fmt.Errorf("链未注册: %s", chain) } @@ -72,6 +74,7 @@ func (b *BlockChainServer) Stop(chain string) { if srv, ok := b.chains[chain]; ok { fmt.Printf("💸 关闭服务: %+v\n", chain) srv.Stop() + return } fmt.Printf("链未注册: %s", chain) } diff --git a/internal/blockchain/eth/draft/multiSend_eth.sol b/internal/blockchain/eth/draft/multiSend_eth.sol deleted file mode 100644 index 0838279..0000000 --- a/internal/blockchain/eth/draft/multiSend_eth.sol +++ /dev/null @@ -1,13 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -contract MultiSend { - function multiTransfer(address[] calldata to, uint256[] calldata amounts) external payable { - uint256 length = to.length; - require(length == amounts.length, "Arrays must have the same length"); - - for (uint256 i = 0; i < length; i++) { - payable(to[i]).transfer(amounts[i]); - } - } -} \ No newline at end of file diff --git a/internal/blockchain/eth/draft/multiSend_usdt.sol b/internal/blockchain/eth/draft/multiSend_usdt.sol deleted file mode 100644 index 1a15dcf..0000000 --- a/internal/blockchain/eth/draft/multiSend_usdt.sol +++ /dev/null @@ -1,24 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -interface IERC20 { - function transfer(address recipient, uint256 amount) external returns (bool); -} - -contract MultiSendUSDT { - address public tokenAddress; - - constructor(address _tokenAddress) { - tokenAddress = _tokenAddress; - } - - function multiTransfer(address[] calldata to, uint256[] calldata amounts) external { - IERC20 token = IERC20(tokenAddress); - uint256 length = to.length; - require(length == amounts.length, "Arrays must have the same length"); - - for (uint256 i = 0; i < length; i++) { - token.transfer(to[i], amounts[i]); - } - } -} \ No newline at end of file diff --git a/internal/blockchain/eth/eth.go b/internal/blockchain/eth/eth.go index f2aae2f..c8d3d4f 100644 --- a/internal/blockchain/eth/eth.go +++ b/internal/blockchain/eth/eth.go @@ -5,86 +5,36 @@ import ( "fmt" "log" "m2pool-payment/internal/db" + "m2pool-payment/internal/listen" 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" - } -] -` - -// USDT 合约地址(主网) -const USDTAddress = "0xdAC17F958D2ee523a2206206994597C13D831ec7" - -// 状态码常量 -const ( - STATUS_FAILED = 0 // 失败 - STATUS_SUCCESS = 1 // 成功 - STATUS_PENDING = 2 // 待确认 - STATUS_VERIFY_FAILED = 3 // 验证失败 -) - type ETHNode struct { - decodeKey string // 私钥解密密钥,仅针对普通钱包 - NetID *big.Int // 网络ID,主网为1,其他ID可通过rpc.NetworkID方法获取 + decodeKey string // 消息解密密钥,用于解密RMQ消息的Sign + NetID *big.Int 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 + MysqlDB *db.MySQLPool + SqliteDB *db.SQLite + USDT *USDT + NetInfo *NetInfo // 网络通用状态(当前高度、gas费用等) + // Messages map[string]any // {"queue_id": message.Topup{}, "queue_id": message.Withdraw{}, ...} + MessageServer *listen.ListenServer + UnConfirmedTxs *UnConfirmedTxs + Wallets map[string]*Wallets // {"address" : &Wallets{}, ...} + LogsChan chan *types.Header + Ctx context.Context + Cancel context.CancelFunc + mu sync.Mutex } type USDT struct { @@ -95,229 +45,176 @@ type USDT struct { LogsChan chan types.Log } -type RealData struct { +type UnConfirmedTxs struct { + mu sync.Mutex + Transactions map[string]message.Transaction // {"queue_id": message.Transactions{}, ...} +} + +type NetInfo struct { mu sync.Mutex - Heihgt uint64 + Height uint64 GasLimit uint64 GasTipCap *big.Int GasFeeCap *big.Int GasPrice *big.Int // 老版本转账使用的gas } -func NewETHNode(cfg message.ETHConfig, decodeKey string) (*ETHNode, error) { +type Wallets struct { + address string + queueId string + pk string + eth_balance *eth_balance + usdt_balance *usdt_balance + timestamp uint64 + sign string + status int + mu sync.Mutex +} + +type eth_balance struct { + symbol string // 默认ETH + used_gas *big.Int // 1 ETH = 1e18 WEI + balance *big.Int // 实际拥有的ETH余额 + successed_tx_hash []string + failed_tx_hash []string +} + +type usdt_balance struct { + symbol string + freeze_num *big.Int // 1 USDT = 1e6 WEI + balance *big.Int // 实际拥有的USDT余额 + successed_tx_hash []string + failed_tx_hash []string +} + +func NewETHNode(cfg message.Config, decodeKey string, l *listen.ListenServer) (*ETHNode, error) { // 连入ETH节点的ws - ws_client, err := ethclient.Dial(cfg.WsURL) + ws_client, err := ethclient.Dial(cfg.ETHConfig.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) + rpc_client, err := ethclient.Dial(cfg.ETHConfig.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) // 初始化合约日志通道 - usdt.ListeningAddresses = make(map[string]any) - // 初始化数据库 - dbConn, err := db.NewMySQLPool(cfg.DbConfig) + // 初始化MySQL数据库 + dbConn, err := db.NewMySQLPool(cfg.MysqlConfig["wallet"]) if err != nil { cancel() return nil, fmt.Errorf("mysql connect error: %w", err) } - // 初始化结构 - 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{} - return ÐNode{ + // 初始化SQLite3 + sqlite, err := db.NewSQLite(cfg.ETHConfig.SqlitePath) + if err != nil { + cancel() + return nil, fmt.Errorf("sqlite3 connect error: %w", err) + } + logchan := make(chan *types.Header) + // 初始化USDT + usdt := init_USDT() + ethnode := ÐNode{ decodeKey: decodeKey, NetID: netId, - Config: cfg, - ConfirmHeight: cfg.ConfirmHeight, + Config: cfg.ETHConfig, + ConfirmHeight: cfg.ETHConfig.ConfirmHeight, WsClient: ws_client, RpcClient: rpc_client, - Db: *dbConn, - TopupMsg: topup, - WithdrawMsg: withdraw, - PayMsg: pay, - UnConfirmTxs: unConfirmTxs, - LogsChan: logsChan, USDT: usdt, - RealData: realData, + MysqlDB: dbConn, + SqliteDB: sqlite, + NetInfo: &NetInfo{}, + UnConfirmedTxs: &UnConfirmedTxs{ + Transactions: make(map[string]message.Transaction), + }, + Wallets: make(map[string]*Wallets), + MessageServer: l, + LogsChan: logchan, Ctx: ctx, Cancel: cancel, - }, nil -} - -// ============================ 抽象接口 ============================ -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) } - return nil -} -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) + // 更新网络公共数据和加载钱包 + height, err := ethnode.getHeight() + if err != nil { + return nil, fmt.Errorf("failed to get blockHeight: %v", err) } - return nil -} - -func (e *ETHNode) Listen(ch chan any) { - log.Println("✅ 开始监听 ETH 和 USDT 转账事件...") - go func() { - err := e.listenETHTransactions(ch) - if err != nil { - log.Fatalf("Listen ETH Transactions Error: %v", err) - } - }() - - go func() { - err := e.listenUSDTTransactions(ch) - if err != nil { - log.Fatalf("Listen USDT Transactions Error: %v", err) - } - }() + // 更新网络公共数据 + ethnode.updateNetInfo(height) + // 加载钱包 + if err := ethnode.loadWallets(); err != nil { + return nil, fmt.Errorf("[inital eth wallets]: %v", err) + } + // 加载未确认交易 + if err := ethnode.loadUnConfirmedTxs(); err != nil { + return nil, fmt.Errorf("load unconfirmtxs error: %v", err) + } + log.Println("✅ ETH节点已启动") + return ethnode, nil } // 转账 -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 - } +func (e *ETHNode) Transfer(from, to, symbol string, amount, fee float64) error { + // 执行转账 + err := e.handleTx(symbol, from, to, amount) + if err != nil { + return fmt.Errorf("%s-tranfer error: %v", symbol, err) + } + return nil +} - // 2. 构建未签名交易 - unSignTx, err := e.contractTx(v.Symbol, v.FromAddress, v.ToAddress, v.Amount) - if err != nil { - log.Printf("failed to create contract transaction: %v", err) - return err - } +// 监听消息 +func (e *ETHNode) ListenMsg() { + log.Printf("✅ 开始监听msg...") + if e.MessageServer.ChToChainServer["ETH"] == nil { + log.Printf("ETH消息通道还未建立") + return + } - // 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++ + for msg := range e.MessageServer.ChToChainServer["ETH"] { + switch v := msg.(type) { + case message.TopupMsg_req: + go e.handleListen_Topup_req(v) + case message.WithdrawMsg_req: + go e.handleListen_Withdraw_req(v) + case message.PayMsg_req: + go e.handleListen_Pay_req(v) + case message.RemoveListenMsg_req: + go e.handleListen_Remove_req(v) + default: + log.Printf("ListenMsg error: %v", msg) } } +} + +// 监听区块链数据 +func (e *ETHNode) Listen() error { + log.Println("✅ 开始监听 ETH 和 USDT 转账事件...") + go func() { + err := e.listenETHTransactions() + if err != nil { + log.Printf("Listen ETH Transactions Error: %v", err) + } + }() + + go func() { + err := e.listenUSDTTransactions() + if err != nil { + log.Printf("Listen USDT Transactions Error: %v", err) + } + }() + return nil } @@ -327,816 +224,3 @@ func (e *ETHNode) Stop() { } log.Println("🛑 停止监听...") } - -// ============================ rpc节点方法 ============================ -func (e *ETHNode) getETHBalance(address string) (*big.Int, error) { - 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 -} - -func (e *ETHNode) getUSDTBalance(address string) (*big.Int, error) { - // 统一转换为小写(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 { - return nil, fmt.Errorf("failed to pack balanceOf data: %w", err) - } - msg := ethereum.CallMsg{ - To: &contractAddress, - Data: data, - } - // 使用 CallContract 方法查询合约余额 - res, err := e.RpcClient.CallContract(e.Ctx, msg, nil) - if err != nil { - return nil, fmt.Errorf("failed to get contract balance: %w", err) - } - // 解析返回的字节为 *big.Int - outputs, err := e.USDT.ABI.Unpack("balanceOf", res) - if err != nil || len(outputs) == 0 { - return nil, fmt.Errorf("failed to unpack balanceOf result: %w", err) - } - balance, ok := outputs[0].(*big.Int) - if !ok { - 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 - } - return nonce, nil -} - -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 -} - -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 - } -} - -// 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 -} - -// ============================ 业务逻辑 ============================ -// 监听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交易...") - // 过滤掉非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, ch) // 事件解析 + 分类,传递链消息的通道是vLog而非ch,且一次只传递一笔交易 - case <-e.Ctx.Done(): - fmt.Println("🛑 收到停止信号,退出USDT交易监听") - sub.Unsubscribe() - return e.Ctx.Err() - } - } - reconnect: - } -} - -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))) - } - }() - - // 等待所有协程完成 - wg.Wait() - - // 检查是否有任何错误 - if gasLimitErr != nil || suggestGasPriceErr != nil || eip1559GasFeesErr != nil { - log.Println("One or more methods failed. Not updating RealData.") - return - } - - // 更新 RealData - e.RealData.mu.Lock() - defer e.RealData.mu.Unlock() - e.RealData = &RealData{ - Heihgt: height, - GasLimit: gasLimit, - GasTipCap: maxPriorityFeePerGas, - GasFeeCap: maxFeePerGas, - GasPrice: suggestGasPrice, - } - // log.Println("✅ RealData updated successfully.") -} - -func (e *ETHNode) handleETHEvent(header *types.Header, ch chan any) { - 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()) - - // 处理充值 - for k, v := range e.TopupMsg { - if k == toAddr { - // 锁定并更新未确认的交易 - 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.TopupMsg[k].Status = STATUS_PENDING - e.mu.Unlock() - - // 创建待确认充值消息 - 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, - } - - // 异步发送消息到通道 - go func(msg message.TopupMsg_resp) { - select { - case ch <- msg: - log.Printf("✅ 待确认充值消息已发送") - default: - log.Printf("⚠️ 通道阻塞,待确认消息发送失败") - } - }(topup_unconfirm_msg_resp) - } - } - - // 处理提现 - 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() - } - } - - // 处理支付 - 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() - } - } - } - } - } -} - -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) - return - } - 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, - } - 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("⚠️ 通道阻塞,待确认消息发送失败") - } - }(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, - } - 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, - } - e.PayMsg[k].Trasnactions[i].Status = STATUS_PENDING - e.mu.Unlock() - } - - } - } - } -} - -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 -} - -// 验证余额 -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") - } - 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) - } -} - -// 构建交易 -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, 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!") - } - 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) - 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) - } - - 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, 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) - // } - - // 获取当前区块高度 - e.mu.Lock() - height := e.RealData.Heihgt - e.mu.Unlock() - - // 将交易存入待交易池 - 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 -} - -// 确认信息并返回 -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 - } - - 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 // 充值成功后不删除,可能还有后续充值 - } - } - - // 判断是否在提现消息中 - 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 - } - } - - // 判断是否在支付消息中 - 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 - } - } - - // 判断是否在提现消息中 - 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 - } - } - - // 判断是否在支付消息中 - 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() - - // 标记为删除 - toDeleteTxs = append(toDeleteTxs, k) - } - } - - // 删除已确认的交易 - if len(toDeleteTxs) > 0 { - e.mu.Lock() - for _, txHash := range toDeleteTxs { - delete(e.UnConfirmTxs, txHash) - } - e.mu.Unlock() - } - - // 检查支付消息是否需要发送完整响应 - 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() - - // 异步发送所有响应消息 - 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) - } - } -} diff --git a/internal/blockchain/eth/eth_prv.go b/internal/blockchain/eth/eth_prv.go new file mode 100644 index 0000000..ff8b38f --- /dev/null +++ b/internal/blockchain/eth/eth_prv.go @@ -0,0 +1,1295 @@ +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, ×tamp, &sign, &status, + ðUsedGas, ðBalance, + ðSuccess, ðFailed, + &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 = ð_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: ð_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) + } + } +} diff --git a/internal/constant/constant.go b/internal/constant/constant.go new file mode 100644 index 0000000..867ae2b --- /dev/null +++ b/internal/constant/constant.go @@ -0,0 +1,44 @@ +package constant + +const ( + STATUS_FAILED = 0 // 失败 + STATUS_SUCCESS = 1 // 成功 + STATUS_PENDING = 2 // 待确认 + STATUS_VERIFY_FAILED = 3 // 验证失败 + STATUS_BALANCE_NOT_ENOUGH = 4 // 钱包余额不足 + STATUS_UNTRANSFER = 5 // 未支付 + STATUS_END = 6 // 完成 + STATUS_ERROR = 7 +) +const ETH_ERC20_USDT_CONTRACT_ADDRESS = "0xdAC17F958D2ee523a2206206994597C13D831ec7" +const ETH_ERC20_ABI = ` +[ + { + "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" + } +] +` diff --git a/internal/db/db.go b/internal/db/mysql.go similarity index 95% rename from internal/db/db.go rename to internal/db/mysql.go index 1ee8cfa..8d6e73d 100644 --- a/internal/db/db.go +++ b/internal/db/mysql.go @@ -13,7 +13,7 @@ type MySQLPool struct { } // NewMySQLPool 初始化连接池 -func NewMySQLPool(cfg message.DbConfig) (*MySQLPool, error) { +func NewMySQLPool(cfg message.MysqlConfig) (*MySQLPool, error) { dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8mb4&parseTime=True&loc=Local", cfg.User, cfg.Password, cfg.Host, cfg.Port, cfg.Database) db, err := sql.Open("mysql", dsn) diff --git a/internal/db/sqlite.go b/internal/db/sqlite.go index 4aa42e0..355a664 100644 --- a/internal/db/sqlite.go +++ b/internal/db/sqlite.go @@ -9,15 +9,20 @@ import ( type SQLite struct { DB *sql.DB + // Ch chan any } // 初始化连接 func NewSQLite(path string) (*SQLite, error) { db, err := sql.Open("sqlite", path) + // ch := make(chan any, 1000) if err != nil { return nil, fmt.Errorf("open sqlite failed: %v", err) } - return &SQLite{DB: db}, nil + return &SQLite{ + DB: db, + // Ch: ch, + }, nil } // 关闭数据库 @@ -27,8 +32,8 @@ func (s *SQLite) Close() { } } -// 通用新建表 -func (s *SQLite) Exec_(sql string) error { +// 新建表 +func (s *SQLite) CreateTable(sql string) error { _, err := s.DB.Exec(sql) if err != nil { return fmt.Errorf("Exec DB error: %w", err) @@ -36,6 +41,48 @@ func (s *SQLite) Exec_(sql string) error { return nil } +// 插入数据(支持事务和批量) +func (s *SQLite) Insert(sqlStr string, args ...[]any) error { + tx, err := s.DB.Begin() + if err != nil { + return err + } + stmt, err := tx.Prepare(sqlStr) + if err != nil { + return err + } + defer stmt.Close() + + for _, a := range args { + _, err = stmt.Exec(a...) + if err != nil { + tx.Rollback() + return fmt.Errorf("exec insert error: %v", err) + } + } + return tx.Commit() +} + +// 删除数据 +func (s *SQLite) Delete(sqlStr string, args ...any) (int64, error) { + res, err := s.DB.Exec(sqlStr, args...) + if err != nil { + return 0, fmt.Errorf("delete error: %v", err) + } + rows, _ := res.RowsAffected() + return rows, nil +} + +// 更新数据 +func (s *SQLite) Update(sqlStr string, args ...any) (int64, error) { + res, err := s.DB.Exec(sqlStr, args...) + if err != nil { + return 0, fmt.Errorf("update error: %v", err) + } + rows, _ := res.RowsAffected() + return rows, nil +} + // 通用查询方法(返回[]map[string]any) func (s *SQLite) Query_(query string, args ...any) ([]map[string]any, error) { rows, err := s.DB.Query(query, args...) @@ -77,44 +124,44 @@ func (s *SQLite) Query_(query string, args ...any) ([]map[string]any, error) { return results, nil } -// 插入数据(支持事务和批量) -func (s *SQLite) Insert(sqlStr string, args ...[]any) error { +// 事务增删改(多条增删改串行,只要有一条增删改失败则全部回滚并返回错误) +func (s *SQLite) ExecuteTransactions(str_sqls []string, params [][]any) error { + // 检查 SQL 和参数的数量是否匹配 + if len(str_sqls) != len(params) { + return fmt.Errorf("sql length != params length") + } + + // 开始事务 tx, err := s.DB.Begin() if err != nil { - return err + return fmt.Errorf("failed to begin transaction: %v", err) } - stmt, err := tx.Prepare(sqlStr) - if err != nil { - return err - } - defer stmt.Close() - for _, a := range args { - _, err = stmt.Exec(a...) + // 确保在函数结束时提交或回滚事务 + defer func() { if err != nil { - tx.Rollback() - return fmt.Errorf("exec insert error: %v", err) + // 发生错误时回滚事务 + if rollbackErr := tx.Rollback(); rollbackErr != nil { + err = fmt.Errorf("failed to rollback transaction: %v", rollbackErr) + } + } else { + // 如果没有错误,提交事务 + if commitErr := tx.Commit(); commitErr != nil { + err = fmt.Errorf("failed to commit transaction: %v", commitErr) + } + } + }() + + // 执行每个 SQL 语句 + for i, sql_str := range str_sqls { + // 使用事务对象 tx 来执行 SQL + _, err := tx.Exec(sql_str, params[i]...) + if err != nil { + // 如果执行失败,立即返回并且触发回滚 + return fmt.Errorf("failed to execute SQL: %v", err) } } - return tx.Commit() -} -// 更新数据 -func (s *SQLite) Update(sqlStr string, args ...any) (int64, error) { - res, err := s.DB.Exec(sqlStr, args...) - if err != nil { - return 0, fmt.Errorf("update error: %v", err) - } - rows, _ := res.RowsAffected() - return rows, nil -} - -// 删除数据 -func (s *SQLite) Delete(sqlStr string, args ...any) (int64, error) { - res, err := s.DB.Exec(sqlStr, args...) - if err != nil { - return 0, fmt.Errorf("delete error: %v", err) - } - rows, _ := res.RowsAffected() - return rows, nil + // 如果所有 SQL 执行成功,则返回 nil + return nil } diff --git a/internal/listen/listen.go b/internal/listen/listen.go new file mode 100644 index 0000000..83d0663 --- /dev/null +++ b/internal/listen/listen.go @@ -0,0 +1,328 @@ +package listen + +import ( + "fmt" + "log" + "m2pool-payment/internal/db" + message "m2pool-payment/internal/msg" + "m2pool-payment/internal/utils" + "sync" +) + +type ListenServer struct { + Config message.Config + // MysqlDB *db.MySQLPool + SqliteDB *db.SQLite + TopupMsgs map[string]*TopupMsgs // {"ETH": TopupMsgs{"queue_id": message.Topupmsg_req{}, ...}} + WithdrawMsgs map[string]*WithdrawMsgs + PayMsgs map[string]*PayMsgs + RemoveMsgs map[string]*RemoveMsgs + ChToChainServer map[string]chan any + ChFromChainServer map[string]chan any + ChFromRmqServer chan any + ChToRmqServer chan any + mu sync.Mutex +} + +type TopupMsgs struct { + Msgs map[string]message.TopupMsg_req // {"queue_id": message.Topupmsg_req{}, ...} + mu sync.RWMutex // 读写锁 +} + +type WithdrawMsgs struct { + Msgs map[string]message.WithdrawMsg_req + mu sync.RWMutex // 读写锁 +} + +type PayMsgs struct { + Msgs map[string]message.PayMsg_req + mu sync.RWMutex // 读写锁 +} + +type RemoveMsgs struct { + Msgs map[string]message.RemoveListenMsg_req + mu sync.RWMutex // 读写锁 +} + +func NewListenServer(cfg message.Config) *ListenServer { + // // 初始化MySQL数据库 + // dbConn, err := db.NewMySQLPool(cfg.MysqlConfig["wallet"]) + // if err != nil { + // log.Printf("mysql connect error: %v", err) + // return nil + // } + + // 初始化SQLite3 + sqlite, err := db.NewSQLite(cfg.MsgConfig.SqlitePath) + if err != nil { + log.Printf("sqlite3 connect error: %v", err) + return nil + } + nets := cfg.Net + topup_msgs := make(map[string]*TopupMsgs) + withdraw_msgs := make(map[string]*WithdrawMsgs) + pay_msgs := make(map[string]*PayMsgs) + remove_msgs := make(map[string]*RemoveMsgs) + for _, net := range nets { + topup_msgs[net] = &TopupMsgs{ + Msgs: make(map[string]message.TopupMsg_req), + } + withdraw_msgs[net] = &WithdrawMsgs{ + Msgs: make(map[string]message.WithdrawMsg_req), + } + pay_msgs[net] = &PayMsgs{ + Msgs: make(map[string]message.PayMsg_req), + } + remove_msgs[net] = &RemoveMsgs{ + Msgs: make(map[string]message.RemoveListenMsg_req), + } + } + ch := make(map[string]chan any) + chin := make(map[string]chan any) + rmq_ch_in := make(chan any, 1000) + rmq_ch_out := make(chan any, 1000) + for _, net := range cfg.Net { + ch[net] = make(chan any, 1000) + chin[net] = make(chan any, 1000) + } + + log.Println("✅ 消息监听处理已启动") + return &ListenServer{ + Config: cfg, + // MysqlDB: dbConn, + SqliteDB: sqlite, + TopupMsgs: topup_msgs, + WithdrawMsgs: withdraw_msgs, + PayMsgs: pay_msgs, + RemoveMsgs: remove_msgs, + ChToChainServer: ch, + ChFromChainServer: chin, + ChFromRmqServer: rmq_ch_in, + ChToRmqServer: rmq_ch_out, + } +} + +// rmq -> listen -> chain server +func (l *ListenServer) RmqMsgIn() { + for msg := range l.ChFromRmqServer { + switch v := msg.(type) { + case message.TopupMsg_req: + if !utils.Contains(l.Config.Net, v.Chain) { + log.Printf("%s has not support", v.Chain) + continue + } + go l.handleRmqTopup_req(v) + case message.WithdrawMsg_req: + if !utils.Contains(l.Config.Net, v.Chain) { + log.Printf("%s has not support", v.Chain) + continue + } + go l.handleRmqWithdraw_req(v) + case message.PayMsg_req: + if !utils.Contains(l.Config.Net, v.Chain) { + log.Printf("%s has not support", v.Chain) + continue + } + go l.handleRmqPay_req(v) + case message.RemoveListenMsg_req: + if !utils.Contains(l.Config.Net, v.Chain) { + log.Printf("%s has not support", v.Chain) + continue + } + go l.handleRmqRemove_req(v) + default: + log.Printf("ListenMsg error: %v", msg) + } + } +} + +// chain server -> listen -> rmq +func (l *ListenServer) NetMsgIn() { + for _, ch := range l.ChFromChainServer { + for msg := range ch { + switch v := msg.(type) { + // 接收到区块链节点服务返回的resp消息,除了修改相关状态和数据库,还需通过rmq_out通道返回出去 + case message.TopupMsg_resp: + go l.handleChainTopup_resp(v) + case message.WithdrawMsg_resp: + go l.handleChainWithdraw_resp(v) + case message.PayData: + go l.handleChainPay_resp(v) + case message.RemoveListenMsg_resp: + go l.handleChainRemove_resp(v) + default: + log.Printf("MsgType not found: %v", v) + } + } + } +} + +// 根据 address 查找对应的消息 +func (l *ListenServer) FindMsgWithAddress(chain, address string) (any, error) { + // 检查链是否支持 + if !utils.Contains(l.Config.Net, chain) { + return nil, fmt.Errorf("%s has not support", chain) + } + + // 查找 TopupMsgs + l.TopupMsgs[chain].mu.RLock() + for _, topup_msg := range l.TopupMsgs[chain].Msgs { + if address == topup_msg.Address { + l.TopupMsgs[chain].mu.RUnlock() + return topup_msg, nil + } + } + l.TopupMsgs[chain].mu.RUnlock() + + // 查找 WithdrawMsgs + l.WithdrawMsgs[chain].mu.RLock() + for _, withdraw_msg := range l.WithdrawMsgs[chain].Msgs { + if address == withdraw_msg.FromAddress { + l.WithdrawMsgs[chain].mu.RUnlock() + return withdraw_msg, nil + } + } + l.WithdrawMsgs[chain].mu.RUnlock() + + // 查找 PayMsgs + l.PayMsgs[chain].mu.RLock() + for _, pay_msg := range l.PayMsgs[chain].Msgs { + if address == pay_msg.FromAddress { + l.PayMsgs[chain].mu.RUnlock() + return pay_msg, nil + } + } + l.PayMsgs[chain].mu.RUnlock() + + // 如果没有找到匹配项,返回错误 + return nil, fmt.Errorf("Address(%s) not found in any message", address) +} + +// 根据address查找Topup消息 +func (l *ListenServer) FindTopupMsgWithToaddress(chain, toAddress string) (message.TopupMsg_req, error) { + // 检查链是否支持 + if !utils.Contains(l.Config.Net, chain) { + return message.TopupMsg_req{}, fmt.Errorf("%s has not support", chain) + } + l.TopupMsgs[chain].mu.RLock() + for _, topup_msg := range l.TopupMsgs[chain].Msgs { + if toAddress == topup_msg.Address { + l.TopupMsgs[chain].mu.RUnlock() + return topup_msg, nil + } + } + l.TopupMsgs[chain].mu.RUnlock() + return message.TopupMsg_req{}, fmt.Errorf("Address(%s) not found in topup message", toAddress) +} + +// 根据address查找Topup消息 +func (l *ListenServer) FindWithdrawMsgWithToaddress(chain, fromAddress string) (message.WithdrawMsg_req, error) { + // 检查链是否支持 + if !utils.Contains(l.Config.Net, chain) { + return message.WithdrawMsg_req{}, fmt.Errorf("%s has not support", chain) + } + l.WithdrawMsgs[chain].mu.RLock() + for _, withdraw_msg := range l.WithdrawMsgs[chain].Msgs { + if fromAddress == withdraw_msg.FromAddress { + l.WithdrawMsgs[chain].mu.RUnlock() + return withdraw_msg, nil + } + } + l.WithdrawMsgs[chain].mu.RUnlock() + return message.WithdrawMsg_req{}, fmt.Errorf("Address(%s) not found in withdraw message", fromAddress) +} + +// 根据address查找Topup消息 +func (l *ListenServer) FindPayMsgWithToaddress(chain, fromAddress string) (message.PayMsg_req, error) { + // 检查链是否支持 + if !utils.Contains(l.Config.Net, chain) { + return message.PayMsg_req{}, fmt.Errorf("%s has not support", chain) + } + l.PayMsgs[chain].mu.RLock() + for _, pay_msg := range l.PayMsgs[chain].Msgs { + if fromAddress == pay_msg.FromAddress { + l.PayMsgs[chain].mu.RUnlock() + return pay_msg, nil + } + } + l.PayMsgs[chain].mu.RUnlock() + return message.PayMsg_req{}, fmt.Errorf("Address(%s) not found in pay message", fromAddress) +} + +// 根据queue_id查找到对应消息 +func (l *ListenServer) FindMsgWithQueueID(chain, queue_id string, txType int) (any, error) { + if !utils.Contains(l.Config.Net, chain) { + return nil, fmt.Errorf("%s has not support", chain) + } + + switch txType { + case 0: + l.TopupMsgs[chain].mu.RLock() + defer l.TopupMsgs[chain].mu.RUnlock() + + if msg, ok := l.TopupMsgs[chain].Msgs[queue_id]; ok { + return msg, nil + } else { + return nil, fmt.Errorf("QueueID(%s) not found in txType(%d)", queue_id, txType) + } + case 1: + l.WithdrawMsgs[chain].mu.RLock() + defer l.WithdrawMsgs[chain].mu.RUnlock() + + if msg, ok := l.WithdrawMsgs[chain].Msgs[queue_id]; ok { + return msg, nil + } else { + return nil, fmt.Errorf("QueueID(%s) not found in txType(%d)", queue_id, txType) + } + case 2: + l.PayMsgs[chain].mu.RLock() + defer l.PayMsgs[chain].mu.RUnlock() + + if msg, ok := l.PayMsgs[chain].Msgs[queue_id]; ok { + return msg, nil + } else { + return nil, fmt.Errorf("QueueID(%s) not found in txType(%d)", queue_id, txType) + } + default: + return nil, fmt.Errorf("This txType(%d) don`t support:txType(0,1,2)", txType) + } +} + +func (l *ListenServer) ReadTopupMsgs(chain string) (map[string]message.TopupMsg_req, error) { + l.TopupMsgs[chain].mu.RLock() + defer l.TopupMsgs[chain].mu.RUnlock() + + var topup_msgs = make(map[string]message.TopupMsg_req) + if !utils.Contains(l.Config.Net, chain) { + return topup_msgs, fmt.Errorf("%s has not support", chain) + } + + topup_msgs = l.TopupMsgs[chain].Msgs + return topup_msgs, nil +} + +func (l *ListenServer) ReadWithdrawMsgs(chain string) (map[string]message.WithdrawMsg_req, error) { + l.WithdrawMsgs[chain].mu.RLock() + defer l.WithdrawMsgs[chain].mu.RUnlock() + + var withdraw_msgs = make(map[string]message.WithdrawMsg_req) + if !utils.Contains(l.Config.Net, chain) { + return withdraw_msgs, fmt.Errorf("%s has not support", chain) + } + + withdraw_msgs = l.WithdrawMsgs[chain].Msgs + return withdraw_msgs, nil +} + +func (l *ListenServer) ReadPayMsgs(chain string) (map[string]message.PayMsg_req, error) { + l.PayMsgs[chain].mu.RLock() + defer l.PayMsgs[chain].mu.RUnlock() + + var pay_msgs = make(map[string]message.PayMsg_req) + if !utils.Contains(l.Config.Net, chain) { + return pay_msgs, fmt.Errorf("%s has not support", chain) + } + + pay_msgs = l.PayMsgs[chain].Msgs + return pay_msgs, nil +} diff --git a/internal/listen/listen_prv.go b/internal/listen/listen_prv.go new file mode 100644 index 0000000..800c940 --- /dev/null +++ b/internal/listen/listen_prv.go @@ -0,0 +1,295 @@ +package listen + +import ( + "fmt" + "log" + "m2pool-payment/internal/constant" + message "m2pool-payment/internal/msg" + "strings" + "time" +) + +// 充值消息 +func (l *ListenServer) handleRmqTopup_req(msg message.TopupMsg_req) { + // 添加到TopupMsgs + l.TopupMsgs[msg.Chain].mu.RLock() + l.TopupMsgs[msg.Chain].Msgs[msg.QueueId] = msg + l.TopupMsgs[msg.Chain].mu.RUnlock() + // 写数据库 + go func() { + sql := "INSERT INTO topup_req_msg (queue_id, chain, symbol, address, timestamp, sign, status) VALUES (?,?,?,?,?,?,?)" + params := []any{msg.QueueId, msg.Chain, msg.Symbol, msg.Address, msg.Timestamp, msg.Sign, 1} + err := l.SqliteDB.Insert(sql, params) + if err != nil { + log.Printf("Insert Topup_req msg error: %v", err) + } + }() + // 传给对应的node server + go l.asyncSendMsgToChain(msg, msg.Chain, 3, 5*time.Second) + log.Printf("Insert Topup_req msg success: QueueId(%s)", msg.QueueId) +} + +// 提现消息 +func (l *ListenServer) handleRmqWithdraw_req(msg message.WithdrawMsg_req) { + // 添加到WithdrawMsgs + l.WithdrawMsgs[msg.Chain].mu.RLock() + l.WithdrawMsgs[msg.Chain].Msgs[msg.QueueId] = msg + l.WithdrawMsgs[msg.Chain].mu.RUnlock() + // 写数据库 + go func() { + sql := "INSERT INTO withdraw_req_msg (queue_id, from_addr, to_addr, amount, fee, chain, symbol, timestamp, sign) VALUES (?,?,?,?,?,?,?,?,?)" + params := []any{msg.QueueId, msg.FromAddress, msg.ToAddress, fmt.Sprintf("%f", msg.Amount), fmt.Sprintf("%f", msg.Fee), msg.Chain, msg.Symbol, msg.Timestamp, msg.Sign} + err := l.SqliteDB.Insert(sql, params) + if err != nil { + log.Printf("Insert Withdraw_req msg error: %v", err) + } + }() + // 传给对应的node server + go l.asyncSendMsgToChain(msg, msg.Chain, 3, 5*time.Second) + log.Printf("Insert Withdraw_req msg success: QueueId(%s)", msg.QueueId) +} + +// 支付消息 +func (l *ListenServer) handleRmqPay_req(msg message.PayMsg_req) { + l.PayMsgs[msg.Chain].mu.RLock() + l.PayMsgs[msg.Chain].Msgs[msg.QueueId] = msg + l.PayMsgs[msg.Chain].mu.RUnlock() + + go func() { + sql1 := "INSERT INTO pay_req_msg (queue_id, chain, symbol, from_addr, total_amount, total_fee, timestamp, sign) VALUES (?,?,?,?,?,?,?,?)" + sql2 := "INSERT INTO transactions (queue_id, chain, symbol, from_addr, to_addr, amount, fee) VALUES " + params1 := []any{msg.QueueId, msg.Chain, msg.Symbol, msg.FromAddress, fmt.Sprintf("%f", msg.TotalAmount), fmt.Sprintf("%f", msg.TotalFee), msg.Timestamp, msg.Sign} + params2 := []any{} + for to_addr, tx := range msg.Transactions { + sql2 += "(?,?,?,?,?,?,?), " + params2 = append(params2, msg.QueueId, msg.Chain, msg.Symbol, strings.ToLower(msg.FromAddress), strings.ToLower(to_addr), fmt.Sprintf("%f", tx.Amount), fmt.Sprintf("%f", tx.Fee)) + } + sql2 = sql2[:len(sql2)-2] + err := l.SqliteDB.ExecuteTransactions([]string{sql1, sql2}, [][]any{params1, params2}) + if err != nil { + log.Printf("Insert Pay_req msg error: %v", err) + } + }() + + go l.asyncSendMsgToChain(msg, msg.Chain, 3, 5*time.Second) + log.Printf("Insert Pay_req msg success: QueueId(%s)", msg.QueueId) +} + +// 移除监听消息 +func (l *ListenServer) handleRmqRemove_req(msg message.RemoveListenMsg_req) { + l.RemoveMsgs[msg.Chain].mu.RLock() + l.RemoveMsgs[msg.Chain].Msgs[msg.QueueId] = msg + l.RemoveMsgs[msg.Chain].mu.RUnlock() + + go func() { + sql := "INSERT INTO remove_req_msg (queue_id, msg_type, chain, symbol, address, timestamp, sign) VALUES (?,?,?,?,?,?,?)" + params := []any{msg.QueueId, msg.MsgType, msg.Chain, msg.Symbol, msg.Address, msg.Timestamp, msg.Sign} + err := l.SqliteDB.Insert(sql, params) + if err != nil { + log.Printf("Insert Remove_req msg error: %v", err) + } + }() + + go l.asyncSendMsgToChain(msg, msg.Chain, 3, 5*time.Second) + log.Printf("Insert Remove_req msg success: QueueId(%s)", msg.QueueId) +} + +// 充值响应 +func (l *ListenServer) handleChainTopup_resp(msg message.TopupMsg_resp) { + +} + +// 提现响应 +func (l *ListenServer) handleChainWithdraw_resp(msg message.WithdrawMsg_resp) { + switch msg.Status { + // pending状态表示转账已完成且在区块链中被找到,等待后续确认+记录到数据库 + case constant.STATUS_PENDING: + go func() { + str := "INSERT INTO withdraw_resp_msg (queue_id, chain, symbol, from_addr, to_addr, tx_hash, amount, fee, height, status) VALUES (?,?,?,?,?,?,?,?,?,?)" + params := []any{msg.QueueId, msg.Chain, msg.Symbol, msg.FromAddress, msg.ToAddress, msg.TxHash, msg.Amount, msg.Fee, msg.BlockHeight, msg.Status} + err := l.SqliteDB.Insert(str, params) + if err != nil { + log.Println(err) + } + }() + case constant.STATUS_SUCCESS, constant.STATUS_FAILED: + l.WithdrawMsgs[msg.Chain].mu.Lock() + delete(l.WithdrawMsgs[msg.Chain].Msgs, msg.QueueId) + l.WithdrawMsgs[msg.Chain].mu.Unlock() + + go func() { + str := "UPDATE withdraw_resp_msg SET status = ? WHERE tx_hash = ?" + params := []any{msg.Status, msg.TxHash} + count, err := l.SqliteDB.Update(str, params) + if err != nil || count != 1 { + log.Printf("count=%d, err=%v", count, err) + } + }() + go l.asyncSendMsgToRmq(msg, 3, 5*time.Second) + default: + l.WithdrawMsgs[msg.Chain].mu.Lock() + delete(l.WithdrawMsgs[msg.Chain].Msgs, msg.QueueId) + l.WithdrawMsgs[msg.Chain].mu.Unlock() + + go func() { + str := "INSERT INTO withdraw_resp_msg (queue_id, chain, symbol, from_addr, to_addr, amount, fee, status) VALUES (?,?,?,?,?,?,?,?)" + params := []any{msg.QueueId, msg.Chain, msg.Symbol, msg.FromAddress, msg.ToAddress, fmt.Sprintf("%f", msg.Amount), fmt.Sprintf("%f", msg.Fee), msg.Status} + err := l.SqliteDB.Insert(str, params) + if err != nil { + log.Println(err) + } + }() + go l.asyncSendMsgToRmq(msg, 3, 5*time.Second) + } +} + +// 支付响应 +func (l *ListenServer) handleChainPay_resp(msg message.PayData) { + switch msg.Status { + case constant.STATUS_PENDING: + l.PayMsgs[msg.Chain].mu.Lock() + l.PayMsgs[msg.Chain].Msgs[msg.QueueId].Transactions[msg.ToAddress] = msg + l.PayMsgs[msg.Chain].mu.Unlock() + + go func() { + str := "INSERT INTO pay_msg_tx (queue_id, tx_hash, to_addr, amount, fee, height, status) VALUES (?,?,?,?,?,?,?)" + params := []any{msg.QueueId, msg.TxHash, msg.ToAddress, fmt.Sprintf("%f", msg.Amount), fmt.Sprintf("%f", msg.Fee), msg.BlockHeight, msg.Status} + err := l.SqliteDB.Insert(str, params) + if err != nil { + log.Printf("Insert pay_msg_tx msg error: %v", err) + } + + }() + case constant.STATUS_SUCCESS, constant.STATUS_FAILED: + l.PayMsgs[msg.Chain].mu.Lock() + l.PayMsgs[msg.Chain].Msgs[msg.QueueId].Transactions[msg.ToAddress] = msg + l.PayMsgs[msg.Chain].mu.Unlock() + + go func() { + str := "UPDATE pay_msg_tx SET status = ? WHERE tx_hash = ?" + params := []any{msg.Status, msg.TxHash} + count, err := l.SqliteDB.Update(str, params...) + if err != nil { + // 更详细的错误日志,包括 QueueId 和 Status + log.Printf("Failed to update remove_resp_msg for queue_id %s: %v", msg.QueueId, err) + } else if count != 1 { + // 如果更新的行数不是 1,日志中记录详细信息 + log.Printf("Unexpected update count for queue_id %s: expected 1, got %d", msg.QueueId, count) + } + }() + + go func() { + need_send := true + l.PayMsgs[msg.Chain].mu.Lock() + for _, tx := range l.PayMsgs[msg.Chain].Msgs[msg.QueueId].Transactions { + if tx.Status != constant.STATUS_SUCCESS && tx.Status != constant.STATUS_FAILED { + need_send = false + break + } + } + l.PayMsgs[msg.Chain].mu.Unlock() + if need_send { + str := "INSERT INTO pay_resp_msg (queue_id, chain, symbol, from_addr, pay_status) VALUES (?,?,?,?,?)" + params := []any{l.PayMsgs[msg.Chain].Msgs[msg.QueueId].QueueId, l.PayMsgs[msg.Chain].Msgs[msg.QueueId].Chain, l.PayMsgs[msg.Chain].Msgs[msg.QueueId].Symbol, l.PayMsgs[msg.Chain].Msgs[msg.QueueId].FromAddress, constant.STATUS_SUCCESS} + err := l.SqliteDB.Insert(str, params) + if err != nil { + log.Printf("Insert pay_resp_msg msg error: %v", err) + } + resp := message.PayMsg_resp{ + QueueId: msg.QueueId, + Chain: l.PayMsgs[msg.Chain].Msgs[msg.QueueId].Chain, + Symbol: l.PayMsgs[msg.Chain].Msgs[msg.QueueId].Symbol, + FromAddress: l.PayMsgs[msg.Chain].Msgs[msg.QueueId].FromAddress, + PayStatus: constant.STATUS_SUCCESS, + Transactions: l.PayMsgs[msg.Chain].Msgs[msg.QueueId].Transactions, + } + go l.asyncSendMsgToRmq(resp, 3, 5*time.Second) + } + }() + default: + l.PayMsgs[msg.Chain].mu.Lock() + l.PayMsgs[msg.Chain].Msgs[msg.QueueId].Transactions[msg.ToAddress] = msg + l.PayMsgs[msg.Chain].mu.Unlock() + + go func() { + str := "UPDATE pay_msg_tx SET status = ? WHERE tx_hash = ?" + params := []any{msg.Status, msg.TxHash} + count, err := l.SqliteDB.Update(str, params...) + if err != nil { + // 更详细的错误日志,包括 QueueId 和 Status + log.Printf("Failed to update remove_resp_msg for queue_id %s: %v", msg.QueueId, err) + } else if count != 1 { + // 如果更新的行数不是 1,日志中记录详细信息 + log.Printf("Unexpected update count for queue_id %s: expected 1, got %d", msg.QueueId, count) + } + }() + } +} + +// 移除监听响应 +func (l *ListenServer) handleChainRemove_resp(msg message.RemoveListenMsg_resp) { + // 如果状态为成功,移除该 QueueId + if msg.Status == constant.STATUS_SUCCESS { + l.RemoveMsgs[msg.Chain].mu.Lock() + delete(l.RemoveMsgs[msg.Chain].Msgs, msg.QueueId) + l.RemoveMsgs[msg.Chain].mu.Unlock() + } + + // 使用 goroutine 异步更新数据库 + go func() { + // 更新数据库 + str := "UPDATE remove_resp_msg SET status = ? WHERE queue_id = ?" + params := []any{msg.Status, msg.QueueId} + count, err := l.SqliteDB.Update(str, params...) + if err != nil { + // 更详细的错误日志,包括 QueueId 和 Status + log.Printf("Failed to update remove_resp_msg for queue_id %s: %v", msg.QueueId, err) + } else if count != 1 { + // 如果更新的行数不是 1,日志中记录详细信息 + log.Printf("Unexpected update count for queue_id %s: expected 1, got %d", msg.QueueId, count) + } + }() + + // 异步发送消息到 RMQ + go l.asyncSendMsgToRmq(msg, 3, 5*time.Second) +} + +// 异步发送通道消息,Listen -> Chain server +func (l *ListenServer) asyncSendMsgToChain(msg any, chain string, retries int, timeout time.Duration) { + for retries > 0 { + select { + case l.ChToChainServer[chain] <- msg: // 如果通道没有满,就发送消息 + // log.Printf("Sent message to rmq_ch_out: %v", msg) + return + case <-time.After(timeout): // 超时控制 + log.Printf("Timeout sending message to rmq_ch_out: %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) + } + } +} + +// 异步发送通道消息,Listen -> Rmq +func (l *ListenServer) asyncSendMsgToRmq(msg any, retries int, timeout time.Duration) { + for retries > 0 { + select { + case l.ChToRmqServer <- msg: // 如果通道没有满,就发送消息 + // log.Printf("Sent message to rmq_ch_out: %v", msg) + return + case <-time.After(timeout): // 超时控制 + log.Printf("Timeout sending message to rmq_ch_out: %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) + } + } +} diff --git a/internal/logger/transaction_logger.go b/internal/logger/transaction_logger.go index 0b12217..54db413 100644 --- a/internal/logger/transaction_logger.go +++ b/internal/logger/transaction_logger.go @@ -211,20 +211,20 @@ func LogTopup(toAddress string, status string, amount float64, txHash string, bl } // LogWithdraw 记录提现消息 -func LogWithdraw(toAddress string, status string, amount float64, fromAddress string, txHash string, blockHeight uint64) { +func LogWithdraw(fromAddress string, status string, amount float64, toAddress string, txHash string, blockHeight uint64) { if txLogger == nil { return } // 使用 toAddress 作为文件名 - lf, err := txLogger.getOrCreateLogFile(toAddress) + lf, err := txLogger.getOrCreateLogFile(fromAddress) if err != nil { fmt.Printf("⚠️ 获取日志文件失败: %v\n", err) return } timestamp := time.Now().Format("2006-01-02 15:04:05") - content := fmt.Sprintf("%s [withdraw]-[%s] | 金额: %.6f | FromAddress: %s | ToAddress: %s | 交易哈希: %s | 区块高度: %d", + content := fmt.Sprintf("%s [提现]-[%s] | 金额: %.6f | FromAddress: %s | ToAddress: %s | 交易哈希: %s | 区块高度: %d", timestamp, status, amount, fromAddress, toAddress, txHash, blockHeight) if err := lf.write(content); err != nil { @@ -233,7 +233,7 @@ func LogWithdraw(toAddress string, status string, amount float64, fromAddress st } // LogPay 记录支付消息 -func LogPay(status string, fromAddress string, queueId string, transactions map[string]*message.PayData_resp) { +func LogPay(status string, fromAddress string, queueId string, transactions map[string]*message.PayData) { if txLogger == nil { return } @@ -258,6 +258,26 @@ func LogPay(status string, fromAddress string, queueId string, transactions map[ } } +// 记录当前监听的钱包和所有消息 +func LogETHNode(msg string) { + if txLogger == nil { + return + } + + lf, err := txLogger.getOrCreateLogFile("ethnode") + if err != nil { + fmt.Printf("⚠️ 获取日志文件失败: %v\n", err) + return + } + + timestamp := time.Now().Format("2006-01-02 15:04:05") + content := fmt.Sprintf("[ETH-NODE:%s]: %s", timestamp, msg) + + if err := lf.write(content); err != nil { + fmt.Printf("⚠️ 写入日志失败: %v\n", err) + } +} + // Close 关闭所有日志文件 func CloseTransactionLogger() { if txLogger == nil { diff --git a/internal/msg/config.go b/internal/msg/config.go new file mode 100644 index 0000000..1f879cc --- /dev/null +++ b/internal/msg/config.go @@ -0,0 +1,53 @@ +package msg + +import "time" + +// 配置文件结构 +type Config struct { + Net []string `json:"net"` + RmqConfig RmqConfig `json:"rmq_config"` + MsgConfig MsgConfig `json:"msg_config"` + MysqlConfig map[string]MysqlConfig `json:"mysql_config"` // {dbname: MysqlConfig{}, ...} + ETHConfig ETHConfig `json:"eth_config"` +} + +type RmqConfig struct { + SubAddr string `json:"sub_addr"` + Pay Queue `json:"pay"` + Topup Queue `json:"topup"` + Withdraw Queue `json:"withdraw"` + Remove Queue `json:"remove"` + PayResp Queue `json:"pay_resp"` + TopupResp Queue `json:"topup_resp"` + WithdrawResp Queue `json:"withdraw_resp"` + RemoveResp Queue `json:"remove_resp"` +} + +type Queue struct { + Queue string `json:"queue"` + Exchange string `json:"exchange"` + Routing []string `json:"routing"` +} + +type MsgConfig struct { + SqlitePath string `json:"sqlite_path"` // sqlite3数据库文件路径 +} + +type MysqlConfig struct { + User string `json:"user"` + Password string `json:"password"` + Host string `json:"host"` + Port int `json:"port"` + Database string `json:"database"` + MaxOpenConns int `json:"maxOpenConns"` + MaxIdleConns int `json:"maxIdleConns"` + ConnMaxLife time.Duration `json:"connMaxLife"` +} + +type ETHConfig struct { + RpcUrl string `json:"rpc_url"` + WsUrl string `json:"ws_url"` + ConfirmHeight uint64 `json:"confirm_height"` + SqlitePath string `json:"sqlite_path"` // sqlite3数据库文件路径 + SupportTokens []string `json:"support_tokens"` // 支持的代币列表,如["USDT", "DAI"] +} diff --git a/internal/msg/msg.go b/internal/msg/msg.go index cbee20d..3b4ddfa 100644 --- a/internal/msg/msg.go +++ b/internal/msg/msg.go @@ -1,61 +1,6 @@ package msg -import "time" - -// 配置文件结构 -type Config struct { - SQLite3 SQLite3 `json:"sqlite3"` - RMQConfig RMQConfig `json:"rmq_config"` - ETHConfig ETHConfig `json:"eth_config"` - TRONConfig TRONConfig `json:"tron_config"` -} - -type SQLite3 struct { - MsgPath string `json:"msg_path"` -} - -type RMQConfig struct { - SubAddr string `json:"sub_addr"` // 监听地址 - PayConfig QueueConfig `json:"pay"` // 支付 - TopUpConfig QueueConfig `json:"topup"` // 充值 - WithdrawConfig QueueConfig `json:"withdraw"` // 提现 - RemoveConfig QueueConfig `json:"remove"` // 移除监听 - PayRespConfig QueueConfig `json:"pay_resp"` // 支付回复 - TopUpRespConfig QueueConfig `json:"topup_resp"` // 充值回复 - WithdrawRespConfig QueueConfig `json:"withdraw_resp"` // 提现回复 - RemoveRespConfig QueueConfig `json:"remove_resp"` // 移除监听回复 -} - -type QueueConfig struct { - QueueName string `json:"queue"` - ExchangeName string `json:"exchange"` - Routing []string `json:"routing"` -} - -type ETHConfig struct { - RpcURL string `json:"rpcUrl"` // rpc连接地址 - WsURL string `json:"wsUrl"` // websocket连接地址 - ConfirmHeight uint64 `json:"confirmHeight"` // 确认所需区块数 - DbConfig DbConfig `json:"dbConfig"` -} - -type TRONConfig struct { - RpcUrl string `json:"rpcUrl"` - ConfirmHeight uint64 `json:"confirmHeight"` - DbConfig DbConfig `json:"dbConfig"` -} - -// Config 数据库配置 -type DbConfig struct { - User string `json:"user"` - Password string `json:"password"` - Host string `json:"host"` - Port int `json:"port"` - Database string `json:"database"` - MaxOpenConns int `json:"maxOpenConns"` // 最大打开连接数 - MaxIdleConns int `json:"maxIdleCoons"` // 最大空闲连接数 - ConnMaxLife time.Duration `json:"connMaxLife"` // 连接最大存活时间 -} +import "math/big" // =============================== type0 =============================== // 接收的充值消息 @@ -66,31 +11,32 @@ type TopupMsg_req struct { Address string `json:"address"` Timestamp uint64 `json:"timestamp"` Sign string `json:"sign"` - Status int `json:"status,omitempty"` + Status int `json:"status,omitempty"` // 1监听中,2停止监听 } // 返回充值结果消息 type TopupMsg_resp struct { QueueId string `json:"queue_id"` - Address string `json:"address"` - Status int `json:"status"` // 0失败,1成功,2待定,3sign校验失败 - Chain string `json:"chain"` // 链名称 - Symbol string `json:"symbol"` // 币种 - Amount float64 `json:"amount"` - TxHash string `json:"tx_hash"` - BlockHeight uint64 `json:"block_height"` // 区块高度 + Chain string `json:"chain"` // 链名称 + Symbol string `json:"symbol"` // 币种 + FromAddress string `json:"from_address,omitempty"` // 充值来源地址 + Address string `json:"address"` // 充值目标地址 + TxHash string `json:"tx_hash,omitempty"` + Amount float64 `json:"amount,omitempty"` + BlockHeight uint64 `json:"block_height,omitempty"` // 区块高度 + Status int `json:"status"` // 0失败,1成功,2待定,3sign校验失败 } // =============================== type1 =============================== // 接收的提现消息 type WithdrawMsg_req struct { QueueId string `json:"queue_id"` + Chain string `json:"chain"` // 链名称 + Symbol string `json:"symbol"` // 币种 FromAddress string `json:"from_address"` // 我们提供的地址 ToAddress string `json:"to_address"` // 用户要提现到的地址 Amount float64 `json:"amount"` - Chain string `json:"chain"` // 链名称 - Symbol string `json:"symbol"` // 币种 - TxHash string `json:"tx_hash,omitempty"` + Fee float64 `json:"fee"` Timestamp uint64 `json:"timestamp"` Sign string `json:"sign"` Status int `json:"status,omitempty"` @@ -99,80 +45,52 @@ type WithdrawMsg_req struct { // 返回提现结果消息 type WithdrawMsg_resp struct { QueueId string `json:"queue_id"` - Chain string `json:"chain"` // 链名称 - Symbol string `json:"symbol"` // 币种 - Status int `json:"status"` // 0失败,1成功,3sign校验失败 - Amount float64 `json:"amount"` - TxHash string `json:"tx_hash"` + Chain string `json:"chain"` // 链名称 + Symbol string `json:"symbol"` // 币种 FromAddress string `json:"from_address"` // 来源地址 ToAddress string `json:"to_address"` // 目标地址 - BlockHeight uint64 `json:"block_height"` // 区块高度 + TxHash string `json:"tx_hash,omitempty"` + Amount float64 `json:"amount,omitempty"` + Fee float64 `json:"fee,omitempty"` + BlockHeight uint64 `json:"block_height,omitempty"` // 区块高度 + Status int `json:"status"` // 0失败,1成功,3sign校验失败 } // =============================== type2 =============================== type PayMsg_req struct { - QueueId string `json:"queue_id"` - FromAddress string `json:"from_address"` - Chain string `json:"chain"` - Symbol string `json:"symbol"` - TotalAmount float64 `json:"total_amount"` - Timestamp uint64 `json:"timestamp"` - Sign string `json:"sign"` - Trasnactions map[string]*PayData_req `json:"transactions"` // {"to_address": PayData_req{}, ...} -} -type PayData_req struct { - OrderId string `json:"order_id"` - ToAddress string `json:"to_address"` - TxHash string `json:"tx_hash,omitempty"` - Amount float64 `json:"amount"` - Status int `json:"status,omitempty"` + QueueId string `json:"queue_id"` + Chain string `json:"chain"` + Symbol string `json:"symbol"` + FromAddress string `json:"from_address"` + TotalAmount float64 `json:"total_amount"` + TotalFee float64 `json:"total_fee"` + Timestamp uint64 `json:"timestamp"` + Sign string `json:"sign"` + Transactions map[string]PayData `json:"transactions"` // {"to_address": PayData_req{}, ...} + Status int `json:"status,omitempty"` } type PayMsg_resp struct { - QueueId string `json:"queue_id"` - FromAddress string `json:"from_address"` - PayStatus int `json:"pay_status"` // 1至少有一笔转账成功,3sign校验失败,4钱包余额不足 - Transactions map[string]*PayData_resp `json:"transactions"` // {"to_address": PayData_resp{}, ...} -} -type PayData_resp struct { - OrderId string `json:"order_id"` - FromAddress string `json:"from_address"` - ToAddress string `json:"to_address"` - Chain string `json:"chain"` - Symbol string `json:"symbol"` - Amount float64 `json:"amount"` - TxHash string `json:"tx_hash,omitempty"` - BlockHeight uint64 `json:"block_height,omitempty"` - Status int `json:"status"` // 0失败,1成功 + QueueId string `json:"queue_id"` + Chain string `json:"chain"` + Symbol string `json:"symbol"` + FromAddress string `json:"from_address"` + PayStatus int `json:"pay_status"` // 1至少有一笔转账成功,3sign校验失败,4钱包余额不足 + Transactions map[string]PayData `json:"transactions"` // {"to_address": PayData_resp{}, ...} } -// 接收到的支付消息 -// type PayMsg_req struct { -// QueueId string `json:"queue_id"` -// FromAddress string `json:"from_address"` // 我们提供的地址 -// ToAddress string `json:"to_address"` // 卖家地址 -// Amount float64 `json:"amount"` -// Chain string `json:"chain"` // 链名称 -// Symbol string `json:"symbol"` // 币种 -// OrderId string `json:"order_id"` // 订单号 -// Timestamp uint64 `json:"timestamp"` -// Sign string `json:"sign"` -// Transactions map[string]Transaction `json:"tx"` -// } -// 返回支付结果消息 -// type PayMsg_resp struct { -// QueueId string `json:"queue_id"` -// Status int `json:"status"` -// Amount float64 `json:"amount"` -// Chain string `json:"chain"` // 链名称 -// Symbol string `json:"symbol"` // 币种 -// OrderId string `json:"order_id"` // 订单号 -// TxHash string `json:"tx_hash"` -// FromAddress string `json:"from_address"` // 来源地址 -// ToAddress string `json:"to_address"` // 目标地址 -// BlockHeight uint64 `json:"block_height"` // 区块高度 -// } +type PayData struct { + QueueId string `json:"queue_id,omitempty"` + Chain string `json:"chain,omitempty"` + Symbol string `json:"symbol,omitempty"` + TxHash string `json:"tx_hash,omitempty"` + ToAddress string `json:"to_address"` + Amount float64 `json:"amount"` + Fee float64 `json:"fee"` + BlockHeight uint64 `json:"block_height,omitempty"` + Status int `json:"status,omitempty"` // 0失败,1成功, 2待确认 +} // =============================== type3 =============================== // 接收到的删除监听地址消息 @@ -184,6 +102,7 @@ type RemoveListenMsg_req struct { Address string `json:"address"` Timestamp uint64 `json:"timestamp"` Sign string `json:"sign"` + Stauts int `json:"status,omitempty"` } // 返回收到的删除监听地址消息 @@ -196,28 +115,43 @@ type RemoveListenMsg_resp struct { Status int `json:"status"` // 0失败 1成功 } -// ===================================================================== -// 节点通用消息结构 -type Tx_msg struct { - TxType int `json:"tx_type"` // 转账类型:0充值,1提现,2支付 - Tx Tx `json:"tx"` -} -type Tx struct { - From string `json:"from"` // 充值/提现/支付的来源地址 - To string `json:"to"` // 充值/提现/支付的目标地址 - Height uint64 `json:"height"` // 区块高度 - TxHash string `json:"tx_hash"` // 交易哈希 - Symbol string `json:"symbol"` // 币种 - Value float64 `json:"value"` // 数量,单位是币 - Status int `json:"status"` // 交易状态,1成功,0失败, 2待确认 +// =============================== ChainServer -> ListenServer =============================== +// 节点服务响应 +type ChainServer_resp struct { + QueueId string + MsgType int + Chain string + Symbol string + Status int // 遵循constant模块定义 } +// =============================== 其他 =============================== type Transaction struct { - From string `json:"from"` // 充值/提现/支付的来源地址 - To string `json:"to"` // 充值/提现/支付的目标地址 - Height uint64 `json:"height"` // 区块高度 - TxHash string `json:"tx_hash"` // 交易哈希 - Symbol string `json:"symbol"` // 币种 - Value float64 `json:"value"` // 数量,单位是币 - Status int `json:"status"` // 交易状态,1成功,0失败, 2待确认 + QueueId string `json:"queue_id,omitempty"` // 交易对应的QueueId + // 交易对应的消息类型,0充值 1提现 2支付 + // 充值:tx.to_address = msg.Address + // 提现/支付:tx.to_address = msg.ToAddress && tx.from_address = msg.FromAddress && tx.value = msg.Amount + // 同时还要通过msg确认属于哪种类型,即上述3个条件是否都存在于某个msg钟 + TxType int `json:"tx_type,omitempty"` + Chain string `json:"chain"` + Symbol string `json:"symbol"` + From string `json:"from"` + To string `json:"to"` + TxHash string `json:"tx_hash,omitempty"` + Height uint64 `json:"height,omitempty"` + Amount *big.Int `json:"amount"` + GasUsed *big.Int `json:"gas_used,omitempty"` // 转账实际产生的eth消耗 + FreezeFee *big.Int `json:"freeze_fee,omitempty"` // erc20转账冻结的usdt,只有提现才会修改 + Status int `json:"status"` // 交易状态,1成功,0失败, 2待确认 +} + +type TransferResult struct { + QueueId string + MsgType int // 1提现,2支付 + Chain string + Symbol string + FromAddress string + ToAddress string + Amount float64 + Status int } diff --git a/internal/queue/README.md b/internal/queue/README.md deleted file mode 100644 index 5243a6c..0000000 --- a/internal/queue/README.md +++ /dev/null @@ -1,46 +0,0 @@ -# RabbitMQ 服务模块 - -本模块提供 RabbitMQ 消息队列的封装,用于接收业务系统的请求和发送交易确认响应。 - -## 快速使用 - -```go -// 创建 RabbitMQ 服务 -rmqServer, err := rmq.NewRabbitMQServer(config.RMQConfig) -if err != nil { - log.Fatal(err) -} -defer rmqServer.Close() - -// 设置消息回调 -rmqServer.OnTopupMsg = func(msg message.TopupMsg_req) { - // 处理充值请求 -} - -// 启动监听 -rmqServer.Start() - -// 发送响应 -rmqServer.PublishTopupResp(response) -``` - -## 消息队列结构 - -### 请求队列(消费) -- `topup` - 充值请求 -- `withdraw` - 提现请求 -- `pay` - 支付请求 - -### 响应队列(发布) -- `topup_resp` - 充值响应 -- `withdraw_resp` - 提现响应 -- `pay_resp` - 支付响应 - -## 特性 - -✅ **自动重连** - 连接断开时自动重连 -✅ **消息持久化** - 消息不会丢失 -✅ **手动确认** - 处理成功后才确认消息 -✅ **并发安全** - 支持多 goroutine 并发发布 - -更多详情请参考 [主 README](../../README.md)。 diff --git a/internal/queue/rabbitmq.go b/internal/queue/rabbitmq.go index bcbd6a9..62d7c9d 100644 --- a/internal/queue/rabbitmq.go +++ b/internal/queue/rabbitmq.go @@ -14,7 +14,7 @@ import ( // RabbitMQServer RabbitMQ 服务 type RabbitMQServer struct { - config message.RMQConfig + config message.RmqConfig conn *amqp.Connection channel *amqp.Channel mu sync.Mutex @@ -29,7 +29,7 @@ type RabbitMQServer struct { } // NewRabbitMQServer 创建 RabbitMQ 服务 -func NewRabbitMQServer(config message.RMQConfig) (*RabbitMQServer, error) { +func NewRabbitMQServer(config message.RmqConfig) (*RabbitMQServer, error) { // 创建连接 conn, err := amqp.Dial(config.SubAddr) if err != nil { @@ -59,63 +59,63 @@ func NewRabbitMQServer(config message.RMQConfig) (*RabbitMQServer, error) { server.Close() return nil, fmt.Errorf("failed to setup queues: %w", err) } - + log.Println("✅ RabbitMQ队列已启动") return server, nil } // setupQueuesAndExchanges 设置队列和交换机 func (r *RabbitMQServer) setupQueuesAndExchanges() error { - configs := []message.QueueConfig{ - r.config.PayConfig, - r.config.TopUpConfig, - r.config.WithdrawConfig, - r.config.RemoveConfig, - r.config.PayRespConfig, - r.config.TopUpRespConfig, - r.config.WithdrawRespConfig, - r.config.RemoveRespConfig, + configs := []message.Queue{ + r.config.Pay, + r.config.Topup, + r.config.Withdraw, + r.config.Remove, + r.config.PayResp, + r.config.TopupResp, + r.config.WithdrawResp, + r.config.RemoveResp, } for _, cfg := range configs { // 声明交换机 err := r.channel.ExchangeDeclare( - cfg.ExchangeName, // 交换机名称 - "direct", // 类型:direct(与现有交换机类型一致) - true, // durable - false, // auto-deleted - false, // internal - false, // no-wait - nil, // arguments + cfg.Exchange, // 交换机名称 + "direct", // 类型:direct(与现有交换机类型一致) + true, // durable + false, // auto-deleted + false, // internal + false, // no-wait + nil, // arguments ) if err != nil { - return fmt.Errorf("failed to declare exchange %s: %w", cfg.ExchangeName, err) + return fmt.Errorf("failed to declare exchange %s: %w", cfg.Exchange, err) } // 声明队列 _, err = r.channel.QueueDeclare( - cfg.QueueName, // 队列名称 - true, // durable - false, // delete when unused - false, // exclusive - false, // no-wait - nil, // arguments + cfg.Exchange, // 队列名称 + true, // durable + false, // delete when unused + false, // exclusive + false, // no-wait + nil, // arguments ) if err != nil { - return fmt.Errorf("failed to declare queue %s: %w", cfg.QueueName, err) + return fmt.Errorf("failed to declare queue %s: %w", cfg.Exchange, err) } // 绑定队列到交换机 for _, routingKey := range cfg.Routing { err = r.channel.QueueBind( - cfg.QueueName, // 队列名称 - routingKey, // routing key - cfg.ExchangeName, // 交换机名称 - false, // no-wait - nil, // arguments + cfg.Queue, // 队列名称 + routingKey, // routing key + cfg.Exchange, // 交换机名称 + false, // no-wait + nil, // arguments ) if err != nil { return fmt.Errorf("failed to bind queue %s to exchange %s with key %s: %w", - cfg.QueueName, cfg.ExchangeName, routingKey, err) + cfg.Queue, cfg.Exchange, routingKey, err) } } @@ -144,8 +144,7 @@ func (r *RabbitMQServer) Start() error { // consumeTopup 消费充值消息 func (r *RabbitMQServer) consumeTopup() { r.consumeQueue( - r.config.TopUpConfig.QueueName, - "topup", + r.config.Topup.Queue, func(body []byte) error { var msg message.TopupMsg_req if err := json.Unmarshal(body, &msg); err != nil { @@ -165,8 +164,7 @@ func (r *RabbitMQServer) consumeTopup() { // consumeWithdraw 消费提现消息 func (r *RabbitMQServer) consumeWithdraw() { r.consumeQueue( - r.config.WithdrawConfig.QueueName, - "withdraw", + r.config.Withdraw.Queue, func(body []byte) error { var msg message.WithdrawMsg_req if err := json.Unmarshal(body, &msg); err != nil { @@ -186,15 +184,14 @@ func (r *RabbitMQServer) consumeWithdraw() { // consumePay 消费支付消息 func (r *RabbitMQServer) consumePay() { r.consumeQueue( - r.config.PayConfig.QueueName, - "pay", + r.config.Pay.Queue, func(body []byte) error { var msg message.PayMsg_req if err := json.Unmarshal(body, &msg); err != nil { return fmt.Errorf("failed to parse pay message: %w", err) } log.Printf("📥 [RMQ] 收到支付请求: QueueId=%s, From=%s, Chain=%s, Symbol=%s, TxCount=%d", - msg.QueueId, msg.FromAddress, msg.Chain, msg.Symbol, len(msg.Trasnactions)) + msg.QueueId, msg.FromAddress, msg.Chain, msg.Symbol, len(msg.Transactions)) if r.OnPayMsg != nil { r.OnPayMsg(msg) @@ -207,8 +204,7 @@ func (r *RabbitMQServer) consumePay() { // consumeRemove 消费删除充值监听消息 func (r *RabbitMQServer) consumeRemove() { r.consumeQueue( - r.config.RemoveConfig.QueueName, - "remove", + r.config.Remove.Queue, func(body []byte) error { var msg message.RemoveListenMsg_req if err := json.Unmarshal(body, &msg); err != nil { @@ -225,7 +221,7 @@ func (r *RabbitMQServer) consumeRemove() { } // consumeQueue 通用队列消费方法 -func (r *RabbitMQServer) consumeQueue(queueName, msgType string, handler func([]byte) error) { +func (r *RabbitMQServer) consumeQueue(queueName string, handler func([]byte) error) { for { select { case <-r.ctx.Done(): @@ -271,7 +267,7 @@ func (r *RabbitMQServer) consumeQueue(queueName, msgType string, handler func([] // PublishTopupResp 发布充值响应 func (r *RabbitMQServer) PublishTopupResp(resp message.TopupMsg_resp) error { return r.publishMessage( - r.config.TopUpRespConfig, + r.config.TopupResp, resp, fmt.Sprintf("充值响应: Address=%s, Status=%d, TxHash=%s", resp.Address, resp.Status, resp.TxHash), @@ -281,7 +277,7 @@ func (r *RabbitMQServer) PublishTopupResp(resp message.TopupMsg_resp) error { // PublishWithdrawResp 发布提现响应 func (r *RabbitMQServer) PublishWithdrawResp(resp message.WithdrawMsg_resp) error { return r.publishMessage( - r.config.WithdrawRespConfig, + r.config.WithdrawResp, resp, fmt.Sprintf("提现响应: QueueId=%s, Status=%d, TxHash=%s", resp.QueueId, resp.Status, resp.TxHash), @@ -291,7 +287,7 @@ func (r *RabbitMQServer) PublishWithdrawResp(resp message.WithdrawMsg_resp) erro // PublishPayResp 发布支付响应 func (r *RabbitMQServer) PublishPayResp(resp message.PayMsg_resp) error { return r.publishMessage( - r.config.PayRespConfig, + r.config.PayResp, resp, "支付响应", ) @@ -300,14 +296,14 @@ func (r *RabbitMQServer) PublishPayResp(resp message.PayMsg_resp) error { // PublishRemoveResp 发布删除充值监听响应 func (r *RabbitMQServer) PublishRemoveResp(resp message.RemoveListenMsg_resp) error { return r.publishMessage( - r.config.RemoveRespConfig, + r.config.RemoveResp, resp, fmt.Sprintf("删除充值监听响应: Address=%s, Status=%d", resp.Address, resp.Status), ) } // publishMessage 通用消息发布方法 -func (r *RabbitMQServer) publishMessage(config message.QueueConfig, msg interface{}, logMsg string) error { +func (r *RabbitMQServer) publishMessage(config message.Queue, msg interface{}, logMsg string) error { r.mu.Lock() defer r.mu.Unlock() @@ -329,10 +325,10 @@ func (r *RabbitMQServer) publishMessage(config message.QueueConfig, msg interfac err = r.channel.PublishWithContext( ctx, - config.ExchangeName, // 交换机 - routingKey, // routing key - false, // mandatory - false, // immediate + config.Exchange, // 交换机 + routingKey, // routing key + false, // mandatory + false, // immediate amqp.Publishing{ ContentType: "application/json", Body: body, diff --git a/internal/server.go b/internal/server.go index 63e5bb9..b5a115b 100644 --- a/internal/server.go +++ b/internal/server.go @@ -6,9 +6,10 @@ import ( "fmt" "log" "m2pool-payment/internal/blockchain" - eth "m2pool-payment/internal/blockchain/eth" + "m2pool-payment/internal/blockchain/eth" + "m2pool-payment/internal/constant" "m2pool-payment/internal/crypto" - "m2pool-payment/internal/db" + "m2pool-payment/internal/listen" "m2pool-payment/internal/logger" message "m2pool-payment/internal/msg" rmq "m2pool-payment/internal/queue" @@ -16,267 +17,93 @@ import ( "os/signal" "strings" "syscall" - "time" -) - -// const MSG_KEY string = "9f3c7a12" - -// 状态码常量 -const ( - STATUS_FAILED = 0 // 失败 - STATUS_SUCCESS = 1 // 成功 - STATUS_PENDING = 2 // 待确认 - STATUS_VERIFY_FAILED = 3 // 验证失败 ) type ServerCtx struct { - msgKey string + msgKey string // 解密msg-sign的密钥,启动命令参数传入 Config message.Config blockChainServer *blockchain.BlockChainServer rmqServer *rmq.RabbitMQServer - sqlitedb db.SQLite + messageServer *listen.ListenServer } -var s_ctx ServerCtx - -// verifyMessage 验证消息签名 -func verifyMessage(timestamp uint64, sign string) bool { - hash_byte := crypto.Sha256Hash(fmt.Sprintf("%x", timestamp) + s_ctx.msgKey) - hash := hex.EncodeToString(hash_byte) - return hash == sign -} - -func loadConfig(msgKey string) { +func loadConfig() message.Config { file, err := os.ReadFile("config.json") if err != nil { panic(fmt.Sprintf("读取配置文件失败: %v", err)) } - - err = json.Unmarshal(file, &s_ctx.Config) + var result message.Config + err = json.Unmarshal(file, &result) if err != nil { panic(fmt.Sprintf("解析配置文件失败: %v", err)) } - log.Printf("✅ 配置加载成功: RPC=%s, WS=%s", - s_ctx.Config.ETHConfig.RpcURL, s_ctx.Config.ETHConfig.WsURL) - - s_ctx.msgKey = msgKey + return result } -func initBlockChainServer() { - // 初始化节点服务 +func NewServer(msgKey string) *ServerCtx { + cfg := loadConfig() + l := listen.NewListenServer(cfg) + eth_node, err := eth.NewETHNode(cfg, msgKey, l) + if err != nil { + panic(err) + } node_server := blockchain.NewBlockChainServer() - // 初始化ETH节点 - eth_node, err := eth.NewETHNode(s_ctx.Config.ETHConfig, "m2pool") - if err != nil { - log.Fatalf("ETH-Node Start error: %v", err) - } - // 注册ETH节点 node_server.RegisterChain("ETH", eth_node) - // 将所有注册的blockChainServer绑定至server - s_ctx.blockChainServer = node_server - - log.Println("✅ 区块链服务初始化完成") -} - -func loadSQLiteData() { - err1 := loadTopupReqMsg() - if err1 != nil { - log.Fatalf("load topup msg err:%v", err1) - } - err2 := loadWithdrawReqMsg() - if err2 != nil { - log.Fatalf("load withdraw msg err:%v", err2) - } - err3 := loadPayReqMsg() - if err3 != nil { - log.Fatalf("load pay msg err:%v", err3) - } -} - -func loadTopupReqMsg() error { - sql := `SELECT chain, symbol, timestamp, to_addr FROM msg_topup_req;` - rows, err := s_ctx.sqlitedb.DB.Query(sql) + rmq_server, err := rmq.NewRabbitMQServer(cfg.RmqConfig) if err != nil { - return fmt.Errorf("query history topup-msg error: %w", err) + panic(err) } - defer rows.Close() - - var topupReq_msg message.TopupMsg_req - hasData := false - for rows.Next() { - hasData = true - if err := rows.Scan(&topupReq_msg.Chain, &topupReq_msg.Symbol, &topupReq_msg.Timestamp, &topupReq_msg.Address); err != nil { - return err - } - s_ctx.blockChainServer.AddAddress(topupReq_msg.Chain, topupReq_msg) + return &ServerCtx{ + msgKey: msgKey, + Config: cfg, + blockChainServer: node_server, + rmqServer: rmq_server, + messageServer: l, } - - if !hasData { - log.Println("Msg_topup_req`s msg has not data, doesn`t need to load.") - return nil - } - - // 在遍历完所有数据后检查是否发生了错误 - if err := rows.Err(); err != nil { - log.Printf("Error encountered while iterating over rows: %v", err) - } - return nil } -func loadWithdrawReqMsg() error { - sql := `SELECT queueId, chain, symbol, timestamp, from_addr, to_addr, amount FROM msg_withdraw_req;` - rows, err := s_ctx.sqlitedb.DB.Query(sql) - if err != nil { - return fmt.Errorf("query history withdraw-msg error: %w", err) - } - defer rows.Close() - - var withdrawReq_msg message.WithdrawMsg_req - hasData := false - for rows.Next() { - hasData = true - // var chain, symbol, to_addr string - // var timestamp uint64 - if err := rows.Scan(&withdrawReq_msg.QueueId, &withdrawReq_msg.Chain, &withdrawReq_msg.Symbol, &withdrawReq_msg.Timestamp, &withdrawReq_msg.FromAddress, &withdrawReq_msg.ToAddress, &withdrawReq_msg.Amount); err != nil { - return err - } - s_ctx.blockChainServer.AddAddress(withdrawReq_msg.Chain, withdrawReq_msg) - } - - if !hasData { - log.Println("Msg_withdraw_req`s msg has not data, doesn`t need to load.") - return nil - } - - // 在遍历完所有数据后检查是否发生了错误 - if err := rows.Err(); err != nil { - log.Printf("Error encountered while iterating over rows: %v", err) - } - return nil +// verifyMessage 验证消息签名 +func (s *ServerCtx) verifyMessage(timestamp uint64, sign string) bool { + hash_byte := crypto.Sha256Hash(fmt.Sprintf("%x", timestamp) + s.msgKey) + hash := hex.EncodeToString(hash_byte) + return hash == sign } -func loadPayReqMsg() error { - sql := `SELECT queueId, chain, symbol, timestamp, from_addr, total_amount FROM msg_pay_req;` - rows, err := s_ctx.sqlitedb.DB.Query(sql) - if err != nil { - return fmt.Errorf("query history pay-msg error: %w", err) - } - defer rows.Close() - - var payReq_msg message.PayMsg_req - hasData := false - for rows.Next() { - hasData = true - if err := rows.Scan(&payReq_msg.QueueId, &payReq_msg.Chain, &payReq_msg.Symbol, &payReq_msg.Timestamp, &payReq_msg.FromAddress, &payReq_msg.TotalAmount); err != nil { - return err - } - s_ctx.blockChainServer.AddAddress(payReq_msg.Chain, payReq_msg) - } - - if !hasData { - log.Println("Msg_pay_req`s msg has not data, doesn`t need to load.") - return nil - } - - // 在遍历完所有数据后检查是否发生了错误 - if err := rows.Err(); err != nil { - log.Printf("Error encountered while iterating over rows: %v", err) - } - return nil -} - -func initRmqServer() { - // 初始化rmq服务 - rmq_server, err := rmq.NewRabbitMQServer(s_ctx.Config.RMQConfig) - if err != nil { - log.Fatalf("RabbitMQ Server Start error: %v", err) - } - // 将rmq服务绑定至server - s_ctx.rmqServer = rmq_server - - log.Printf("✅ RabbitMQ服务初始化完成: %s", s_ctx.Config.RMQConfig.SubAddr) -} - -func initSQLite(sqlite3_file string) { - // 初始化sqlite3数据库 - sqlite3, err := db.NewSQLite(sqlite3_file) - if err != nil { - log.Fatalf("connect sqlite3 error:%v", err) - return - } - sqlByte, err := os.ReadFile("../public/SQLite3.sql") - if err != nil { - log.Fatalf("open sql file error: %s", "../public/SQLite3.sql") - } - sqlite3.Exec_(string(sqlByte)) - s_ctx.sqlitedb = *sqlite3 -} - -func handleTopupMsg() { - s_ctx.rmqServer.OnTopupMsg = func(msg message.TopupMsg_req) { +func (s *ServerCtx) handleTopupMsg() { + s.rmqServer.OnTopupMsg = func(msg message.TopupMsg_req) { msg.Address = strings.ToLower(msg.Address) - // 验证签名 - if !verifyMessage(msg.Timestamp, msg.Sign) { - err := s_ctx.rmqServer.PublishTopupResp(message.TopupMsg_resp{ + if !s.verifyMessage(msg.Timestamp, msg.Sign) { + err := s.rmqServer.PublishTopupResp(message.TopupMsg_resp{ + QueueId: msg.QueueId, Address: msg.Address, - Status: STATUS_VERIFY_FAILED, + Status: constant.STATUS_VERIFY_FAILED, Chain: msg.Chain, Symbol: msg.Symbol, - Amount: 0, - TxHash: "", }) if err != nil { log.Printf("❌ 发布充值失败响应失败: %v", err) } return } - - // 添加监听地址 - // go func() { - err := s_ctx.blockChainServer.AddAddress(msg.Chain, msg) - if err != nil { - log.Printf("❌ 添加监听地址失败: %v", err) - // 发送失败响应 - err = s_ctx.rmqServer.PublishTopupResp(message.TopupMsg_resp{ - Address: msg.Address, - Status: STATUS_FAILED, - Chain: msg.Chain, - Symbol: msg.Symbol, - Amount: 0, - TxHash: "", - }) - if err != nil { - log.Printf("❌ 发布充值失败响应失败: %v", err) - } - return - } - // }() - // 将新增数据写入sqlite - insert_sql := `INSERT OR REPLACE INTO msg_topup_req (chain, symbol, timestamp, to_addr) VALUES (?, ?, ?, ?)` - data := []any{msg.Chain, msg.Symbol, msg.Timestamp, msg.Address} - err = s_ctx.sqlitedb.Insert(insert_sql, data) - if err != nil { - log.Printf("❌ 插入 msg_req 失败: %v, data: %+v", err, data) - } + s.messageServer.ChFromRmqServer <- msg } } -func handleWithdrawMsg() { - s_ctx.rmqServer.OnWithdrawMsg = func(msg message.WithdrawMsg_req) { +func (s *ServerCtx) handleWithdrawMsg() { + s.rmqServer.OnWithdrawMsg = func(msg message.WithdrawMsg_req) { msg.FromAddress = strings.ToLower(msg.FromAddress) msg.ToAddress = strings.ToLower(msg.ToAddress) - // 验证签名 - if !verifyMessage(msg.Timestamp, msg.Sign) { - err := s_ctx.rmqServer.PublishWithdrawResp(message.WithdrawMsg_resp{ - QueueId: msg.QueueId, - Status: STATUS_VERIFY_FAILED, - Chain: msg.Chain, - Symbol: msg.Symbol, - Amount: 0, - TxHash: "", + if !s.verifyMessage(msg.Timestamp, msg.Sign) { + err := s.rmqServer.PublishWithdrawResp(message.WithdrawMsg_resp{ + QueueId: msg.QueueId, + Chain: msg.Chain, + Symbol: msg.Symbol, + Status: constant.STATUS_VERIFY_FAILED, + FromAddress: msg.FromAddress, + ToAddress: msg.ToAddress, }) if err != nil { log.Printf("❌ 发布提现失败响应失败: %v", err) @@ -284,58 +111,22 @@ func handleWithdrawMsg() { return } - // 执行转账 - err := s_ctx.blockChainServer.Transfer(msg.Chain, msg) - if err != nil { - log.Printf("❌ 提现转账失败: %v", err) - // 发送失败响应 - s_ctx.rmqServer.PublishWithdrawResp(message.WithdrawMsg_resp{ - QueueId: msg.QueueId, - Status: STATUS_FAILED, - Amount: msg.Amount, - Chain: msg.Chain, - Symbol: msg.Symbol, - TxHash: "", - }) - return // 转账失败时直接返回,不进入链上确认流程 - } - // go func() { - err = s_ctx.blockChainServer.AddAddress(msg.Chain, msg) - if err != nil { - log.Printf("❌ 添加监听地址失败: %v", err) - // 发送失败响应 - s_ctx.rmqServer.PublishWithdrawResp(message.WithdrawMsg_resp{ - QueueId: msg.QueueId, - Status: STATUS_FAILED, - Amount: msg.Amount, - Chain: msg.Chain, - Symbol: msg.Symbol, - TxHash: "", - }) - return - } - // }() - // 将新增数据写入sqlite - insert_sql := `INSERT OR REPLACE INTO msg_withdraw_req (queueId, chain, symbol, timestamp, from_addr, to_addr, amount) VALUES (?, ?, ?, ?, ?, ?, ?)` - data := []any{msg.QueueId, msg.Chain, msg.Symbol, msg.Timestamp, msg.FromAddress, msg.ToAddress, msg.Amount} - err = s_ctx.sqlitedb.Insert(insert_sql, data) - if err != nil { - log.Printf("❌ 插入 withdraw_req 失败: %v, data: %+v", err, data) - } + s.messageServer.ChFromRmqServer <- msg + } + } -func handlePayMsg() { - s_ctx.rmqServer.OnPayMsg = func(msg message.PayMsg_req) { +func (s *ServerCtx) handlePayMsg() { + s.rmqServer.OnPayMsg = func(msg message.PayMsg_req) { msg.FromAddress = strings.ToLower(msg.FromAddress) - // msg.ToAddress = strings.ToLower(msg.ToAddress) - // 验证签名 - if !verifyMessage(msg.Timestamp, msg.Sign) { - err := s_ctx.rmqServer.PublishPayResp(message.PayMsg_resp{ - QueueId: msg.QueueId, - FromAddress: msg.FromAddress, - PayStatus: STATUS_VERIFY_FAILED, + if !s.verifyMessage(msg.Timestamp, msg.Sign) { + err := s.rmqServer.PublishPayResp(message.PayMsg_resp{ + QueueId: msg.QueueId, + FromAddress: msg.FromAddress, + PayStatus: constant.STATUS_VERIFY_FAILED, + Transactions: msg.Transactions, }) if err != nil { log.Printf("❌ 发布支付失败响应失败: %v", err) @@ -343,168 +134,68 @@ func handlePayMsg() { return } - // 执行转账 - err := s_ctx.blockChainServer.Transfer(msg.Chain, msg) - if err != nil { - log.Printf("❌ 支付转账失败: %v", err) - // 发送失败响应 - s_ctx.rmqServer.PublishPayResp(message.PayMsg_resp{ - QueueId: msg.QueueId, - FromAddress: msg.FromAddress, - PayStatus: STATUS_FAILED, - }) - return // 转账失败时直接返回,不进入链上确认流程 - } - // go func() { - err = s_ctx.blockChainServer.AddAddress(msg.Chain, msg) - if err != nil { - log.Printf("❌ 添加监听地址失败: %v", err) - // 发送失败响应 - s_ctx.rmqServer.PublishPayResp(message.PayMsg_resp{ - QueueId: msg.QueueId, - FromAddress: msg.FromAddress, - PayStatus: STATUS_FAILED, + s.messageServer.ChFromRmqServer <- msg + } +} + +func (s *ServerCtx) handleRemoveMsg() { + s.rmqServer.OnRemoveMsg = func(msg message.RemoveListenMsg_req) { + msg.Address = strings.ToLower(msg.Address) + // 验证签名 + if !s.verifyMessage(msg.Timestamp, msg.Sign) { + err := s.rmqServer.PublishRemoveResp(message.RemoveListenMsg_resp{ + QueueId: msg.QueueId, + MsgType: msg.MsgType, + Chain: msg.Chain, + Symbol: msg.Symbol, + Address: msg.Address, + Status: constant.STATUS_VERIFY_FAILED, }) + if err != nil { + log.Printf("❌ 发布移除监听失败响应失败: %v", err) + } return } - // }() - // 将新增数据写入sqlite - insert_sql := `INSERT OR REPLACE INTO msg_pay_req (queueId, chain, symbol, timestamp, from_addr, total_amount) VALUES (?, ?, ?, ?, ?, ?)` - data := []any{msg.QueueId, msg.Chain, msg.Symbol, msg.Timestamp, msg.FromAddress, msg.TotalAmount} - err = s_ctx.sqlitedb.Insert(insert_sql, data) - if err != nil { - log.Printf("❌ 插入 pay_req 失败: %v, data: %+v", err, data) - } + + s.messageServer.ChFromRmqServer <- msg } } -func initRmqListen() { - // ================== 设置 RabbitMQ 消息处理回调 ================== - // 先设置所有回调(同步执行,避免竞态) - handleTopupMsg() - handleWithdrawMsg() - handlePayMsg() - - // 回调设置完成后,再启动 RabbitMQ 监听 - if err := s_ctx.rmqServer.Start(); err != nil { - log.Fatalf("启动 RabbitMQ 监听失败: %v", err) - } - log.Println("✅ RabbitMQ 监听启动完成") -} - -func handleChainEvent(chainEventCh chan any) { - for event := range chainEventCh { - // 添加 panic 恢复,防止单个消息处理错误导致整个 goroutine 退出 - func() { - defer func() { - if r := recover(); r != nil { - log.Printf("❌ 处理链上事件 panic: %v, event: %+v", r, event) - } - }() - - // 根据消息类型发送不同的响应 - switch msg := event.(type) { - case message.TopupMsg_resp: - // 充值确认 - if msg.Status == STATUS_PENDING { - log.Printf("📨 [链上] 充值待确认: Address=%s, Amount=%.2f, TxHash=%s", - msg.Address, msg.Amount, msg.TxHash) - // 记录交易日志:待确认 - logger.LogTopup(msg.Address, "待确认", msg.Amount, msg.TxHash, msg.BlockHeight) - } else { - log.Printf("✅ [链上] 充值确认: Address=%s, Amount=%.2f, TxHash=%s, Status=%d", - msg.Address, msg.Amount, msg.TxHash, msg.Status) - // 记录交易日志:已确认 - logger.LogTopup(msg.Address, "确认", msg.Amount, msg.TxHash, msg.BlockHeight) - } - err := s_ctx.rmqServer.PublishTopupResp(msg) - if err != nil { - log.Printf("❌ 发送充值响应失败: %v", err) - return - } - - go func() { - // 插入响应数据 - sql := `INSERT INTO msg_topup_resp (chain, symbol, timestamp, to_addr, amount, height, txHash, status) VALUES (?, ?, ?, ?, ?, ?, ?, ?)` - data := []any{msg.Chain, msg.Symbol, time.Now().Unix(), msg.Address, msg.Amount, msg.BlockHeight, msg.TxHash, msg.Status} - err := s_ctx.sqlitedb.Insert(sql, data) - if err != nil { - log.Printf("❌ 插入 topup_resp 失败: %v", err) - return - } - }() - case message.WithdrawMsg_resp: - // 提现确认 - log.Printf("✅ [链上] 提现确认: QueueId=%s, Amount=%.2f, TxHash=%s, Status=%d", - msg.QueueId, msg.Amount, msg.TxHash, msg.Status) - // 记录交易日志 - logger.LogWithdraw(msg.ToAddress, "确认", msg.Amount, msg.FromAddress, msg.TxHash, msg.BlockHeight) - err := s_ctx.rmqServer.PublishWithdrawResp(msg) - if err != nil { - log.Printf("❌ 发送提现响应失败: %v", err) - return - } - - go func() { - // 插入响应数据 - sql := `INSERT INTO msg_withdraw_resp (queueId, chain, symbol, timestamp, from_addr, to_addr, amount, height, txHash, status) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)` - data := []any{msg.QueueId, msg.Chain, msg.Symbol, time.Now().Unix(), msg.FromAddress, msg.ToAddress, msg.Amount, msg.BlockHeight, msg.TxHash, msg.Status} - err := s_ctx.sqlitedb.Insert(sql, data) - if err != nil { - log.Printf("❌ 插入 withdraw_resp 失败: %v", err) - return - } - - // 删除对应数据 - del_sql := `DELETE FROM msg_withdraw_req WHERE queueId = ?;` - count, err := s_ctx.sqlitedb.Delete(del_sql, msg.QueueId) - if err != nil { - log.Printf("❌ 清理 withdraw_req 失败: %v, queueId=%s", err, msg.QueueId) - } else if count == 0 { - log.Printf("⚠️ 未找到要删除的 withdraw_req 记录: queueId=%s", msg.QueueId) - } else { - log.Printf("✅ 清理 withdraw_req 成功: 删除了 %d 条记录, queueId=%s", count, msg.QueueId) - } - }() - - case message.PayMsg_resp: - // 支付确认 - log.Printf("✅ [链上] 支付确认: QueueId=%s, FromAddress=%s, Status=%d", - msg.QueueId, msg.FromAddress, msg.PayStatus) - // 记录交易日志 - logger.LogPay("全部交易确认", msg.FromAddress, msg.QueueId, msg.Transactions) - err := s_ctx.rmqServer.PublishPayResp(msg) - if err != nil { - log.Printf("❌ 发送支付响应失败: %v", err) - return - } - - // go func() { - // // 插入响应数据 - // sql := `INSERT INTO msg_pay_resp (queueId, chain, symbol, timestamp, from_addr, to_addr, amount, height, txHash, status, orderId) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)` - // data := []any{msg.QueueId, msg.Chain, msg.Symbol, time.Now().Unix(), msg.FromAddress, msg.ToAddress, msg.Amount, msg.BlockHeight, msg.TxHash, msg.Status, msg.OrderId} - // err := s_ctx.sqlitedb.Insert(sql, data) - // if err != nil { - // log.Printf("❌ 插入 pay_resp 失败: %v", err) - // return - // } - - // // 删除对应数据 - // del_sql := `DELETE FROM msg_pay_req WHERE queueId = ?;` - // count, err := s_ctx.sqlitedb.Delete(del_sql, msg.QueueId) - // if err != nil { - // log.Printf("❌ 清理 pay_req 失败: %v, queueId=%s", err, msg.QueueId) - // } else if count == 0 { - // log.Printf("⚠️ 未找到要删除的 pay_req 记录: queueId=%s", msg.QueueId) - // } else { - // log.Printf("✅ 清理 pay_req 成功: 删除了 %d 条记录, queueId=%s", count, msg.QueueId) - // } - // }() - - default: - log.Printf("⚠️ 未知消息类型: %T", event) +func (s *ServerCtx) handleRespMsg() { + for msg := range s.messageServer.ChToRmqServer { + switch v := msg.(type) { + case message.TopupMsg_resp: + log.Printf("📨[充值响应]:QueueID=%s, Address=%s, Chain=%s, Symbol=%s, TxHash=%s, Status=%d, Amount=%f", v.QueueId, v.Address, v.Chain, v.Symbol, v.TxHash, v.Status, v.Amount) + err := s.rmqServer.PublishTopupResp(v) + if err != nil { + log.Printf("❌ 发送充值响应失败: %v", err) + return } - }() + case message.WithdrawMsg_resp: + log.Printf("📨[提现响应]:QueueID=%s, Chain=%s, Symbol=%s, FromAddress=%s, ToAddress=%s, TxHash=%s, Status=%d, Amount=%f", v.QueueId, v.Chain, v.Symbol, v.FromAddress, v.ToAddress, v.TxHash, v.Status, v.Amount) + err := s.rmqServer.PublishWithdrawResp(v) + if err != nil { + log.Printf("❌ 发送提现响应失败: %v", err) + return + } + case message.PayMsg_resp: + log.Printf("📨[提现响应]:QueueID=%s, Chain=%s, Symbol=%s, FromAddress=%s, Status=%d", v.QueueId, v.Chain, v.Symbol, v.FromAddress, v.PayStatus) + err := s.rmqServer.PublishPayResp(v) + if err != nil { + log.Printf("❌ 发送支付响应失败: %v", err) + return + } + case message.RemoveListenMsg_resp: + log.Printf("📨[充值响应]:QueueID=%s, Address=%s, Chain=%s, Symbol=%s,Status=%d", v.QueueId, v.Address, v.Chain, v.Symbol, v.Status) + err := s.rmqServer.PublishRemoveResp(v) + if err != nil { + log.Printf("❌ 发送移除监听响应失败: %v", err) + return + } + default: + log.Printf("❌ 错误响应结构: %v", v) + return + } } } @@ -513,35 +204,37 @@ func Start(msgKey string) { log.Println("🚀 M2Pool Payment System Starting...") log.Println("========================================") - // 加载配置 - loadConfig(msgKey) + server := NewServer(msgKey) - // 初始化交易日志系统 - if err := logger.InitTransactionLogger("logs"); err != nil { - log.Fatalf("❌ 初始化交易日志系统失败: %v", err) + // 启动消息处理 + go server.handleTopupMsg() + go server.handleWithdrawMsg() + go server.handlePayMsg() + go server.handleRemoveMsg() + go server.handleRespMsg() + + // 启动 RabbitMQ 服务 + if err := server.rmqServer.Start(); err != nil { + log.Fatalf("❌ 启动 RabbitMQ 服务失败: %v", err) } - log.Println("✅ 交易日志系统初始化完成") + log.Println("✅ RabbitMQ 服务启动成功") - // ================== 初始化区块链节点 ================== - initBlockChainServer() + // 启动消息服务器监听 + go server.messageServer.RmqMsgIn() + go server.messageServer.NetMsgIn() + log.Println("✅ 消息服务器监听启动成功") - // ================== 初始化SQLite3 ================== - initSQLite(s_ctx.Config.SQLite3.MsgPath) - // 读取历史信息 - loadSQLiteData() + // 启动区块链监听 + if err := server.blockChainServer.Listen("ETH", nil); err != nil { + log.Fatalf("❌ 启动区块链监听失败: %v", err) + } + log.Println("✅ 区块链监听启动成功") - // ================== 初始化 RabbitMQ 服务 ================== - initRmqServer() - - // ================== 启动链上事件监听通道 ================== - chainEventCh := make(chan any, 1000) // 增加缓冲区,避免高并发丢消息 - go s_ctx.blockChainServer.Listen("ETH", chainEventCh) - - // ================== 启动 RabbitMQ 监听 ================== - initRmqListen() - - // ================== 处理链上确认事件 ================== - go handleChainEvent(chainEventCh) + // 启动区块链消息监听 + if err := server.blockChainServer.ListenMsg("ETH"); err != nil { + log.Fatalf("❌ 启动区块链消息监听失败: %v", err) + } + log.Println("✅ 区块链消息监听启动成功") log.Println("========================================") log.Println("🎉 所有服务启动完成!") @@ -556,8 +249,8 @@ func Start(msgKey string) { log.Println("🛑 收到退出信号,正在关闭服务...") log.Println("========================================") - s_ctx.blockChainServer.Stop("ETH") - s_ctx.rmqServer.Close() + server.blockChainServer.Stop("ETH") + server.rmqServer.Close() logger.CloseTransactionLogger() log.Println("👋 服务已全部关闭") diff --git a/internal/utils/utils.go b/internal/utils/utils.go index 8dd087f..23b0013 100644 --- a/internal/utils/utils.go +++ b/internal/utils/utils.go @@ -56,3 +56,46 @@ func Slice_delete(arr []any, index int) []any { } return arr } + +// 函数:检查一个切片是否包含某个字符串 +func Contains(slice []string, str string) bool { + for _, s := range slice { + if s == str { + return true + } + } + return false +} + +func Float64ToBigInt(symbol string, amount float64) *big.Int { + var scale float64 + switch symbol { + case "ETH": + scale = math.Pow10(ETHDecimals) + case "USDT": + scale = math.Pow10(USDTDecimals) + default: + log.Printf("don`t support symbol: %s", symbol) + return nil + } + bigAmount := new(big.Int) + bigAmount.SetInt64(int64(amount * scale)) + return bigAmount +} + +func BigIntToFloat64(symbol string, amount *big.Int) float64 { + var scale *big.Float + f := new(big.Float).SetInt(amount) + switch symbol { + case "ETH": + scale = new(big.Float).SetFloat64(1e18) + case "USDT": + scale = new(big.Float).SetFloat64(1e6) + default: + log.Printf("don`t support symbol: %s", symbol) + return 0 + } + f.Quo(f, scale) + result, _ := f.Float64() + return result +} diff --git a/public/SQLite3.sql b/public/SQLite3.sql deleted file mode 100644 index a90fe79..0000000 --- a/public/SQLite3.sql +++ /dev/null @@ -1,111 +0,0 @@ -CREATE TABLE IF NOT EXISTS msg_topup_req ( - chain TEXT, - symbol TEXT, - timestamp INTEGER, - to_addr TEXT, - PRIMARY KEY(to_addr) -); - -CREATE TABLE IF NOT EXISTS msg_withdraw_req ( - queueId TEXT, - chain TEXT, - symbol TEXT, - timestamp INTEGER, - from_addr TEXT, - to_addr TEXT, - amount NUMERIC, - PRIMARY KEY(queueId) -); - --- CREATE TABLE IF NOT EXISTS msg_pay_req ( --- queueId TEXT, --- chain TEXT, --- symbol TEXT, --- timestamp INTEGER, --- from_addr TEXT, --- to_addr TEXT, --- amount NUMERIC, --- orderId TEXT, --- PRIMARY KEY(queueId) --- ); - -CREATE TABLE msg_pay_req ( - queueId TEXT PRIMARY KEY, - from_addr TEXT NOT NULL, - chain TEXT NOT NULL, - symbol TEXT NOT NULL, - total_amount REAL NOT NULL, - timestamp INTEGER NOT NULL, - sign TEXT NOT NULL -); - -CREATE TABLE msg_pay_req_transactions ( - queueId TEXT NOT NULL, - to_addr TEXT NOT NULL, - order_id TEXT NOT NULL, - tx_hash TEXT, - amount REAL NOT NULL, - FOREIGN KEY (queueId) REFERENCES msg_pay_req(queueId) -); - -CREATE TABLE IF NOT EXISTS msg_topup_resp ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - chain TEXT, - symbol TEXT, - timestamp INTEGER, - to_addr TEXT, - amount NUMERIC, - height INTEGER, - TxHash TEXT, - status INTEGER -); - -CREATE TABLE IF NOT EXISTS msg_withdraw_resp ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - queueId TEXT, - chain TEXT, - symbol TEXT, - timestamp INTEGER, - from_addr TEXT, - to_addr TEXT, - amount NUMERIC, - height INTEGER, - txHash TEXT, - status INTEGER -); - --- CREATE TABLE IF NOT EXISTS msg_pay_resp ( --- id INTEGER PRIMARY KEY AUTOINCREMENT, --- queueId TEXT, --- chain TEXT, --- symbol TEXT, --- timestamp INTEGER, --- from_addr TEXT, --- to_addr TEXT, --- amount NUMERIC, --- height INTEGER, --- txHash TEXT, --- orderId TEXT, --- status INTEGER --- ); - -CREATE TABLE msg_pay_resp ( - queueId TEXT PRIMARY KEY, - from_addr TEXT NOT NULL, - pay_status INTEGER NOT NULL, -- 1: At least one success, 3: Sign verification failed, 4: Insufficient balance - transactions JSON -- 存储交易的JSON格式 -); - -CREATE TABLE msg_pay_resp_transactions ( - queueId TEXT NOT NULL, - order_id TEXT NOT NULL, - from_addr TEXT NOT NULL, - to_addr TEXT NOT NULL, - chain TEXT NOT NULL, - symbol TEXT NOT NULL, - amount REAL NOT NULL, - tx_hash TEXT, - height INTEGER, - status INTEGER NOT NULL, -- 0: Failed, 1: Success - FOREIGN KEY (queueId) REFERENCES msg_pay_resp(queueId) -); \ No newline at end of file diff --git a/public/eth.sql b/public/eth.sql new file mode 100644 index 0000000..3e0efa4 --- /dev/null +++ b/public/eth.sql @@ -0,0 +1,45 @@ +CREATE TABLE IF NOT EXISTS ETH_wallets ( + address TEXT PRIMARY KEY NOT NULL, + queue_id TEXT NOT NULL, + timestamp INTEGER NOT NULL, + sign TEXT NOT NULL, + status INTEGER DEFAULT 0, -- 0未在监听 1正在监听 +); + +CREATE TABLE IF NOT EXISTS ETH_balances ( + address TEXT NOT NULL, + symbol TEXT DEFAULT "ETH", + used_gas TEXT DEFAULT "0", + balance TEXT DEFAULT "0", + success_tx_hash TEXT DEFAULT NULL, --使用,隔开 + failed_tx_hash TEXT DEFAULT NULL, --使用,隔开 + PRIMARY KEY (address), -- 钱包地址唯一 + FOREIGN KEY (address) REFERENCES eth_wallets (address) ON DELETE CASCADE +); + + +CREATE TABLE IF NOT EXISTS USDT_balances ( + address TEXT NOT NULL, + symbol TEXT DEFAULT "USDT", + freeze_num TEXT DEFAULT "0", + balance TEXT DEFAULT "0", + success_tx_hash TEXT DEFAULT NULL, --使用,隔开 + failed_tx_hash TEXT DEFAULT NULL, --使用,隔开 + PRIMARY KEY (address), -- 钱包地址唯一 + FOREIGN KEY (address) REFERENCES eth_wallets (address) ON DELETE CASCADE +); + +CREATE TABLE IF NOT EXISTS eth_unconfirmed_tx ( + queue_id TEXT NOT NULL, -- 关联的msg queue_id + tx_type INTEGER NOT NULL, --0充值,1提现,2支付 + chain TEXT DEFALUT "ETH", + symbol TEXT, + from_addr TEXT, + to_addr TEXT, + tx_hash TEXT, + height INTEGER, + amount TEXT, + status INTEGER DEFAULT 2, --0充值失败,1充值成功,2充值待确认 + PRIMARY KEY (tx_hash), + FOREIGN KEY (queue_id) REFERENCES eth_wallets (queue_id) ON DELETE CASCADE +); \ No newline at end of file diff --git a/public/eth_mysql.sql b/public/eth_mysql.sql new file mode 100644 index 0000000..a64a2ee --- /dev/null +++ b/public/eth_mysql.sql @@ -0,0 +1,43 @@ +CREATE TABLE IF NOT EXISTS ETH_wallets ( + address VARCHAR(255) PRIMARY KEY NOT NULL, + queue_id VARCHAR(255) NOT NULL, + timestamp BIGINT NOT NULL, + sign VARCHAR(255) NOT NULL, + status TINYINT DEFAULT 0 -- 0未在监听 1正在监听 +); + +CREATE TABLE IF NOT EXISTS ETH_balances ( + address VARCHAR(255) NOT NULL, + symbol VARCHAR(32) DEFAULT "ETH", + used_gas DECIMAL(40,16) DEFAULT 0, + balance DECIMAL(40,16) DEFAULT 0, + success_tx_hash TEXT DEFAULT NULL, -- 使用,隔开 + failed_tx_hash TEXT DEFAULT NULL, -- 使用,隔开 + PRIMARY KEY (address), -- 钱包地址唯一 + FOREIGN KEY (address) REFERENCES ETH_wallets (address) ON DELETE CASCADE +); + +CREATE TABLE IF NOT EXISTS USDT_balances ( + address VARCHAR(255) NOT NULL, + symbol VARCHAR(32) DEFAULT "USDT", + freeze_num DECIMAL(40,16) DEFAULT 0, + balance DECIMAL(40,16) DEFAULT 0, + success_tx_hash TEXT DEFAULT NULL, -- 使用,隔开 + failed_tx_hash TEXT DEFAULT NULL, -- 使用,隔开 + PRIMARY KEY (address), -- 钱包地址唯一 + FOREIGN KEY (address) REFERENCES ETH_wallets (address) ON DELETE CASCADE +); + +CREATE TABLE IF NOT EXISTS ETH_unconfirmed_tx ( + queue_id VARCHAR(255) NOT NULL, -- 关联的msg queue_id + tx_type TINYINT NOT NULL, -- 0充值,1提现,2支付 + chain VARCHAR(32) DEFAULT "ETH", + symbol VARCHAR(32), + from_addr VARCHAR(255), + to_addr VARCHAR(255), + tx_hash VARCHAR(255), + height BIGINT, + amount DECIMAL(40,16), + status TINYINT DEFAULT 2, -- 0充值失败,1充值成功,2充值待确认 + FOREIGN KEY (queue_id) REFERENCES ETH_wallets (queue_id) ON DELETE CASCADE +); diff --git a/public/infura b/public/infura new file mode 100644 index 0000000..05ffbb5 --- /dev/null +++ b/public/infura @@ -0,0 +1,3 @@ +API-KEY=431dbd08d89c406fb9f2b5ee812f5faf +HTTPS=https://mainnet.infura.io/v3/431dbd08d89c406fb9f2b5ee812f5faf +mnemonic=income miracle spin apple live cigar trim social age push eye chase \ No newline at end of file diff --git a/public/msg.sql b/public/msg.sql new file mode 100644 index 0000000..c52e3b7 --- /dev/null +++ b/public/msg.sql @@ -0,0 +1,108 @@ +CREATE TABLE IF NOT EXISTS topup_req_msg ( + queue_id TEXT NOT NULL, + chain TEXT NOT NULL, + symbol TEXT NOT NULL, + address TEXT NOT NULL, + timestamp INTEGER, + sign TEXT, + status INTEGER DEFAULT 1, + PRIMARY KEY (queue_id) +); + +CREATE TABLE IF NOT EXISTS topup_resp_msg ( + queue_id TEXT NOT NULL, + chain TEXT NOT NULL, + symbol TEXT NOT NULL, + from_addr TEXT NOT NULL, + to_addr TEXT NOT NULL, + amount TEXT NOT NULL, -- 改为 TEXT 类型 + tx_hash TEXT DEFAULT NULL, + height INTEGER DEFAULT NULL, + status INTEGER DEFAULT 5, + FOREIGN KEY (queue_id) REFERENCES topup_req_msg(queue_id) +); + +CREATE TABLE IF NOT EXISTS withdraw_req_msg ( + queue_id TEXT NOT NULL, + chain TEXT NOT NULL, + symbol TEXT NOT NULL, + from_addr TEXT NOT NULL, + to_addr TEXT NOT NULL, + amount TEXT NOT NULL, -- 改为 TEXT 类型 + fee TEXT NOT NULL, -- 改为 TEXT 类型 + timestamp INTEGER, + sign TEXT, + status INTEGER DEFAULT 5, + PRIMARY KEY (queue_id) +); + +CREATE TABLE IF NOT EXISTS withdraw_resp_msg ( + queue_id TEXT NOT NULL, + chain TEXT NOT NULL, + symbol TEXT NOT NULL, + from_addr TEXT NOT NULL, + to_addr TEXT NOT NULL, + tx_hash TEXT DEFAULT NULL, + amount TEXT DEFAULT NULL, -- 改为 TEXT 类型 + fee TEXT DEFAULT NULL, -- 改为 TEXT 类型 + height INTEGER DEFAULT NULL, + status INTEGER DEFAULT 5, + FOREIGN KEY (queue_id) REFERENCES withdraw_req_msg(queue_id) +); + +CREATE TABLE IF NOT EXISTS pay_req_msg ( + queue_id TEXT NOT NULL, + chain TEXT NOT NULL, + symbol TEXT NOT NULL, + from_addr TEXT NOT NULL, + to_addr TEXT NOT NULL, + total_amount TEXT NOT NULL, -- 改为 TEXT 类型 + total_fee TEXT NOT NULL, -- 改为 TEXT 类型 + timestamp INTEGER, + sign TEXT, + status INTEGER DEFAULT 5, + PRIMARY KEY (queue_id) +); + +CREATE TABLE IF NOT EXISTS pay_resp_msg ( + queue_id TEXT NOT NULL, + chain TEXT NOT NULL, + symbol TEXT NOT NULL, + from_addr TEXT NOT NULL, + pay_status INTEGER DEFAULT 5, + FOREIGN KEY (queue_id) REFERENCES pay_req_msg(queue_id) +); + +-- pay_msg的交易 +CREATE TABLE IF NOT EXISTS pay_msg_tx ( + queue_id TEXT NOT NULL, + tx_hash TEXT DEFAULT NULL, + to_addr TEXT NOT NULL, + amount TEXT DEFAULT NULL, -- 改为 TEXT 类型 + fee TEXT, -- 改为 TEXT 类型 + height INTEGER DEFAULT 0, + status INTEGER DEFAULT 5, + FOREIGN KEY (queue_id) REFERENCES pay_req_msg(queue_id) +); + +CREATE TABLE IF NOT EXISTS remove_req_msg( + queue_id TEXT NOT NULL, + msg_type INTEGER NOT NULL, + chain TEXT NOT NULL, + symbol TEXT NOT NULL, + address TEXT NOT NULL, + timestamp INTEGER, + sign TEXT, + status INTEGER DEFAULT 2, + PRIMARY KEY (queue_id) +); + +CREATE TABLE IF NOT EXISTS remove_resp_msg( + queue_id TEXT NOT NULL, + msg_type INTEGER NOT NULL, + chain TEXT NOT NULL, + symbol TEXT NOT NULL, + address TEXT NOT NULL, + status INTEGER DEFAULT 2, + FOREIGN KEY (queue_id) REFERENCES remove_req_msg(queue_id) +); \ No newline at end of file diff --git a/public/msg_mysql.sql b/public/msg_mysql.sql new file mode 100644 index 0000000..a890982 --- /dev/null +++ b/public/msg_mysql.sql @@ -0,0 +1,109 @@ +-- mysql +CREATE TABLE IF NOT EXISTS topup_req_msg ( + queue_id VARCHAR(255) NOT NULL, + chain VARCHAR(32) NOT NULL, + symbol VARCHAR(32) NOT NULL, + address VARCHAR(255) NOT NULL, + timestamp BIGINT, + sign VARCHAR(255), + status TINYINT DEFAULT 1, + PRIMARY KEY (queue_id) +); + +CREATE TABLE IF NOT EXISTS topup_resp_msg ( + queue_id VARCHAR(255) NOT NULL, + chain VARCHAR(32) NOT NULL, + symbol VARCHAR(32) NOT NULL, + from_addr VARCHAR(255) NOT NULL, + to_addr VARCHAR(255) NOT NULL, + amount DECIMAL(30,16) NOT NULL, + tx_hash VARCHAR(255) DEFAULT NULL, + height BIGINT DEFAULT NULL, + status TINYINT DEFAULT 5, --遵循constant模块的定义 + FOREIGN KEY (queue_id) REFERENCES topup_req_msg(queue_id) +); + +CREATE TABLE IF NOT EXISTS withdraw_req_msg ( + queue_id VARCHAR(255) NOT NULL, + chain VARCHAR(32) NOT NULL, + symbol VARCHAR(32) NOT NULL, + from_addr VARCHAR(255) NOT NULL, + to_addr VARCHAR(255) NOT NULL, + amount DECIMAL(30,16) NOT NULL, + fee DECIMAL(30,16) NOT NULL, + timestamp BIGINT, + sign VARCHAR(255), + status TINYINT DEFAULT 5, --遵循constant模块的定义 + PRIMARY KEY (queue_id) +); + +CREATE TABLE IF NOT EXISTS withdraw_resp_msg ( + queue_id VARCHAR(255) NOT NULL, + chain VARCHAR(32) NOT NULL, + symbol VARCHAR(32) NOT NULL, + from_addr VARCHAR(255) NOT NULL, + to_addr VARCHAR(255) NOT NULL, + tx_hash VARCHAR(255) DEFAULT NULL, + amount DECIMAL(30,16) DEFAULT NULL, + fee DECIMAL(30,16) DEFAULT NULL, + height BIGINT DEFAULT NULL, + status TINYINT DEFAULT 5, --遵循constant模块的定义 + FOREIGN KEY (queue_id) REFERENCES withdraw_req_msg(queue_id) +); + +CREATE TABLE IF NOT EXISTS pay_req_msg ( + queue_id VARCHAR(255) NOT NULL, + chain VARCHAR(32) NOT NULL, + symbol VARCHAR(32) NOT NULL, + from_addr VARCHAR(255) NOT NULL, + to_addr VARCHAR(255) NOT NULL, + total_amount DECIMAL(40, 16) NOT NULL, + total_fee DECIMAL(40, 16) NOT NULL, + timestamp BIGINT, + sign VARCHAR(255), + status TINYINT DEFAULT 5, --遵循constant模块的定义 + PRIMARY KEY (queue_id) +); + +CREATE TABLE IF NOT EXISTS pay_resp_msg ( + queue_id VARCHAR(255) NOT NULL, + chain VARCHAR(32) NOT NULL, + symbol VARCHAR(32) NOT NULL, + from_addr VARCHAR(255) NOT NULL, + pay_status TINYINT DEFAULT 5, --遵循constant模块的定义 + FOREIGN KEY (queue_id) REFERENCES pay_req_msg(queue_id) +); + +-- pay_msg的交易 +CREATE TABLE IF NOT EXISTS pay_msg_tx ( + queue_id VARCHAR(255) NOT NULL, + tx_hash VARCHAR(255) DEFAULT NULL, + to_addr VARCHAR(255) NOT NULL, + amount DECIMAL(30,16) DEFAULT NULL, + fee DECIMAL(30,16), + height BIGINT DEFAULT 0, + status TINYINT DEFAULT 5, --遵循constant模块的定义 + FOREIGN KEY (queue_id) REFERENCES pay_req_msg(queue_id) +); + +CREATE TABLE IF NOT EXISTS remove_req_msg( + queue_id VARCHAR(255) NOT NULL, + msg_type TINYINT NOT NULL, + chain VARCHAR(32) NOT NULL, + symbol VARCHAR(32) NOT NULL, + address VARCHAR(255) NOT NULL, + timestamp BIGINT, + sign VARCHAR(255), + status TINYINT DEFAULT 2, + PRIMARY KEY (queue_id) +); + +CREATE TABLE IF NOT EXISTS remove_resp_msg( + queue_id VARCHAR(255) NOT NULL, + msg_type TINYINT NOT NULL, + chain VARCHAR(32) NOT NULL, + symbol VARCHAR(32) NOT NULL, + address VARCHAR(255) NOT NULL, + status TINYINT DEFAULT 2, + FOREIGN KEY (queue_id) REFERENCES remove_req_msg(queue_id) +); \ No newline at end of file