浏览代码

转账nonce本地和线上对比判定

huangjunling 1 周之前
父节点
当前提交
79b5b08389

+ 0 - 4
blockchain-transfer/src/main/java/com/table/transfer/module/controller/TransferController.java

@@ -6,7 +6,6 @@ import com.table.transfer.config.RabbitConfig;
 import com.table.transfer.module.enpty.TransferRecord;
 import com.table.transfer.module.enpty.TransferRecord;
 import com.table.transfer.module.enpty.TransferRequest;
 import com.table.transfer.module.enpty.TransferRequest;
 import com.table.transfer.module.enpty.TransferResponse;
 import com.table.transfer.module.enpty.TransferResponse;
-import com.table.transfer.module.service.BlockchainService;
 import com.table.transfer.module.service.EthereumService;
 import com.table.transfer.module.service.EthereumService;
 import com.table.transfer.module.service.TransferRecordService;
 import com.table.transfer.module.service.TransferRecordService;
 import com.table.transfer.module.service.TronService;
 import com.table.transfer.module.service.TronService;
@@ -48,10 +47,7 @@ public class TransferController {
                 response= tronService.transfer(request);
                 response= tronService.transfer(request);
             }
             }
             if (chain.equals("ETH")||chain.equals("BSC")){
             if (chain.equals("ETH")||chain.equals("BSC")){
-                if(chain.equals("ETH")){}
-                Thread.sleep(5000);
                 response= ethereumService.transfer(request);
                 response= ethereumService.transfer(request);
-
             }
             }
 
 
             if (response.isSuccess()) {
             if (response.isSuccess()) {

+ 0 - 25
blockchain-transfer/src/main/java/com/table/transfer/module/service/BlockchainService.java

@@ -1,25 +0,0 @@
-package com.table.transfer.module.service;
-
-
-import com.table.transfer.module.enpty.TransactionInfoDTO;
-import com.table.transfer.module.enpty.TransferRequest;
-import com.table.transfer.module.enpty.TransferResponse;
-
-import java.math.BigDecimal;
-import java.math.BigInteger;
-
-public interface BlockchainService {
-    TransferResponse transfer(TransferRequest request) throws Exception;
-
-    boolean validateAddress(String address);
-
-    void validateTransferRequest(TransferRequest transferRequest) throws Exception;
-
-    BigInteger getNonce(String assetCode, String address) throws Exception;
-
-    BigDecimal getTokenBalance(String chain, String contractAddress, String targetAddress) throws Exception;
-
-    BigDecimal getBalance(String chain, String targetAddress) throws Exception;
-
-    TransactionInfoDTO getTransactionInfoByHash(String assetCode, String transactionHash) throws Exception;
-}

+ 77 - 169
blockchain-transfer/src/main/java/com/table/transfer/module/service/EthereumService.java

@@ -3,8 +3,6 @@ package com.table.transfer.module.service;
 
 
 import cn.hutool.core.util.StrUtil;
 import cn.hutool.core.util.StrUtil;
 import cn.hutool.json.JSONUtil;
 import cn.hutool.json.JSONUtil;
-import com.google.common.cache.Cache;
-import com.google.common.cache.CacheBuilder;
 import com.table.transfer.module.enpty.ChainCoinDetail;
 import com.table.transfer.module.enpty.ChainCoinDetail;
 import com.table.transfer.module.enpty.TransactionInfoDTO;
 import com.table.transfer.module.enpty.TransactionInfoDTO;
 import com.table.transfer.module.enpty.TransferRequest;
 import com.table.transfer.module.enpty.TransferRequest;
@@ -12,6 +10,7 @@ import com.table.transfer.module.enpty.TransferResponse;
 import com.table.transfer.util.CryptoUtil;
 import com.table.transfer.util.CryptoUtil;
 import com.table.transfer.util.aUtil;
 import com.table.transfer.util.aUtil;
 import com.table.transfer.util.ether.ChainCoinDetailManager;
 import com.table.transfer.util.ether.ChainCoinDetailManager;
+import com.table.transfer.util.ether.EtherNonceManager;
 import com.table.transfer.util.ether.Web3jManager;
 import com.table.transfer.util.ether.Web3jManager;
 import com.table.transfer.util.encryp.RSAEncryptionUtil;
 import com.table.transfer.util.encryp.RSAEncryptionUtil;
 import lombok.extern.slf4j.Slf4j;
 import lombok.extern.slf4j.Slf4j;
@@ -40,12 +39,10 @@ import java.math.BigInteger;
 import java.math.RoundingMode;
 import java.math.RoundingMode;
 import java.rmi.ServerException;
 import java.rmi.ServerException;
 import java.util.*;
 import java.util.*;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.TimeUnit;
 
 
 @Slf4j
 @Slf4j
 @Service
 @Service
-public class EthereumService implements BlockchainService {
+public class EthereumService {
 
 
     //EVM 链上的 ERC20 固定的事件签名keccak256 哈希
     //EVM 链上的 ERC20 固定的事件签名keccak256 哈希
     String TRANSFER_EVENT_SIGNATURE = "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef";
     String TRANSFER_EVENT_SIGNATURE = "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef";
@@ -53,14 +50,6 @@ public class EthereumService implements BlockchainService {
     // 默认 Gas Limit(保险值)
     // 默认 Gas Limit(保险值)
     public static final BigInteger DEFAULT_GAS_LIMIT_NATIVE = BigInteger.valueOf(21_000);
     public static final BigInteger DEFAULT_GAS_LIMIT_NATIVE = BigInteger.valueOf(21_000);
     public static final BigInteger DEFAULT_GAS_LIMIT_TOKEN = BigInteger.valueOf(120_000);
     public static final BigInteger DEFAULT_GAS_LIMIT_TOKEN = BigInteger.valueOf(120_000);
-    // BSC:官方显示平均手续费,后续应该在数据库中配置,2021年时出现过手续费暴涨的情况,手动配置可以提高最低值,提高成功率
-
-    // 全局 nonce 锁,到期自动释放
-    private final Cache<String, Object> nonceLocks = CacheBuilder.newBuilder()
-            .expireAfterAccess(1, TimeUnit.HOURS) // 1小时无访问自动移除
-            .build();
-    private final Map<String, Object> pendingTransfers = new ConcurrentHashMap<>();
-
 
 
     /**
     /**
      * 获取当前网络 gas price,并设置“最低安全值”
      * 获取当前网络 gas price,并设置“最低安全值”
@@ -80,13 +69,11 @@ public class EthereumService implements BlockchainService {
 
 
         BigInteger maxGasPrice = chainCoinDetail.getMaxGasPrice();
         BigInteger maxGasPrice = chainCoinDetail.getMaxGasPrice();
         if (networkGasPrice.compareTo(maxGasPrice) > 0) {
         if (networkGasPrice.compareTo(maxGasPrice) > 0) {
-            throw new ServerException("手续费超过设定最大值,当前估算Price"+networkGasPrice);
+            throw new ServerException(StrUtil.format("手续费超过设定最大值:{},当前估算Price:{}", maxGasPrice, networkGasPrice));
         }
         }
         return networkGasPrice;
         return networkGasPrice;
     }
     }
 
 
-
-
     public void validateTransferRequest(TransferRequest transferRequest) throws Exception {
     public void validateTransferRequest(TransferRequest transferRequest) throws Exception {
         String pk = RSAEncryptionUtil.decrypt(transferRequest.getEncryptionPrivateKey());
         String pk = RSAEncryptionUtil.decrypt(transferRequest.getEncryptionPrivateKey());
         Credentials credentials = Credentials.create(pk);
         Credentials credentials = Credentials.create(pk);
@@ -107,13 +94,13 @@ public class EthereumService implements BlockchainService {
         if (ChainCoinDetailManager.getChainCoinDetail(transferRequest.getAssetCode()) == null) {
         if (ChainCoinDetailManager.getChainCoinDetail(transferRequest.getAssetCode()) == null) {
             throw new Exception("不支持" + transferRequest.getAssetCode());
             throw new Exception("不支持" + transferRequest.getAssetCode());
         }
         }
-        //todo 支持的类型校验 合约
     }
     }
 
 
 
 
-    @Override
     public TransferResponse transfer(TransferRequest transferRequest) throws Exception {
     public TransferResponse transfer(TransferRequest transferRequest) throws Exception {
         validateTransferRequest(transferRequest);
         validateTransferRequest(transferRequest);
+        //链
+        String chain = transferRequest.getChain();
         //交易对
         //交易对
         String assetCode = transferRequest.getAssetCode();
         String assetCode = transferRequest.getAssetCode();
         //接受地址
         //接受地址
@@ -134,159 +121,100 @@ public class EthereumService implements BlockchainService {
         ChainCoinDetail chainCoinDetail = ChainCoinDetailManager.getChainCoinDetail(assetCode);
         ChainCoinDetail chainCoinDetail = ChainCoinDetailManager.getChainCoinDetail(assetCode);
 
 
         Credentials credentials = Credentials.create(privateKey);
         Credentials credentials = Credentials.create(privateKey);
-        //重试次数
-        int retryCount = 0;
         //hash
         //hash
-        String sentTxHash = null;
-
+        String sentTxHash;
+        //nonce
+        BigInteger nonce = BigInteger.ZERO;
         try {
         try {
-            //todo chainId和链检查 必须相同,避免跨链丢币风险。
-
             //4. 转换金额为wei
             //4. 转换金额为wei
             BigInteger decimalsAmount = toWei(transferRequest.getAmount(), chainCoinDetail.getDecimal());
             BigInteger decimalsAmount = toWei(transferRequest.getAmount(), chainCoinDetail.getDecimal());
             if (decimalsAmount.compareTo(BigInteger.ONE) < 0) {
             if (decimalsAmount.compareTo(BigInteger.ONE) < 0) {
                 return TransferResponse.error("转账金额过低");
                 return TransferResponse.error("转账金额过低");
             }
             }
-            //2.上锁获取nonce
-            BigInteger nonce = null;
-            if (pendingTransfers.containsKey(fromAddress)) {
-                throw new Exception("该发送地址已有未完成转账");
-            }
-            pendingTransfers.put(fromAddress, true);
-
-            Object lock = nonceLocks.get(fromAddress, Object::new);
-            nonce = getNonce(assetCode, fromAddress);
-            while (retryCount < 3) {
-                try {
-
-                    synchronized (lock) {
-                        log.info("【转账开始】账户={},目标={}, 金额={}, nonce={}", fromAddress, toAddress, amount, nonce);
-                        //5. 构建交易
-                        RawTransaction rawTransaction;
-                        //3.估算gas和gaslimit 估算个屁,limit和price估算完全不靠谱
+
+
+            //5. 构建交易
+            RawTransaction rawTransaction;
+            //3.估算gas和gaslimit 估算个屁,limit和price估算完全不靠谱
 //                        BigInteger gasLimit = estimateGasSafely(transferRequest);
 //                        BigInteger gasLimit = estimateGasSafely(transferRequest);
-                        BigInteger gasLimit= isTokenTransfer ? DEFAULT_GAS_LIMIT_TOKEN : DEFAULT_GAS_LIMIT_NATIVE;
-                        BigInteger currentGasPrice = getSafeGasPrice(transferRequest);
-                        log.info("gasLimit: {},currentGasPrice {}", gasLimit, currentGasPrice);
-                        //代币转账
-                        if (isTokenTransfer) {
-                            // 构造代币转账的 function 调用
-                            Function function = new Function(
-                                    "transfer",
-                                    Arrays.asList(new Address(toAddress), new Uint256(decimalsAmount)),
-                                    Arrays.asList(new TypeReference<Bool>() {
-                                    })
-                            );
-                            String data = FunctionEncoder.encode(function);
-
-                            log.info("【代币转账开始】发送者={}, 接收者={}, 代币合约={}, 金额={}, nonce={}", fromAddress, toAddress, contractAddress, decimalsAmount, nonce);
-                            // 构造 RawTransaction:目标是代币合约地址
-                            rawTransaction = RawTransaction.createTransaction(
-                                    nonce,
-                                    currentGasPrice,
-                                    gasLimit,
-                                    transferRequest.getContractAddress(),
-                                    BigInteger.ZERO,
-                                    data
-                            );
-                        } else {
-                            //主币转账
-                            log.info("【转账开始】账户={},目标={}, 金额={}, nonce={}", fromAddress, toAddress, amount, nonce);
-                            rawTransaction = RawTransaction.createEtherTransaction(
-                                    nonce,
-                                    currentGasPrice,
-                                    gasLimit,
-                                    toAddress,
-                                    decimalsAmount
-                            );
-                        }
-
-                        //6.签名交易
-                        byte[] signedMessage = TransactionEncoder.signMessage(rawTransaction, chainCoinDetail.getChainId(), credentials);
-                        String hexValue = Numeric.toHexString(signedMessage);
-                        //7.发送交易
-                        EthSendTransaction response = web3j.ethSendRawTransaction(hexValue).send();
-
-                        if (response.hasError()) {
-                            String errorMsg = response.getError().getMessage();
-                            // 仅对 underpriced 错误重试(同一 nonce)
-                            if (errorMsg.contains("underpriced") || errorMsg.contains("replacement transaction underpriced")) {
-                                retryCount++;
-                                log.warn(" 交易因手续费太低被拒,nonce={},第 {}/3 次重试,gasPrice={}",
-                                        nonce, retryCount, currentGasPrice);
-
-                                // 根据重试次数动态提高 gasPrice
-                                currentGasPrice = currentGasPrice.multiply(BigInteger.valueOf(12)).divide(BigInteger.valueOf(10)); // +20%
-
-                                // 不超过链上限
-                                if (currentGasPrice.compareTo(chainCoinDetail.getMaxGasPrice()) > 0) {
-                                    //todo 超过了链的上限,直接抛弃,然后修改数据库错误提示,告知是因为手续费太高导致的,避免大额手续费浪费
-                                    // 更新数据库
-                                    break;
-                                }
-
-                                continue; // 重试(同一 nonce)
-                            } else {
-                                // 其他错误直接抛出,如 nonce too low, insufficient funds, already known
-                                log.error("节点拒绝交易(不可重试): {}", errorMsg);
-                                throw new Exception("交易失败: " + errorMsg);
-                            }
-                        }
-
-                        // 8.成功发送,拿到哈希
-                        sentTxHash = response.getTransactionHash();
-                    }
-                    log.info("交易已广播,TxHash={}", sentTxHash);
-                    // 不再重试发送,直接等待确认
-                    break;
-
-                } catch (IOException e) {
-                    // 网络层异常:可能是超时,但交易可能已在链上
-                    if (sentTxHash != null) {
-                        TransactionInfoDTO transactionInfoByHash = getTransactionInfoByHash(assetCode, sentTxHash);
-                        if (transactionInfoByHash != null) {
-                            log.warn("网络异常,但交易已发送,尝试查询链上状态: {}", sentTxHash);
-                        }
-                        break; // 跳出重试,进入等待
-                    } else {
-                        // 连广播都没完成,且不是 underpriced 错误,不重试
-                        log.error("网络异常且无交易哈希,放弃重试", e);
-                        throw new Exception("网络异常,无法发送交易" + e);
-                    }
-                } catch (Exception e) {
-                    log.error("交易过程发生未预期异常", e);
-                    throw e;
+            BigInteger gasLimit = isTokenTransfer ? DEFAULT_GAS_LIMIT_TOKEN : DEFAULT_GAS_LIMIT_NATIVE;
+            BigInteger currentGasPrice = getSafeGasPrice(transferRequest);
+            log.info("gasLimit: {},currentGasPrice {}", gasLimit, currentGasPrice);
+            //代币转账
+            if (isTokenTransfer) {
+                // 构造代币转账的 function 调用
+                Function function = new Function(
+                        "transfer",
+                        Arrays.asList(new Address(toAddress), new Uint256(decimalsAmount)),
+                        Arrays.asList(new TypeReference<Bool>() {
+                        })
+                );
+                String data = FunctionEncoder.encode(function);
+
+                nonce = EtherNonceManager.getNonce(chain, fromAddress);
+
+                log.info("【代币转账开始】发送者={}, 接收者={}, 代币合约={}, 金额={}, nonce={}", fromAddress, toAddress, contractAddress, decimalsAmount, nonce);
+                // 构造 RawTransaction:目标是代币合约地址
+                rawTransaction = RawTransaction.createTransaction(
+                        nonce,
+                        currentGasPrice,
+                        gasLimit,
+                        transferRequest.getContractAddress(),
+                        BigInteger.ZERO,
+                        data
+                );
+            } else {
+                //主币转账
+                log.info("【转账开始】账户={},目标={}, 金额={}, nonce={}", fromAddress, toAddress, amount, nonce);
+                rawTransaction = RawTransaction.createEtherTransaction(
+                        nonce,
+                        currentGasPrice,
+                        gasLimit,
+                        toAddress,
+                        decimalsAmount
+                );
+            }
 
 
+            //6.签名交易
+            byte[] signedMessage = TransactionEncoder.signMessage(rawTransaction, chainCoinDetail.getChainId(), credentials);
+            String hexValue = Numeric.toHexString(signedMessage);
+            //7.发送交易
+            EthSendTransaction response = web3j.ethSendRawTransaction(hexValue).send();
+
+            if (response.hasError()) {
+                String errorMsg = response.getError().getMessage();
+                // 仅对 underpriced 错误重试(同一 nonce)
+                if (errorMsg.contains("underpriced") || errorMsg.contains("replacement transaction underpriced")) {
+                    log.warn(" 交易因手续费太低被拒,nonce={},gasPrice={}", nonce, currentGasPrice);
+                    throw new Exception("交易因手续费太低被拒: " + errorMsg);
+                } else {
+                    String f = StrUtil.format("{}转账未知异常{}", chain, errorMsg);
+                    log.error(f);
+                    throw new Exception(f);
                 }
                 }
             }
             }
 
 
+            // 8.成功发送,拿到哈希
+            sentTxHash = response.getTransactionHash();
+
+
             if (sentTxHash == null) {
             if (sentTxHash == null) {
-                // 说明 3 次都失败了,且没拿到哈希
-                log.error("交易发送失败:尝试 3 次均被拒绝(gasPrice 过低),nonce={}", nonce);
-                // 注意:这里不能抛“可重试”异常,否则外部可能再调用
-                throw new Exception("交易因手续费太低被拒,已尝试3次,请稍后重试");
+                // 没拿到哈希
+                log.error("交易已发送,且未提示异常,但是hash为空");
+                throw new Exception("交易已发送,且未提示异常,但是hash为空");
             }
             }
             log.info("交易已发送,TxHash: {}", sentTxHash);
             log.info("交易已发送,TxHash: {}", sentTxHash);
-
-//            // 11. 立即验证是否进入 mempool?
-//            if (!isTransactionInMempool(web3j, sentTxHash)) {
-//                log.error("交易未立即进入 mempool,可能广播失败: {}", sentTxHash);
-//            }
             return TransferResponse.success(sentTxHash);
             return TransferResponse.success(sentTxHash);
 
 
         } catch (Exception e) {
         } catch (Exception e) {
-            log.error("ETH转账失败: {}", e.getMessage());
-            return TransferResponse.error("ETH转账失败: " + e.getMessage());
-        } finally {
-            pendingTransfers.remove(fromAddress);
+            String msg = StrUtil.format("{}转账失败: {}", assetCode, e.getMessage());
+            log.error(msg);
+            return TransferResponse.error(msg);
         }
         }
     }
     }
 
 
     /**
     /**
      * 获取gasLimit
      * 获取gasLimit
-     *
-     * @param request
-     * @return
      */
      */
     public BigInteger estimateGasSafely(TransferRequest request) {
     public BigInteger estimateGasSafely(TransferRequest request) {
         String chain = request.getChain();
         String chain = request.getChain();
@@ -338,29 +266,11 @@ public class EthereumService implements BlockchainService {
 
 
         } catch (Exception e) {
         } catch (Exception e) {
             log.error("Gas 估算准备阶段异常: {}", e.getMessage());
             log.error("Gas 估算准备阶段异常: {}", e.getMessage());
-            e.printStackTrace();
             // 返回基于链类型的默认值
             // 返回基于链类型的默认值
             return isTokenTransfer ? DEFAULT_GAS_LIMIT_TOKEN : DEFAULT_GAS_LIMIT_NATIVE;
             return isTokenTransfer ? DEFAULT_GAS_LIMIT_TOKEN : DEFAULT_GAS_LIMIT_NATIVE;
         }
         }
     }
     }
 
 
-
-    /**
-     * 获取nonce/交易总数
-     *
-     * @return
-     * @throws Exception
-     */
-    public BigInteger getNonce(String assetCode, String address) throws Exception {
-        Web3j web3j = Web3jManager.getWeb3j(aUtil.assetCode2Chain(assetCode));
-        EthGetTransactionCount ethGetTransactionCount = web3j
-                .ethGetTransactionCount(address, DefaultBlockParameterName.PENDING)
-                .send();
-        return ethGetTransactionCount.getTransactionCount();
-
-    }
-
-
     private BigInteger toWei(String amount, Integer decimal) throws Exception {
     private BigInteger toWei(String amount, Integer decimal) throws Exception {
         // 简化的转换,实际需要处理小数
         // 简化的转换,实际需要处理小数
         BigDecimal pow = BigDecimal.TEN.pow(decimal);
         BigDecimal pow = BigDecimal.TEN.pow(decimal);
@@ -433,7 +343,6 @@ public class EthereumService implements BlockchainService {
      * @param address
      * @param address
      * @return
      * @return
      */
      */
-    @Override
     public boolean validateAddress(String address) {
     public boolean validateAddress(String address) {
         return CryptoUtil.validateEthAddress(address);
         return CryptoUtil.validateEthAddress(address);
     }
     }
@@ -441,9 +350,9 @@ public class EthereumService implements BlockchainService {
     /**
     /**
      * 通过hash获取交易信息
      * 通过hash获取交易信息
      */
      */
-    @Override
     public TransactionInfoDTO getTransactionInfoByHash(String assetCode, String transactionHash) throws Exception {
     public TransactionInfoDTO getTransactionInfoByHash(String assetCode, String transactionHash) throws Exception {
         String chain = aUtil.assetCode2Chain(assetCode);
         String chain = aUtil.assetCode2Chain(assetCode);
+        ChainCoinDetail chainCoinDetail = ChainCoinDetailManager.getChainCoinDetail(assetCode);
         // 查询交易回执
         // 查询交易回执
         EthGetTransactionReceipt receiptResponse = Web3jManager.getWeb3j(chain).ethGetTransactionReceipt(transactionHash).send();
         EthGetTransactionReceipt receiptResponse = Web3jManager.getWeb3j(chain).ethGetTransactionReceipt(transactionHash).send();
         Optional<TransactionReceipt> receiptOpt = receiptResponse.getTransactionReceipt();
         Optional<TransactionReceipt> receiptOpt = receiptResponse.getTransactionReceipt();
@@ -519,5 +428,4 @@ public class EthereumService implements BlockchainService {
         if (hex.length() >= length) return hex;
         if (hex.length() >= length) return hex;
         return "0".repeat(length - hex.length()) + hex;
         return "0".repeat(length - hex.length()) + hex;
     }
     }
-
 }
 }

+ 6 - 7
blockchain-transfer/src/main/java/com/table/transfer/module/service/TronService.java

@@ -64,10 +64,6 @@ public class TronService {
      * 单词转账的能量阈值
      * 单词转账的能量阈值
      */
      */
     private static final long ENERGY_PER_TRANSFER = 65_000L;
     private static final long ENERGY_PER_TRANSFER = 65_000L;
-    /**
-     * 转账地址锁,todo mq保证串行后可删
-     */
-    private final Map<String, Object> transferLocks = new ConcurrentHashMap<>();
 
 
     /**
     /**
      * 频率限制器
      * 频率限制器
@@ -113,7 +109,9 @@ public class TronService {
                 heathApiWrapperCount = 0;
                 heathApiWrapperCount = 0;
                 return apiWrapper;
                 return apiWrapper;
             } catch (Exception e) {
             } catch (Exception e) {
-                apiWrapper.close();
+                if (apiWrapper != null) {
+                    apiWrapper.close();
+                }
                 log.warn("ApiWrapper连接异常{},尝试第{}次重新连接", e.getMessage(), MaxHeathApiWrapperCount);
                 log.warn("ApiWrapper连接异常{},尝试第{}次重新连接", e.getMessage(), MaxHeathApiWrapperCount);
                 heathApiWrapperCount++;
                 heathApiWrapperCount++;
             }
             }
@@ -167,8 +165,9 @@ public class TronService {
             log.error("TRON转账失败: {}", e.getMessage(), e);
             log.error("TRON转账失败: {}", e.getMessage(), e);
             return TransferResponse.error("TRON转账失败: " + e.getMessage());
             return TransferResponse.error("TRON转账失败: " + e.getMessage());
         } finally {
         } finally {
-            apiWrapper.close();
-            transferLocks.remove(fromAddress);
+            if (apiWrapper != null) {
+                apiWrapper.close();
+            }
         }
         }
 
 
     }
     }

+ 99 - 0
blockchain-transfer/src/main/java/com/table/transfer/util/ether/EtherNonceManager.java

@@ -0,0 +1,99 @@
+package com.table.transfer.util.ether;
+
+import com.google.common.cache.Cache;
+import com.google.common.cache.CacheBuilder;
+import lombok.extern.slf4j.Slf4j;
+import org.web3j.protocol.Web3j;
+import org.web3j.protocol.core.DefaultBlockParameterName;
+import org.web3j.protocol.core.methods.response.EthGetTransactionCount;
+
+import java.math.BigInteger;
+import java.util.concurrent.TimeUnit;
+
+@Slf4j
+public class EtherNonceManager {
+
+    // 缓存5分钟nonce
+    private static final Cache<String, BigInteger> addressNonceCache = CacheBuilder.newBuilder()
+            .expireAfterAccess(5, TimeUnit.MINUTES)
+            .build();
+
+    /**
+     * 获取nonce - 保守策略
+     * 缓存的值是"下一个要使用的nonce"
+     */
+    public static BigInteger getNonce(String chain, String address) throws Exception {
+        String cacheKey = chain + address;
+        Web3j web3j = Web3jManager.getWeb3j(chain);
+
+        // 获取链上最新nonce(这个值就是下一个可用的nonce)
+        BigInteger chainNonce = getChainNonce(web3j, address);
+
+        // 尝试从缓存获取(缓存的值也应该是下一个要用的nonce)
+        BigInteger cachedNonce = addressNonceCache.getIfPresent(cacheKey);
+
+        if (cachedNonce == null) {
+            // 缓存为空:直接使用链上nonce,缓存保持为链上nonce转账结束后更新缓存nonce
+            addressNonceCache.put(cacheKey, chainNonce.add(BigInteger.ONE));
+            log.info("缓存为空,使用链上nonce: {}", chainNonce);
+            return chainNonce;
+        }
+
+        // 缓存存在:对比缓存和链上nonce
+        if (cachedNonce.compareTo(chainNonce) == 0) {
+            // 一致:使用缓存nonce,缓存递增(因为这次要用掉这个nonce)
+            addressNonceCache.put(cacheKey, cachedNonce.add(BigInteger.ONE));
+            log.info("缓存一致,使用nonce: {}", cachedNonce);
+            return cachedNonce;
+        } else if (cachedNonce.compareTo(chainNonce) < 0) {
+            // 缓存落后:使用链上nonce,更新缓存为链上nonce
+            addressNonceCache.put(cacheKey, chainNonce.add(BigInteger.ONE));
+            log.info("缓存落后,使用链上nonce: {}", chainNonce);
+            return chainNonce;
+        } else {
+            // 缓存超前:等待链上同步到缓存的值
+            log.warn("缓存超前,等待链上同步: 缓存={}, 链上={}", cachedNonce, chainNonce);
+            BigInteger syncedNonce = waitForChainSync(web3j, cacheKey, address);
+            // 等待成功后,缓存设置为syncedNonce + 1(因为这次要用掉syncedNonce)
+            addressNonceCache.put(cacheKey, syncedNonce.add(BigInteger.ONE));
+            return syncedNonce;
+        }
+    }
+
+    /**
+     * 处理缓存超前情况:等待链上nonce同步到缓存的值
+     */
+    private static BigInteger waitForChainSync(Web3j web3j, String cacheKey, String address) throws Exception {
+        int waitCount = 0;
+        BigInteger cacheNonce;
+
+        while ((cacheNonce = addressNonceCache.getIfPresent(cacheKey)) != null && waitCount < 30) {
+            Thread.sleep(10_000);
+
+            BigInteger currentChainNonce = getChainNonce(web3j, address);
+
+            // 等待链上nonce同步,相等正常,大于可能是有人平台外转账了
+            if (currentChainNonce.compareTo(cacheNonce) >= 0) {
+                log.info("链上已同步,返回缓存nonce: {}", cacheNonce);
+                return cacheNonce;
+            }
+            log.info("等待链上同步: 缓存={}, 链上={}, 等待{}/30", cacheNonce, currentChainNonce, ++waitCount);
+        }
+
+        // 缓存过期或等待超时,或者一直小于本地,那么可能是有转账失败了,本地无法降低nonce值,使用链上nonce
+        BigInteger finalChainNonce = getChainNonce(web3j, address);
+        log.info("缓存过期或等待超时,使用链上nonce: {}", finalChainNonce);
+        return finalChainNonce;
+    }
+
+    /**
+     * 从链上获取nonce(包含pending交易)
+     * 这个返回值就是下一个可用的nonce
+     */
+    private static BigInteger getChainNonce(Web3j web3j, String address) throws Exception {
+        EthGetTransactionCount count = web3j
+                .ethGetTransactionCount(address, DefaultBlockParameterName.PENDING)
+                .send();
+        return count.getTransactionCount();
+    }
+}

+ 3 - 2
blockchain-transfer/src/test/java/BlockchainServiceTest.java

@@ -2,6 +2,7 @@ import cn.hutool.json.JSONUtil;
 import com.table.transfer.module.enpty.TransactionInfoDTO;
 import com.table.transfer.module.enpty.TransactionInfoDTO;
 import com.table.transfer.module.enpty.TransferRequest;
 import com.table.transfer.module.enpty.TransferRequest;
 import com.table.transfer.module.service.EthereumService;
 import com.table.transfer.module.service.EthereumService;
+import com.table.transfer.util.ether.EtherNonceManager;
 
 
 import java.math.BigDecimal;
 import java.math.BigDecimal;
 import java.math.BigInteger;
 import java.math.BigInteger;
@@ -25,7 +26,7 @@ public class BlockchainServiceTest {
         bscTransferRequest.setChain("BSC");
         bscTransferRequest.setChain("BSC");
         bscTransferRequest.setCoin("BNB");
         bscTransferRequest.setCoin("BNB");
         //獲取nonce
         //獲取nonce
-        BigInteger bscNonce = ethereumService.getNonce(bscTransferRequest.getAssetCode(), bscTransferRequest.getFromAddress());
+        BigInteger bscNonce = EtherNonceManager.getNonce(bscTransferRequest.getChain(), bscTransferRequest.getFromAddress());
         System.out.println("bsc链获取nonce" + bscNonce);
         System.out.println("bsc链获取nonce" + bscNonce);
         //获取gasLimit和SafeGasPrice
         //获取gasLimit和SafeGasPrice
         BigInteger bscSafeGasPrice = ethereumService.getSafeGasPrice(bscTransferRequest);
         BigInteger bscSafeGasPrice = ethereumService.getSafeGasPrice(bscTransferRequest);
@@ -47,7 +48,7 @@ public class BlockchainServiceTest {
 
 
 
 
         ethereumService.transfer(bscTransferRequest);
         ethereumService.transfer(bscTransferRequest);
-        BigInteger ethNonce = ethereumService.getNonce("USDT-ETH", "0x6F18844e79BeefF5f2D1eE30508aB695A829C12e");
+        BigInteger ethNonce = EtherNonceManager.getNonce("ETH", "0x6F18844e79BeefF5f2D1eE30508aB695A829C12e");
         System.out.println("eth链获取nonce" + ethNonce);
         System.out.println("eth链获取nonce" + ethNonce);
 
 
     }
     }