From 400a585563a1fa74153a3f477c1343fcb2153c21 Mon Sep 17 00:00:00 2001 From: JZ <1379149253@qq.com> Date: Sun, 29 Dec 2024 12:31:43 +0800 Subject: [PATCH] Inspired by the transaction receipt synchronous blocking confirmation strategy in web3J, a synchronous blocking confirmation feature has been implemented in the current repository. --- src/main/java/org/p2p/solanaj/rpc/RpcApi.java | 30 ++++++++ ...anaPollingTransactionConfirmProcessor.java | 69 +++++++++++++++++++ .../SolanaTransactionConfirmProcessor.java | 39 +++++++++++ .../SolanaTransactionException.java | 27 ++++++++ 4 files changed, 165 insertions(+) create mode 100644 src/main/java/org/p2p/solanaj/transaction/confirm/SolanaPollingTransactionConfirmProcessor.java create mode 100644 src/main/java/org/p2p/solanaj/transaction/confirm/SolanaTransactionConfirmProcessor.java create mode 100644 src/main/java/org/p2p/solanaj/transaction/exceptions/SolanaTransactionException.java diff --git a/src/main/java/org/p2p/solanaj/rpc/RpcApi.java b/src/main/java/org/p2p/solanaj/rpc/RpcApi.java index 0e0109a..dc75f2d 100644 --- a/src/main/java/org/p2p/solanaj/rpc/RpcApi.java +++ b/src/main/java/org/p2p/solanaj/rpc/RpcApi.java @@ -9,6 +9,9 @@ import org.p2p.solanaj.rpc.types.TokenResultObjects.TokenAmountInfo; import org.p2p.solanaj.rpc.types.config.*; import org.p2p.solanaj.rpc.types.config.RpcSendTransactionConfig.Encoding; +import org.p2p.solanaj.transaction.confirm.SolanaPollingTransactionConfirmProcessor; +import org.p2p.solanaj.transaction.confirm.SolanaTransactionConfirmProcessor; +import org.p2p.solanaj.transaction.exceptions.SolanaTransactionException; import org.p2p.solanaj.ws.SubscriptionWebSocketClient; import org.p2p.solanaj.ws.listeners.NotificationEventListener; @@ -133,6 +136,24 @@ public void sendAndConfirmTransaction(Transaction transaction, List sig subClient.signatureSubscribe(signature, listener); } + /** + * Send the transaction and block the current thread until the transaction status is confirmed or the waiting time is exceeded. + * @param transaction the transaction to send + * @param signers the list of accounts that will sign the transaction + * @param confirmProcessor the processor that will process the transaction + * @return the confirmation transaction + * @throws RpcException if an error occurs during the RPC call + * @throws SolanaTransactionException Transaction confirmation failed or exceeded the maximum waiting time. + */ + public ConfirmedTransaction sendAndConfirmTransaction(Transaction transaction, List signers, SolanaTransactionConfirmProcessor confirmProcessor) throws RpcException, SolanaTransactionException { + String signature = sendTransaction(transaction, signers, null); + return confirmProcessor.waitForTransactionConfirm(signature); + } + + public ConfirmedTransaction sendAndConfirmTransaction(Transaction transaction, List signers) throws RpcException, SolanaTransactionException { + return sendAndConfirmTransaction(transaction, signers, new SolanaPollingTransactionConfirmProcessor()); + } + public void sendAndConfirmRawTransaction(String encodeSerializedTransaction, RpcSendTransactionConfig rpcSendTransactionConfig, NotificationEventListener listener) throws RpcException { String signature = sendRawTransaction(encodeSerializedTransaction, rpcSendTransactionConfig); @@ -141,6 +162,15 @@ public void sendAndConfirmRawTransaction(String encodeSerializedTransaction, Rpc subClient.signatureSubscribe(signature, listener); } + public ConfirmedTransaction sendAndConfirmRawTransaction(String encodeSerializedTransaction, RpcSendTransactionConfig rpcSendTransactionConfig, SolanaTransactionConfirmProcessor confirmProcessor) throws RpcException, SolanaTransactionException { + String signature = sendRawTransaction(encodeSerializedTransaction, rpcSendTransactionConfig); + return confirmProcessor.waitForTransactionConfirm(signature); + } + + public ConfirmedTransaction sendAndConfirmRawTransaction(String encodeSerializedTransaction, RpcSendTransactionConfig rpcSendTransactionConfig) throws RpcException, SolanaTransactionException { + return sendAndConfirmRawTransaction(encodeSerializedTransaction, rpcSendTransactionConfig, new SolanaPollingTransactionConfirmProcessor()); + } + public long getBalance(PublicKey account) throws RpcException { return getBalance(account, null); } diff --git a/src/main/java/org/p2p/solanaj/transaction/confirm/SolanaPollingTransactionConfirmProcessor.java b/src/main/java/org/p2p/solanaj/transaction/confirm/SolanaPollingTransactionConfirmProcessor.java new file mode 100644 index 0000000..86398ef --- /dev/null +++ b/src/main/java/org/p2p/solanaj/transaction/confirm/SolanaPollingTransactionConfirmProcessor.java @@ -0,0 +1,69 @@ +package org.p2p.solanaj.transaction.confirm; + +import org.p2p.solanaj.rpc.Cluster; +import org.p2p.solanaj.rpc.RpcClient; +import org.p2p.solanaj.rpc.RpcException; +import org.p2p.solanaj.rpc.types.ConfirmedTransaction; +import org.p2p.solanaj.rpc.types.config.Commitment; +import org.p2p.solanaj.transaction.exceptions.SolanaTransactionException; + + +/** + * 使用每个提供的事务id,轮询直到我们获到确认交易 + */ +public class SolanaPollingTransactionConfirmProcessor extends SolanaTransactionConfirmProcessor { + protected final long sleepDuration; + protected final int attempts; + + public SolanaPollingTransactionConfirmProcessor(RpcClient rpcClient, Commitment commitment, long sleepDuration, int attempts) { + super(rpcClient, commitment); + this.sleepDuration = sleepDuration; + this.attempts = attempts; + } + + public SolanaPollingTransactionConfirmProcessor(RpcClient rpcClient, Commitment commitment) { + this(rpcClient, commitment, 1000, 120); + } + + public SolanaPollingTransactionConfirmProcessor(RpcClient rpcClient) { + this(rpcClient, Commitment.FINALIZED); + } + + public SolanaPollingTransactionConfirmProcessor() { + this(new RpcClient(Cluster.MAINNET)); + } + + @Override + public ConfirmedTransaction waitForTransactionConfirm(String txid) throws SolanaTransactionException, RpcException { + return getTransactionReceipt(txid, sleepDuration, attempts); + } + + private ConfirmedTransaction getTransactionReceipt( + String txid, long sleepDuration, int attempts) + throws SolanaTransactionException, RpcException { + + for (int i = 0; i < attempts; i++) { + ConfirmedTransaction confirmedTransaction = sendConfirmTransactionRequest(txid); + + if (confirmedTransaction != null) { + return confirmedTransaction; + } + + // Sleep unless it is the last attempt. + if (i < attempts - 1) { + try { + Thread.sleep(sleepDuration); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + } + + throw new SolanaTransactionException( + "Transaction confirm was not generated after " + + ((sleepDuration * attempts) / 1000 + + " seconds for txid: " + + txid), + txid); + } +} diff --git a/src/main/java/org/p2p/solanaj/transaction/confirm/SolanaTransactionConfirmProcessor.java b/src/main/java/org/p2p/solanaj/transaction/confirm/SolanaTransactionConfirmProcessor.java new file mode 100644 index 0000000..5bf4576 --- /dev/null +++ b/src/main/java/org/p2p/solanaj/transaction/confirm/SolanaTransactionConfirmProcessor.java @@ -0,0 +1,39 @@ +package org.p2p.solanaj.transaction.confirm; + +import org.p2p.solanaj.rpc.RpcClient; +import org.p2p.solanaj.rpc.RpcException; +import org.p2p.solanaj.rpc.types.ConfirmedTransaction; +import org.p2p.solanaj.rpc.types.config.Commitment; +import org.p2p.solanaj.transaction.exceptions.SolanaTransactionException; + + +/** + * solana交易状态确认检查处理 + */ +public abstract class SolanaTransactionConfirmProcessor { + private final RpcClient rpcClient; + private final Commitment commitment; + + public SolanaTransactionConfirmProcessor(RpcClient rpcClient, Commitment commitment) { + this.rpcClient = rpcClient; + this.commitment = commitment; + } + + public abstract ConfirmedTransaction waitForTransactionConfirm(String txid) + throws SolanaTransactionException, RpcException; + + ConfirmedTransaction sendConfirmTransactionRequest(String txid) + throws RpcException, SolanaTransactionException { + ConfirmedTransaction confirmedTransaction = rpcClient.getApi().getTransaction(txid, commitment); + + if (confirmedTransaction == null) { + return null; + } + if (confirmedTransaction.getMeta().getErr() != null) { + throw new SolanaTransactionException( + "Error processing request: " + confirmedTransaction.getMeta().getErr()); + } + + return confirmedTransaction; + } +} diff --git a/src/main/java/org/p2p/solanaj/transaction/exceptions/SolanaTransactionException.java b/src/main/java/org/p2p/solanaj/transaction/exceptions/SolanaTransactionException.java new file mode 100644 index 0000000..61c569f --- /dev/null +++ b/src/main/java/org/p2p/solanaj/transaction/exceptions/SolanaTransactionException.java @@ -0,0 +1,27 @@ +package org.p2p.solanaj.transaction.exceptions; + + +import lombok.Getter; + +import java.util.Optional; + +public class SolanaTransactionException extends Exception { + + @Getter + private Optional txid = Optional.empty(); + + public SolanaTransactionException(String message) { + super(message); + } + + public SolanaTransactionException(String message, String txid) { + super(message); + this.txid = Optional.ofNullable(txid); + } + + public SolanaTransactionException(Throwable cause) { + super(cause); + } + + +}