diff --git a/client/src/integration-test/java/com/lmax/solana4j/client/jsonrpc/GetAccountInfoContractTest.java b/client/src/integration-test/java/com/lmax/solana4j/client/jsonrpc/GetAccountInfoContractTest.java index 02ea8029..8af457f9 100644 --- a/client/src/integration-test/java/com/lmax/solana4j/client/jsonrpc/GetAccountInfoContractTest.java +++ b/client/src/integration-test/java/com/lmax/solana4j/client/jsonrpc/GetAccountInfoContractTest.java @@ -1,7 +1,5 @@ package com.lmax.solana4j.client.jsonrpc; -import com.lmax.solana4j.assertion.Condition; -import com.lmax.solana4j.assertion.Waiter; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; @@ -11,7 +9,7 @@ class GetAccountInfoContractTest extends SolanaClientIntegrationTestBase { @Test - void shouldGetAccountInfo() + void shouldGetAccountInfo() throws SolanaJsonRpcClientException { // { // "jsonrpc" : "2.0", @@ -32,7 +30,7 @@ void shouldGetAccountInfo() // "id" : 4 // } - final var accountInfo = Waiter.waitFor(Condition.isNotNull(() -> api.getAccountInfo(payerAccount).getResponse())); + final var accountInfo = api.getAccountInfo(payerAccount).getResponse(); assertThat(accountInfo.getOwner()).isEqualTo("11111111111111111111111111111111"); assertThat(accountInfo.getData().get(0)).isEqualTo(""); @@ -45,7 +43,7 @@ void shouldGetAccountInfo() @Test @Disabled - void shouldGetTokenAccountInfo() + void shouldGetTokenAccountInfo() throws SolanaJsonRpcClientException { // { // "jsonrpc" : "2.0", @@ -66,7 +64,7 @@ void shouldGetTokenAccountInfo() // "id" : 4 // } - final var tokenAccountInfo = Waiter.waitFor(Condition.isNotNull(() -> api.getAccountInfo(tokenAccount))); + final var tokenAccountInfo = api.getAccountInfo(tokenAccount).getResponse(); // assertThat(tokenAccountInfo.getOwner()).isEqualTo("11111111111111111111111111111111"); diff --git a/client/src/integration-test/java/com/lmax/solana4j/client/jsonrpc/GetBalanceContractTest.java b/client/src/integration-test/java/com/lmax/solana4j/client/jsonrpc/GetBalanceContractTest.java index c9c546b2..7a67ca78 100644 --- a/client/src/integration-test/java/com/lmax/solana4j/client/jsonrpc/GetBalanceContractTest.java +++ b/client/src/integration-test/java/com/lmax/solana4j/client/jsonrpc/GetBalanceContractTest.java @@ -9,7 +9,7 @@ class GetBalanceContractTest extends SolanaClientIntegrationTestBase { @Test - void shouldGetBalance() + void shouldGetBalance() throws SolanaJsonRpcClientException { // { // "jsonrpc" : "2.0", diff --git a/client/src/integration-test/java/com/lmax/solana4j/client/jsonrpc/GetBlockHeightContractTest.java b/client/src/integration-test/java/com/lmax/solana4j/client/jsonrpc/GetBlockHeightContractTest.java index 6ed96cc5..50ebdf26 100644 --- a/client/src/integration-test/java/com/lmax/solana4j/client/jsonrpc/GetBlockHeightContractTest.java +++ b/client/src/integration-test/java/com/lmax/solana4j/client/jsonrpc/GetBlockHeightContractTest.java @@ -8,7 +8,7 @@ class GetBlockHeightContractTest extends SolanaClientIntegrationTestBase { @Test - void shouldGetBlockHeight() + void shouldGetBlockHeight() throws SolanaJsonRpcClientException { // { // "jsonrpc" : "2.0", diff --git a/client/src/integration-test/java/com/lmax/solana4j/client/jsonrpc/GetLatestBlockhashContractTest.java b/client/src/integration-test/java/com/lmax/solana4j/client/jsonrpc/GetLatestBlockhashContractTest.java index 705d68f1..34aff61d 100644 --- a/client/src/integration-test/java/com/lmax/solana4j/client/jsonrpc/GetLatestBlockhashContractTest.java +++ b/client/src/integration-test/java/com/lmax/solana4j/client/jsonrpc/GetLatestBlockhashContractTest.java @@ -8,7 +8,7 @@ class GetLatestBlockhashContractTest extends SolanaClientIntegrationTestBase { @Test - void shouldGetLatestBlockhash() + void shouldGetLatestBlockhash() throws SolanaJsonRpcClientException { // { // "jsonrpc" : "2.0", diff --git a/client/src/integration-test/java/com/lmax/solana4j/client/jsonrpc/GetMinimumBalanceForRentExemptionContractTest.java b/client/src/integration-test/java/com/lmax/solana4j/client/jsonrpc/GetMinimumBalanceForRentExemptionContractTest.java index 5e239a51..642b1a2b 100644 --- a/client/src/integration-test/java/com/lmax/solana4j/client/jsonrpc/GetMinimumBalanceForRentExemptionContractTest.java +++ b/client/src/integration-test/java/com/lmax/solana4j/client/jsonrpc/GetMinimumBalanceForRentExemptionContractTest.java @@ -9,7 +9,7 @@ class GetMinimumBalanceForRentExemptionContractTest extends SolanaClientIntegrationTestBase { @Test - void shouldGetMinimumBalanceForRentExemption() + void shouldGetMinimumBalanceForRentExemption() throws SolanaJsonRpcClientException { // { // "jsonrpc" : "2.0", diff --git a/client/src/integration-test/java/com/lmax/solana4j/client/jsonrpc/GetSlotContractTest.java b/client/src/integration-test/java/com/lmax/solana4j/client/jsonrpc/GetSlotContractTest.java index a4f248d0..261b92d9 100644 --- a/client/src/integration-test/java/com/lmax/solana4j/client/jsonrpc/GetSlotContractTest.java +++ b/client/src/integration-test/java/com/lmax/solana4j/client/jsonrpc/GetSlotContractTest.java @@ -8,7 +8,7 @@ class GetSlotContractTest extends SolanaClientIntegrationTestBase { @Test - void shouldGetSlot() + void shouldGetSlot() throws SolanaJsonRpcClientException { // { // "jsonrpc" : "2.0", diff --git a/client/src/integration-test/java/com/lmax/solana4j/client/jsonrpc/GetTokenAccountBalanceContractTest.java b/client/src/integration-test/java/com/lmax/solana4j/client/jsonrpc/GetTokenAccountBalanceContractTest.java index af4f9dc1..97b9295c 100644 --- a/client/src/integration-test/java/com/lmax/solana4j/client/jsonrpc/GetTokenAccountBalanceContractTest.java +++ b/client/src/integration-test/java/com/lmax/solana4j/client/jsonrpc/GetTokenAccountBalanceContractTest.java @@ -9,7 +9,7 @@ class GetTokenAccountBalanceContractTest extends SolanaClientIntegrationTestBase { @Test - void shouldGetTokenAccountBalance() + void shouldGetTokenAccountBalance() throws SolanaJsonRpcClientException { // { // "jsonrpc" : "2.0", diff --git a/client/src/integration-test/java/com/lmax/solana4j/client/jsonrpc/GetTransactionContractTest.java b/client/src/integration-test/java/com/lmax/solana4j/client/jsonrpc/GetTransactionContractTest.java index a28f994a..e97c6ccb 100644 --- a/client/src/integration-test/java/com/lmax/solana4j/client/jsonrpc/GetTransactionContractTest.java +++ b/client/src/integration-test/java/com/lmax/solana4j/client/jsonrpc/GetTransactionContractTest.java @@ -13,7 +13,7 @@ class GetTransactionContractTest extends SolanaClientIntegrationTestBase { @Test @Disabled - void shouldGetTransaction() + void shouldGetTransaction() throws SolanaJsonRpcClientException { { // "jsonrpc" : "2.0", @@ -64,7 +64,18 @@ void shouldGetTransaction() final String transactionSignature = api.requestAirdrop(payerAccount, Sol.lamports(BigDecimal.ONE)).getResponse(); - final var transaction = Waiter.waitFor(Condition.isNotNull(() -> api.getTransaction(transactionSignature))); + // will need to wait a bit for the transaction above to appear + final var transaction = Waiter.waitFor(Condition.isNotNull(() -> + { + try + { + return api.getTransaction(transactionSignature); + } + catch (SolanaJsonRpcClientException e) + { + throw new RuntimeException(e); + } + })); } } diff --git a/client/src/integration-test/java/com/lmax/solana4j/client/jsonrpc/RequestAirdropContractTest.java b/client/src/integration-test/java/com/lmax/solana4j/client/jsonrpc/RequestAirdropContractTest.java index 74bec599..dba8b917 100644 --- a/client/src/integration-test/java/com/lmax/solana4j/client/jsonrpc/RequestAirdropContractTest.java +++ b/client/src/integration-test/java/com/lmax/solana4j/client/jsonrpc/RequestAirdropContractTest.java @@ -12,7 +12,7 @@ class RequestAirdropContractTest extends SolanaClientIntegrationTestBase { @Test - void shouldRequestAirdrop() + void shouldRequestAirdrop() throws SolanaJsonRpcClientException { // { // "jsonrpc" : "2.0", diff --git a/client/src/integration-test/java/com/lmax/solana4j/client/jsonrpc/SendTransactionContractTest.java b/client/src/integration-test/java/com/lmax/solana4j/client/jsonrpc/SendTransactionContractTest.java index 46396ba7..80918b7e 100644 --- a/client/src/integration-test/java/com/lmax/solana4j/client/jsonrpc/SendTransactionContractTest.java +++ b/client/src/integration-test/java/com/lmax/solana4j/client/jsonrpc/SendTransactionContractTest.java @@ -10,7 +10,7 @@ class SendTransactionContractTest extends SolanaClientIntegrationTestBase { @Test @Disabled - void shouldSendTransaction() + void shouldSendTransaction() throws SolanaJsonRpcClientException { final var transactionSignature = api.sendTransaction("blobWellFormed...Not"); fail(); diff --git a/client/src/integration-test/java/com/lmax/solana4j/client/jsonrpc/SolanaClientIntegrationTestBase.java b/client/src/integration-test/java/com/lmax/solana4j/client/jsonrpc/SolanaClientIntegrationTestBase.java index 5877f5c9..020a29d6 100644 --- a/client/src/integration-test/java/com/lmax/solana4j/client/jsonrpc/SolanaClientIntegrationTestBase.java +++ b/client/src/integration-test/java/com/lmax/solana4j/client/jsonrpc/SolanaClientIntegrationTestBase.java @@ -24,6 +24,16 @@ void moreSetup() { api = new SolanaJsonRpcClient(rpcUrl, true); - Waiter.waitFor(Condition.isTrue(() -> api.getSlot().getResponse() > 35)); + Waiter.waitFor(Condition.isTrue(() -> + { + try + { + return api.getSlot().getResponse() > 35; + } + catch (SolanaJsonRpcClientException e) + { + throw new RuntimeException("Something went wrong in the test setup.", e); + } + })); } } diff --git a/client/src/main/java/com/lmax/solana4j/client/api/ErrorCode.java b/client/src/main/java/com/lmax/solana4j/client/api/ErrorCode.java deleted file mode 100644 index a4b2b641..00000000 --- a/client/src/main/java/com/lmax/solana4j/client/api/ErrorCode.java +++ /dev/null @@ -1,35 +0,0 @@ -package com.lmax.solana4j.client.api; - -/** - * Enum representing various error codes that can be encountered - * during interactions with the Solana JSON-RPC API. - */ -public enum ErrorCode -{ - /** - * Error encountered while processing JSON data. - * This typically indicates a problem with serializing or deserializing - * JSON when communicating with the Solana API. - */ - JSON_PROCESSING_ERROR, - - /** - * Error encountered during communication with the JSON-RPC endpoint. - * This usually refers to network issues or failures when attempting to - * send or receive data from the Solana JSON-RPC node. - */ - JSON_RPC_COMMUNICATIONS_FAILURE, - - /** - * Error indicating that the JSON-RPC call received an unexpected status code. - * This could happen if the HTTP status code from the Solana node is not - * the expected one (e.g., 4xx or 5xx errors). - */ - JSON_RPC_UNEXPECTED_STATUS_CODE, - - /** - * Error reported by the Solana JSON-RPC node itself. - * This occurs when the node returns an error in response to a JSON-RPC request. - */ - JSON_RPC_REPORTED_ERROR; -} \ No newline at end of file diff --git a/client/src/main/java/com/lmax/solana4j/client/api/SolanaApi.java b/client/src/main/java/com/lmax/solana4j/client/api/SolanaApi.java index b3f38980..b1659ca8 100644 --- a/client/src/main/java/com/lmax/solana4j/client/api/SolanaApi.java +++ b/client/src/main/java/com/lmax/solana4j/client/api/SolanaApi.java @@ -1,5 +1,7 @@ package com.lmax.solana4j.client.api; +import com.lmax.solana4j.client.jsonrpc.SolanaJsonRpcClientException; + /** * Represents the API for interacting with the Solana blockchain. * This interface provides methods for sending transactions, querying account balances, requesting airdrops, @@ -13,16 +15,18 @@ public interface SolanaApi * * @param transactionBlob the base64-encoded string representing the transaction * @return the signature of the transaction, which is a base58-encoded string + * @throws SolanaJsonRpcClientException if there is an error with the JSON-RPC request */ - SolanaClientResponse sendTransaction(String transactionBlob); + SolanaClientResponse sendTransaction(String transactionBlob) throws SolanaJsonRpcClientException; /** * Retrieves the transaction response for a given transaction signature. * * @param transactionSignature the base58-encoded signature of the transaction * @return the {@link TransactionResponse} containing details of the transaction + * @throws SolanaJsonRpcClientException if there is an error with the JSON-RPC request */ - SolanaClientResponse getTransaction(String transactionSignature); + SolanaClientResponse getTransaction(String transactionSignature) throws SolanaJsonRpcClientException; /** * Requests an airdrop of lamports to the specified address. @@ -31,24 +35,27 @@ public interface SolanaApi * @param address the base58-encoded public key of the recipient account * @param amountLamports the amount of lamports to be airdropped * @return the transaction signature as a base58-encoded string + * @throws SolanaJsonRpcClientException if there is an error with the JSON-RPC request */ - SolanaClientResponse requestAirdrop(String address, long amountLamports); + SolanaClientResponse requestAirdrop(String address, long amountLamports) throws SolanaJsonRpcClientException; /** * Retrieves the balance of an account in lamports. * * @param address the base58-encoded public key of the account * @return the balance of the account in lamports + * @throws SolanaJsonRpcClientException if there is an error with the JSON-RPC request */ - SolanaClientResponse getBalance(String address); + SolanaClientResponse getBalance(String address) throws SolanaJsonRpcClientException; /** * Retrieves the token account balance of an SPL token account. * * @param address the base58-encoded public key of the token account * @return the {@link TokenAmount} representing the token balance of the account + * @throws SolanaJsonRpcClientException if there is an error with the JSON-RPC request */ - SolanaClientResponse getTokenAccountBalance(String address); + SolanaClientResponse getTokenAccountBalance(String address) throws SolanaJsonRpcClientException; /** * Retrieves the account information for the specified address. @@ -56,32 +63,36 @@ public interface SolanaApi * * @param address the base58-encoded public key of the account * @return the {@link AccountInfo} containing details of the account + * @throws SolanaJsonRpcClientException if there is an error with the JSON-RPC request */ - SolanaClientResponse getAccountInfo(String address); + SolanaClientResponse getAccountInfo(String address) throws SolanaJsonRpcClientException; /** * Retrieves the current block height of the Solana blockchain. * The block height represents the number of blocks preceding the current block. * * @return the current block height + * @throws SolanaJsonRpcClientException if there is an error with the JSON-RPC request */ - SolanaClientResponse getBlockHeight(); + SolanaClientResponse getBlockHeight() throws SolanaJsonRpcClientException; /** * Retrieves the current slot number. * A slot is a specific point in time or block on the Solana blockchain. * * @return the current slot number + * @throws SolanaJsonRpcClientException if there is an error with the JSON-RPC request */ - SolanaClientResponse getSlot(); + SolanaClientResponse getSlot() throws SolanaJsonRpcClientException; /** * Retrieves the most recent blockhash. * The blockhash ensures that a transaction is processed within a specific time window. * * @return the {@link Blockhash} representing the most recent blockhash + * @throws SolanaJsonRpcClientException if there is an error with the JSON-RPC request */ - SolanaClientResponse getLatestBlockhash(); + SolanaClientResponse getLatestBlockhash() throws SolanaJsonRpcClientException; /** * Retrieves the minimum balance required for rent exemption for an account of the given size. @@ -89,6 +100,7 @@ public interface SolanaApi * * @param size the size of the account in bytes * @return the minimum balance in lamports for rent exemption + * @throws SolanaJsonRpcClientException if there is an error with the JSON-RPC request */ - SolanaClientResponse getMinimumBalanceForRentExemption(int size); -} \ No newline at end of file + SolanaClientResponse getMinimumBalanceForRentExemption(int size) throws SolanaJsonRpcClientException; +} diff --git a/client/src/main/java/com/lmax/solana4j/client/api/SolanaClientError.java b/client/src/main/java/com/lmax/solana4j/client/api/SolanaClientError.java index 6b481ee1..c67ca174 100644 --- a/client/src/main/java/com/lmax/solana4j/client/api/SolanaClientError.java +++ b/client/src/main/java/com/lmax/solana4j/client/api/SolanaClientError.java @@ -2,9 +2,7 @@ public interface SolanaClientError { - ErrorCode getErrorCode(); + long getErrorCode(); String getErrorMessage(); - - boolean isRecoverable(); } diff --git a/client/src/main/java/com/lmax/solana4j/client/jsonrpc/SolanaJsonRpcClient.java b/client/src/main/java/com/lmax/solana4j/client/jsonrpc/SolanaJsonRpcClient.java index f7603d01..134bcb6b 100644 --- a/client/src/main/java/com/lmax/solana4j/client/jsonrpc/SolanaJsonRpcClient.java +++ b/client/src/main/java/com/lmax/solana4j/client/jsonrpc/SolanaJsonRpcClient.java @@ -5,7 +5,6 @@ import com.lmax.solana4j.client.api.AccountInfo; import com.lmax.solana4j.client.api.Blockhash; import com.lmax.solana4j.client.api.Commitment; -import com.lmax.solana4j.client.api.ErrorCode; import com.lmax.solana4j.client.api.SolanaApi; import com.lmax.solana4j.client.api.SolanaClientError; import com.lmax.solana4j.client.api.SolanaClientResponse; @@ -19,7 +18,6 @@ import java.net.http.HttpResponse; import java.nio.charset.StandardCharsets; import java.time.Duration; -import java.util.Arrays; import java.util.Locale; import java.util.Map; @@ -50,7 +48,7 @@ public SolanaJsonRpcClient(final String rpcUrl, final Duration connectionTimeout } @Override - public SolanaClientResponse requestAirdrop(final String address, final long amountLamports) + public SolanaClientResponse requestAirdrop(final String address, final long amountLamports) throws SolanaJsonRpcClientException { final var result = queryForObject(new TypeReference>() { @@ -58,14 +56,14 @@ public SolanaClientResponse requestAirdrop(final String address, final l if (result.isError()) { - return new SolanaJsonRpcClientResponse<>(result.getError().getErrorCode(), result.getError().getErrorMessage()); + return new SolanaJsonRpcClientResponse<>(result.getError()); } return new SolanaJsonRpcClientResponse<>(result.getSuccess()); } @Override - public SolanaClientResponse getTransaction(final String transactionSignature) + public SolanaClientResponse getTransaction(final String transactionSignature) throws SolanaJsonRpcClientException { final var result = queryForObject(new TypeReference>() { @@ -80,14 +78,14 @@ public SolanaClientResponse getTransaction(final String tra if (result.isError()) { - return new SolanaJsonRpcClientResponse<>(result.getError().getErrorCode(), result.getError().getErrorMessage()); + return new SolanaJsonRpcClientResponse<>(result.getError()); } return new SolanaJsonRpcClientResponse<>(result.getSuccess()); } @Override - public SolanaClientResponse sendTransaction(final String transactionBlob) + public SolanaClientResponse sendTransaction(final String transactionBlob) throws SolanaJsonRpcClientException { final var result = queryForObject(new TypeReference>() { @@ -99,14 +97,14 @@ public SolanaClientResponse sendTransaction(final String transactionBlob if (result.isError()) { - return new SolanaJsonRpcClientResponse<>(result.getError().getErrorCode(), result.getError().getErrorMessage()); + return new SolanaJsonRpcClientResponse<>(result.getError()); } return new SolanaJsonRpcClientResponse<>(result.getSuccess()); } @Override - public SolanaClientResponse getBalance(final String address) + public SolanaClientResponse getBalance(final String address) throws SolanaJsonRpcClientException { final var result = queryForObject(new TypeReference>() { @@ -116,14 +114,14 @@ public SolanaClientResponse getBalance(final String address) if (result.isError()) { - return new SolanaJsonRpcClientResponse<>(result.getError().getErrorCode(), result.getError().getErrorMessage()); + return new SolanaJsonRpcClientResponse<>(result.getError()); } return new SolanaJsonRpcClientResponse<>(result.getSuccess().getValue()); } @Override - public SolanaClientResponse getTokenAccountBalance(final String address) + public SolanaClientResponse getTokenAccountBalance(final String address) throws SolanaJsonRpcClientException { final var result = queryForObject( new TypeReference>() @@ -134,14 +132,14 @@ public SolanaClientResponse getTokenAccountBalance(final String add if (result.isError()) { - return new SolanaJsonRpcClientResponse<>(result.getError().getErrorCode(), result.getError().getErrorMessage()); + return new SolanaJsonRpcClientResponse<>(result.getError()); } return new SolanaJsonRpcClientResponse<>(result.getSuccess().getValue()); } @Override - public SolanaClientResponse getAccountInfo(final String address) + public SolanaClientResponse getAccountInfo(final String address) throws SolanaJsonRpcClientException { final var result = queryForObject(new TypeReference>() { @@ -153,14 +151,14 @@ public SolanaClientResponse getAccountInfo(final String address) if (result.isError()) { - return new SolanaJsonRpcClientResponse<>(result.getError().getErrorCode(), result.getError().getErrorMessage()); + return new SolanaJsonRpcClientResponse<>(result.getError()); } return new SolanaJsonRpcClientResponse<>(result.getSuccess().getValue()); } @Override - public SolanaClientResponse getBlockHeight() + public SolanaClientResponse getBlockHeight() throws SolanaJsonRpcClientException { final var result = queryForObject(new TypeReference>() { @@ -168,14 +166,14 @@ public SolanaClientResponse getBlockHeight() if (result.isError()) { - return new SolanaJsonRpcClientResponse<>(result.getError().getErrorCode(), result.getError().getErrorMessage()); + return new SolanaJsonRpcClientResponse<>(result.getError()); } return new SolanaJsonRpcClientResponse<>(result.getSuccess()); } @Override - public SolanaClientResponse getSlot() + public SolanaClientResponse getSlot() throws SolanaJsonRpcClientException { final var result = queryForObject(new TypeReference>() { @@ -183,14 +181,14 @@ public SolanaClientResponse getSlot() if (result.isError()) { - return new SolanaJsonRpcClientResponse<>(result.getError().getErrorCode(), result.getError().getErrorMessage()); + return new SolanaJsonRpcClientResponse<>(result.getError()); } return new SolanaJsonRpcClientResponse<>(result.getSuccess()); } @Override - public SolanaClientResponse getLatestBlockhash() + public SolanaClientResponse getLatestBlockhash() throws SolanaJsonRpcClientException { final var result = queryForObject(new TypeReference>() { @@ -198,14 +196,14 @@ public SolanaClientResponse getLatestBlockhash() if (result.isError()) { - return new SolanaJsonRpcClientResponse<>(result.getError().getErrorCode(), result.getError().getErrorMessage()); + return new SolanaJsonRpcClientResponse<>(result.getError()); } return new SolanaJsonRpcClientResponse<>(result.getSuccess().getValue()); } @Override - public SolanaClientResponse getMinimumBalanceForRentExemption(final int size) + public SolanaClientResponse getMinimumBalanceForRentExemption(final int size) throws SolanaJsonRpcClientException { final var result = queryForObject(new TypeReference>() { @@ -213,96 +211,68 @@ public SolanaClientResponse getMinimumBalanceForRentExemption(final int si if (result.isError()) { - return new SolanaJsonRpcClientResponse<>(result.getError().getErrorCode(), result.getError().getErrorMessage()); + return new SolanaJsonRpcClientResponse<>(result.getError().getErrorCode()); } return new SolanaJsonRpcClientResponse<>(result.getSuccess()); } - private Result queryForObject(final TypeReference> type, final String method, final Object... params) + private Result queryForObject(final TypeReference> type, final String method, final Object... params) throws SolanaJsonRpcClientException { - return performRequest(method, params, type); - } + final HttpRequest request = prepareRequest(method, params); + final HttpResponse httpResponse = sendRequest(request); - private Result performRequest( - final String method, - final Object[] params, - final TypeReference> type) - { - return prepareRequest(method, params) - .onSuccess(success -> sendRequest(method, params, success)) - .onSuccess(success -> decodeResponse(method, params, type, success)); + return decodeResponse(type, httpResponse); } - private Result prepareRequest(final String method, final Object[] params) + private HttpRequest prepareRequest(final String method, final Object[] params) throws SolanaJsonRpcClientException { try { - return Result.success(buildPostRequest(solanaCodec.encodeRequest(method, params))); + return buildPostRequest(solanaCodec.encodeRequest(method, params)); } catch (final JsonProcessingException e) { - return Result.error(new SolanaUnrecoverableJsonRpcClientError( - ErrorCode.JSON_PROCESSING_ERROR, - String.format("Unable to encode JSON RPC request for method %s and params %s.", method, Arrays.toString(params))) - ); + throw new SolanaJsonRpcClientException(String.format("An error occurred building the JSON RPC request for method %s.", method), e); } } - private Result> sendRequest(final String method, final Object[] params, final HttpRequest request) + private HttpResponse sendRequest(final HttpRequest request) throws SolanaJsonRpcClientException { try { final HttpResponse httpResponse = httpClient.send(request, HttpResponse.BodyHandlers.ofString()); if (httpResponse.statusCode() != 200) { - return Result.error(new SolanaUnrecoverableJsonRpcClientError( - ErrorCode.JSON_RPC_UNEXPECTED_STATUS_CODE, - String.format("Unexpected status code returned from the JSON RPC for method %s and params %s. The status code was %s with message %s.", - method, - Arrays.toString(params), - httpResponse.statusCode(), - httpResponse.body())) - ); + throw new SolanaJsonRpcClientException(String.format("Unexpected status code %s returned from the JSON RPC for request %s.", httpResponse.statusCode(), request)); } else { - return Result.success(httpResponse); + return httpResponse; } } catch (final IOException | InterruptedException e) { - return Result.error(new SolanaRecoverableJsonRpcClientError( - ErrorCode.JSON_RPC_COMMUNICATIONS_FAILURE, - String.format("Unable to communicate with the JSON RPC for method %s and params %s.", method, Arrays.toString(params))) - ); + throw new SolanaJsonRpcClientException(String.format("Unable to communicate with the JSON RPC for request %s.", request), e, true); } } private Result decodeResponse( - final String method, - final Object[] params, final TypeReference> type, - final HttpResponse httpResponse) + final HttpResponse httpResponse) throws SolanaJsonRpcClientException { try { final RpcWrapperDTO rpcResult = solanaCodec.decodeResponse(httpResponse.body().getBytes(StandardCharsets.UTF_8), type); if (rpcResult.getError() != null) { - return Result.error(new SolanaUnrecoverableJsonRpcClientError( - ErrorCode.JSON_RPC_REPORTED_ERROR, - String.format("An error was reported by the JSON RPC with code %s and message %s.", rpcResult.getError().getCode(), rpcResult.getError().getMessage())) - ); + return Result.error(new SolanaJsonRpcClientError(rpcResult.getError().getCode(), rpcResult.getError().getMessage())); } return Result.success(rpcResult.getResult()); } catch (final IOException e) { - return Result.error(new SolanaUnrecoverableJsonRpcClientError( - ErrorCode.JSON_PROCESSING_ERROR, - String.format("Unable to decode JSON RPC response for method %s and params %s.", method, Arrays.toString(params))) - ); + throw new SolanaJsonRpcClientException(String.format("Unable to decode JSON RPC response %s.", httpResponse), e); } } diff --git a/client/src/main/java/com/lmax/solana4j/client/jsonrpc/SolanaRecoverableJsonRpcClientError.java b/client/src/main/java/com/lmax/solana4j/client/jsonrpc/SolanaJsonRpcClientError.java similarity index 50% rename from client/src/main/java/com/lmax/solana4j/client/jsonrpc/SolanaRecoverableJsonRpcClientError.java rename to client/src/main/java/com/lmax/solana4j/client/jsonrpc/SolanaJsonRpcClientError.java index 5b54bf82..f231e8f4 100644 --- a/client/src/main/java/com/lmax/solana4j/client/jsonrpc/SolanaRecoverableJsonRpcClientError.java +++ b/client/src/main/java/com/lmax/solana4j/client/jsonrpc/SolanaJsonRpcClientError.java @@ -1,21 +1,20 @@ package com.lmax.solana4j.client.jsonrpc; -import com.lmax.solana4j.client.api.ErrorCode; import com.lmax.solana4j.client.api.SolanaClientError; -class SolanaRecoverableJsonRpcClientError implements SolanaClientError +class SolanaJsonRpcClientError implements SolanaClientError { - private final ErrorCode errorCode; + private final long errorCode; private final String errorMessage; - SolanaRecoverableJsonRpcClientError(final ErrorCode errorCode, final String errorMessage) + SolanaJsonRpcClientError(final long errorCode, final String errorMessage) { this.errorCode = errorCode; this.errorMessage = errorMessage; } @Override - public ErrorCode getErrorCode() + public long getErrorCode() { return errorCode; } @@ -25,10 +24,4 @@ public String getErrorMessage() { return errorMessage; } - - @Override - public boolean isRecoverable() - { - return true; - } } diff --git a/client/src/main/java/com/lmax/solana4j/client/jsonrpc/SolanaJsonRpcClientException.java b/client/src/main/java/com/lmax/solana4j/client/jsonrpc/SolanaJsonRpcClientException.java new file mode 100644 index 00000000..763a37eb --- /dev/null +++ b/client/src/main/java/com/lmax/solana4j/client/jsonrpc/SolanaJsonRpcClientException.java @@ -0,0 +1,57 @@ +package com.lmax.solana4j.client.jsonrpc; + + +public class SolanaJsonRpcClientException extends Exception +{ + private final String message; + private final Throwable throwable; + private final boolean recoverable; + + SolanaJsonRpcClientException(final String message) + { + this.message = message; + this.throwable = null; + this.recoverable = false; + } + + SolanaJsonRpcClientException(final String message, final Throwable throwable) + { + this.message = message; + this.throwable = throwable; + this.recoverable = false; + } + + SolanaJsonRpcClientException(final String message, final Throwable throwable, final boolean recoverable) + { + this.message = message; + this.throwable = throwable; + this.recoverable = recoverable; + } + + @Override + public String getMessage() + { + return message; + } + + @Override + public Throwable getCause() + { + return throwable; + } + + public boolean isRecoverable() + { + return recoverable; + } + + @Override + public String toString() + { + return "SolanaJsonRpcError{" + + "message='" + message + '\'' + + ", throwable=" + throwable + + ", recoverable=" + recoverable + + '}'; + } +} diff --git a/client/src/main/java/com/lmax/solana4j/client/jsonrpc/SolanaJsonRpcClientResponse.java b/client/src/main/java/com/lmax/solana4j/client/jsonrpc/SolanaJsonRpcClientResponse.java index 36903355..6ddd47d6 100644 --- a/client/src/main/java/com/lmax/solana4j/client/jsonrpc/SolanaJsonRpcClientResponse.java +++ b/client/src/main/java/com/lmax/solana4j/client/jsonrpc/SolanaJsonRpcClientResponse.java @@ -1,13 +1,12 @@ package com.lmax.solana4j.client.jsonrpc; -import com.lmax.solana4j.client.api.ErrorCode; import com.lmax.solana4j.client.api.SolanaClientError; import com.lmax.solana4j.client.api.SolanaClientResponse; class SolanaJsonRpcClientResponse implements SolanaClientResponse { private final T response; - private final SolanaRecoverableJsonRpcClientError error; + private final SolanaClientError error; SolanaJsonRpcClientResponse(final T response) { @@ -15,10 +14,10 @@ class SolanaJsonRpcClientResponse implements SolanaClientResponse this.error = null; } - SolanaJsonRpcClientResponse(final ErrorCode errorCode, final String errorMessage) + SolanaJsonRpcClientResponse(final SolanaClientError error) { this.response = null; - this.error = new SolanaRecoverableJsonRpcClientError(errorCode, errorMessage); + this.error = error; } @Override diff --git a/client/src/main/java/com/lmax/solana4j/client/jsonrpc/SolanaUnrecoverableJsonRpcClientError.java b/client/src/main/java/com/lmax/solana4j/client/jsonrpc/SolanaUnrecoverableJsonRpcClientError.java deleted file mode 100644 index 4615e956..00000000 --- a/client/src/main/java/com/lmax/solana4j/client/jsonrpc/SolanaUnrecoverableJsonRpcClientError.java +++ /dev/null @@ -1,34 +0,0 @@ -package com.lmax.solana4j.client.jsonrpc; - -import com.lmax.solana4j.client.api.ErrorCode; -import com.lmax.solana4j.client.api.SolanaClientError; - -class SolanaUnrecoverableJsonRpcClientError implements SolanaClientError -{ - private final ErrorCode errorCode; - private final String errorMessage; - - SolanaUnrecoverableJsonRpcClientError(final ErrorCode errorCode, final String errorMessage) - { - this.errorCode = errorCode; - this.errorMessage = errorMessage; - } - - @Override - public ErrorCode getErrorCode() - { - return errorCode; - } - - @Override - public String getErrorMessage() - { - return errorMessage; - } - - @Override - public boolean isRecoverable() - { - return false; - } -} diff --git a/message-encoding/build.gradle b/message-encoding/build.gradle index b5c5a23b..9d11717a 100644 --- a/message-encoding/build.gradle +++ b/message-encoding/build.gradle @@ -73,6 +73,7 @@ dependencies { testSupportImplementation project(path: ':client', configuration: 'default') testSupportImplementation project(path: ':shared', configuration: 'sharedTestSupport') + testSupportImplementation 'org.slf4j:slf4j-api:2.0.9' testSupportImplementation 'com.lmax:simple-dsl:3.0.0' testSupportImplementation 'org.assertj:assertj-core:3.26.3' testSupportImplementation 'org.junit.jupiter:junit-jupiter-api:5.10.2' @@ -94,7 +95,6 @@ dependencies { integrationTestImplementation project(path: ':shared', configuration: 'sharedTestSupport') integrationTestImplementation 'org.junit.jupiter:junit-jupiter-api:5.10.2' - integrationTestImplementation 'junit:junit:4.13.2' integrationTestRuntimeOnly 'com.lmax:simple-dsl:3.0.0' integrationTestRuntimeOnly 'org.testcontainers:testcontainers:1.20.2' diff --git a/message-encoding/src/integration-test/java/com/lmax/solana4j/programs/ComputeBudgetProgramIntegrationTest.java b/message-encoding/src/integration-test/java/com/lmax/solana4j/programs/ComputeBudgetProgramIntegrationTest.java index dd9ca293..ff4265b1 100644 --- a/message-encoding/src/integration-test/java/com/lmax/solana4j/programs/ComputeBudgetProgramIntegrationTest.java +++ b/message-encoding/src/integration-test/java/com/lmax/solana4j/programs/ComputeBudgetProgramIntegrationTest.java @@ -2,8 +2,6 @@ import com.lmax.solana4j.parameterisation.ParameterizedMessageEncodingTest; -import static org.junit.Assert.assertThrows; - public class ComputeBudgetProgramIntegrationTest extends SolanaProgramsIntegrationTestBase { @@ -14,16 +12,16 @@ void shouldSetComputeUnitLimitAndPrice(final String messageEncoding) solana.createKeyPair("payer"); - solana.airdropSol("payer", "1"); + solana.airdropSol("payer", "0.001"); solana.setComputeUnits("300", "10000", "payer", "tx"); solana.verifyTransactionMetadata("tx", "computeUnitsConsumed: 300"); - // transaction is exactly 300 compute units so this should fail - assertThrows(RuntimeException.class, () -> solana.setComputeUnits("299", "10000", "payer")); + // -32002 = transaction simulation failed + solana.setComputeUnits("299", "10000", "payer", "expectedErrorCode: -32002"); - // let's set the compute unit price so high that we don't have enough sol on the payer account - assertThrows(RuntimeException.class, () -> solana.setComputeUnits("300", "100000000000", "payer")); + // -32002 = transaction simulation failed + solana.setComputeUnits("300", "1000000000", "payer", "expectedErrorCode: -32002"); } } diff --git a/message-encoding/src/test-support/java/com/lmax/solana4j/SolanaDriver.java b/message-encoding/src/test-support/java/com/lmax/solana4j/SolanaDriver.java index 20a88c9b..6f8cb3fa 100644 --- a/message-encoding/src/test-support/java/com/lmax/solana4j/SolanaDriver.java +++ b/message-encoding/src/test-support/java/com/lmax/solana4j/SolanaDriver.java @@ -10,6 +10,7 @@ import com.lmax.solana4j.client.api.SolanaClientResponse; import com.lmax.solana4j.client.api.TokenAmount; import com.lmax.solana4j.client.api.TransactionResponse; +import com.lmax.solana4j.client.jsonrpc.SolanaJsonRpcClientException; import com.lmax.solana4j.domain.TestKeyPair; import com.lmax.solana4j.domain.TestPublicKey; import com.lmax.solana4j.domain.TokenProgram; @@ -17,13 +18,21 @@ import com.lmax.solana4j.transaction.LegacyTransactionBlobFactory; import com.lmax.solana4j.transaction.TransactionBlobFactory; import com.lmax.solana4j.transaction.V0TransactionBlobFactory; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import java.time.Duration; import java.util.List; +import java.util.OptionalInt; import java.util.concurrent.locks.LockSupport; import java.util.stream.Collectors; +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; + public class SolanaDriver { + private static final Logger LOGGER = LoggerFactory.getLogger(SolanaDriver.class); + private final SolanaApi solanaApi; private TransactionBlobFactory transactionBlobFactory; @@ -34,35 +43,36 @@ public SolanaDriver(final SolanaApi solanaApi) public String requestAirdrop(final TestPublicKey address, final long amountLamports) { - return retryingClient(solanaApi.requestAirdrop(address.getPublicKeyBase58(), amountLamports)); + return retryClient(() -> solanaApi.requestAirdrop(address.getPublicKeyBase58(), amountLamports)); } public TransactionResponse getTransactionResponse(final String transactionSignature) { - return retryingClient(solanaApi.getTransaction(transactionSignature)); + return retryClient(() -> solanaApi.getTransaction(transactionSignature)); } - public long getSlot() + public Long getSlot() { - return retryingClient(solanaApi.getSlot()); + return retryClient(solanaApi::getSlot); } - public long getBlockHeight() + public Long getBlockHeight() { - return retryingClient(solanaApi.getBlockHeight()); + return retryClient(solanaApi::getBlockHeight); } public AccountInfo getAccountInfo(final TestPublicKey address) { - return retryingClient(solanaApi.getAccountInfo(address.getPublicKeyBase58())); + return retryClient(() -> solanaApi.getAccountInfo(address.getPublicKeyBase58())); } - public String createAddressLookupTable(final ProgramDerivedAddress programDerivedAddress, - final TestKeyPair authority, - final TestKeyPair payer, - final Slot slot) + public String createAddressLookupTable( + final ProgramDerivedAddress programDerivedAddress, + final TestKeyPair authority, + final TestKeyPair payer, + final Slot slot) { - final Blockhash recentBlockhash = retryingClient(solanaApi.getLatestBlockhash()); + final Blockhash recentBlockhash = retryClient(solanaApi::getLatestBlockhash); final String transactionBlob = getTransactionFactory().createAddressLookupTable( programDerivedAddress, @@ -72,7 +82,7 @@ public String createAddressLookupTable(final ProgramDerivedAddress programDerive payer.getSolana4jPublicKey(), List.of(payer, authority)); - return retryingClient(solanaApi.sendTransaction(transactionBlob)); + return retryClient(() -> solanaApi.sendTransaction(transactionBlob)); } public String extendAddressLookupTable(final TestPublicKey addressLookupTable, @@ -81,7 +91,7 @@ public String extendAddressLookupTable(final TestPublicKey addressLookupTable, final List addressesToAdd, final List addressLookupTables) { - final Blockhash recentBlockhash = retryingClient(solanaApi.getLatestBlockhash()); + final Blockhash recentBlockhash = retryClient(solanaApi::getLatestBlockhash); final String transactionBlob = getTransactionFactory().extendAddressLookupTable( addressLookupTable.getSolana4jPublicKey(), @@ -92,7 +102,7 @@ public String extendAddressLookupTable(final TestPublicKey addressLookupTable, List.of(payer, authority), addressLookupTables); - return retryingClient(solanaApi.sendTransaction(transactionBlob)); + return retryClient(() -> solanaApi.sendTransaction(transactionBlob)); } public String createMintAccount( @@ -105,8 +115,8 @@ public String createMintAccount( final int accountSpan, final List addressLookupTables) { - final Blockhash recentBlockhash = retryingClient(solanaApi.getLatestBlockhash()); - final long rentExemption = retryingClient(solanaApi.getMinimumBalanceForRentExemption(accountSpan)); + final Blockhash latestBlockhash = retryClient(solanaApi::getLatestBlockhash); + final long rentExemption = retryClient(() -> solanaApi.getMinimumBalanceForRentExemption(accountSpan)); final String transactionBlob = getTransactionFactory().createMintAccount( tokenProgram, @@ -116,13 +126,13 @@ public String createMintAccount( freezeAuthority.getSolana4jPublicKey(), rentExemption, accountSpan, - Solana.blockhash(recentBlockhash.getBlockhashBase58()), + Solana.blockhash(latestBlockhash.getBlockhashBase58()), payer.getSolana4jPublicKey(), List.of(payer, tokenMint), addressLookupTables ); - return retryingClient(solanaApi.sendTransaction(transactionBlob)); + return retryClient(() -> solanaApi.sendTransaction(transactionBlob)); } public String mintTo( @@ -134,20 +144,20 @@ public String mintTo( final TestKeyPair payer, final List addressLookupTables) { - final Blockhash recentBlockhash = retryingClient(solanaApi.getLatestBlockhash()); + final Blockhash latestBlockhash = retryClient(solanaApi::getLatestBlockhash); final String transactionBlob = getTransactionFactory().mintTo( tokenProgram, tokenMintAddress.getSolana4jPublicKey(), authority.getSolana4jPublicKey(), Solana.destination(to.getSolana4jPublicKey(), amount), - Solana.blockhash(recentBlockhash.getBlockhashBase58()), + Solana.blockhash(latestBlockhash.getBlockhashBase58()), payer.getSolana4jPublicKey(), List.of(payer, authority), addressLookupTables ); - return retryingClient(solanaApi.sendTransaction(transactionBlob)); + return retryClient(() -> solanaApi.sendTransaction(transactionBlob)); } public String createTokenAccount( @@ -159,8 +169,8 @@ public String createTokenAccount( final int accountSpan, final List addressLookupTables) { - final Long rentExemption = retryingClient(solanaApi.getMinimumBalanceForRentExemption(accountSpan)); - final Blockhash blockhash = retryingClient(solanaApi.getLatestBlockhash()); + final Long rentExemption = retryClient(() -> solanaApi.getMinimumBalanceForRentExemption(accountSpan)); + final Blockhash blockhash = retryClient(solanaApi::getLatestBlockhash); final String transactionBlob = getTransactionFactory().createTokenAccount( tokenProgram, @@ -174,17 +184,17 @@ public String createTokenAccount( List.of(payer, account), addressLookupTables); - return retryingClient(solanaApi.sendTransaction(transactionBlob)); + return retryClient(() -> solanaApi.sendTransaction(transactionBlob)); } public long getBalance(final String address) { - return retryingClient(solanaApi.getBalance(address)); + return retryClient(() -> solanaApi.getBalance(address)); } public TokenAmount getTokenBalance(final String address) { - return retryingClient(solanaApi.getTokenAccountBalance(address)); + return retryClient(() -> solanaApi.getTokenAccountBalance(address)); } public String createNonceAccount( @@ -194,8 +204,8 @@ public String createNonceAccount( final int accountSpan, final List addressLookupTables) { - final Long rentExemption = retryingClient(solanaApi.getMinimumBalanceForRentExemption(accountSpan)); - final Blockhash blockhash = retryingClient(solanaApi.getLatestBlockhash()); + final Long rentExemption = retryClient(() -> solanaApi.getMinimumBalanceForRentExemption(accountSpan)); + final Blockhash blockhash = retryClient(solanaApi::getLatestBlockhash); final String transactionBlob = getTransactionFactory().createNonce( nonceAccount.getSolana4jPublicKey(), @@ -207,7 +217,7 @@ public String createNonceAccount( List.of(payer, nonceAccount, nonceAuthority), addressLookupTables); - return retryingClient(solanaApi.sendTransaction(transactionBlob)); + return retryClient(() -> solanaApi.sendTransaction(transactionBlob)); } public String createMultiSigAccount( @@ -219,8 +229,8 @@ public String createMultiSigAccount( final TestKeyPair payer, final List addressLookupTables) { - final Long rentExemption = retryingClient(solanaApi.getMinimumBalanceForRentExemption(accountSpan)); - final Blockhash blockhash = retryingClient(solanaApi.getLatestBlockhash()); + final Long rentExemption = retryClient(() -> solanaApi.getMinimumBalanceForRentExemption(accountSpan)); + final Blockhash blockhash = retryClient(solanaApi::getLatestBlockhash); final String transactionBlob = getTransactionFactory().createMultiSigAccount( tokenProgram, @@ -234,7 +244,7 @@ public String createMultiSigAccount( List.of(payer, account), addressLookupTables); - return retryingClient(solanaApi.sendTransaction(transactionBlob)); + return retryClient(() -> solanaApi.sendTransaction(transactionBlob)); } public String advanceNonce( @@ -243,7 +253,7 @@ public String advanceNonce( final TestKeyPair payer, final List addressLookupTables) { - final Blockhash blockhash = retryingClient(solanaApi.getLatestBlockhash()); + final Blockhash blockhash = retryClient(solanaApi::getLatestBlockhash); final String transactionBlob = getTransactionFactory().advanceNonce( account.getSolana4jPublicKey(), @@ -253,7 +263,7 @@ public String advanceNonce( List.of(payer, authority), addressLookupTables); - return retryingClient(solanaApi.sendTransaction(transactionBlob)); + return retryClient(() -> solanaApi.sendTransaction(transactionBlob)); } public String tokenTransfer( @@ -266,7 +276,7 @@ public String tokenTransfer( final List signers, final List addressLookupTables) { - final Blockhash blockhash = retryingClient(solanaApi.getLatestBlockhash()); + final Blockhash blockhash = retryClient(solanaApi::getLatestBlockhash); final String transactionBlob = getTransactionFactory().tokenTransfer( tokenProgram, @@ -279,7 +289,7 @@ public String tokenTransfer( signers, addressLookupTables); - return retryingClient(solanaApi.sendTransaction(transactionBlob)); + return retryClient(() -> solanaApi.sendTransaction(transactionBlob)); } public String transfer( @@ -289,7 +299,7 @@ public String transfer( final TestKeyPair payer, final List addressLookupTables) { - final Blockhash blockhash = retryingClient(solanaApi.getLatestBlockhash()); + final Blockhash blockhash = retryClient(solanaApi::getLatestBlockhash); final String transactionBlob = getTransactionFactory().solTransfer( from.getSolana4jPublicKey(), @@ -300,7 +310,7 @@ public String transfer( List.of(payer, from), addressLookupTables); - return retryingClient(solanaApi.sendTransaction(transactionBlob)); + return retryClient(() -> solanaApi.sendTransaction(transactionBlob)); } public String createAssociatedTokenAccount( @@ -312,7 +322,7 @@ public String createAssociatedTokenAccount( final TestKeyPair payer, final List addressLookupTables) { - final Blockhash blockhash = retryingClient(solanaApi.getLatestBlockhash()); + final Blockhash blockhash = retryClient(solanaApi::getLatestBlockhash); final String transactionBlob = getTransactionFactory().createAssociatedTokenAccount( tokenProgram, @@ -325,7 +335,7 @@ public String createAssociatedTokenAccount( List.of(payer), addressLookupTables); - return retryingClient(solanaApi.sendTransaction(transactionBlob)); + return retryClient(() -> solanaApi.sendTransaction(transactionBlob)); } public String setTokenAccountAuthority( @@ -338,7 +348,7 @@ public String setTokenAccountAuthority( final List signers, final List addressLookupTables) { - final Blockhash blockhash = retryingClient(solanaApi.getLatestBlockhash()); + final Blockhash blockhash = retryClient(solanaApi::getLatestBlockhash); final String transactionBlob = getTransactionFactory().setTokenAccountAuthority( tokenProgram, @@ -351,12 +361,16 @@ public String setTokenAccountAuthority( signers, addressLookupTables); - return retryingClient(solanaApi.sendTransaction(transactionBlob)); + return retryClient(() -> solanaApi.sendTransaction(transactionBlob)); } - public String setComputeUnits(final int computeUnitLimit, final long computeUnitPrice, final TestKeyPair payer) + public SolanaClientResponse setComputeUnits( + final int computeUnitLimit, + final long computeUnitPrice, + final TestKeyPair payer, + final OptionalInt expectedErrorCode) { - final Blockhash blockhash = retryingClient(solanaApi.getLatestBlockhash()); + final Blockhash blockhash = retryClient(solanaApi::getLatestBlockhash); final String transactionBlob = getTransactionFactory().setComputeUnits( computeUnitLimit, @@ -365,7 +379,7 @@ public String setComputeUnits(final int computeUnitLimit, final long computeUnit payer.getSolana4jPublicKey(), List.of(payer)); - return retryingClient(solanaApi.sendTransaction(transactionBlob)); + return retryClient(() -> solanaApi.sendTransaction(transactionBlob), expectedErrorCode); } public String setBpfUpgradeableProgramUpgradeAuthority( @@ -376,7 +390,7 @@ public String setBpfUpgradeableProgramUpgradeAuthority( final List signers, final List addressLookupTables) { - final Blockhash blockhash = retryingClient(solanaApi.getLatestBlockhash()); + final Blockhash blockhash = retryClient(solanaApi::getLatestBlockhash); final String transactionBlob = getTransactionFactory().setBpfUpgradeableProgramUpgradeAuthority( program, @@ -387,7 +401,7 @@ public String setBpfUpgradeableProgramUpgradeAuthority( signers, addressLookupTables); - return retryingClient(solanaApi.sendTransaction(transactionBlob)); + return retryClient(() -> solanaApi.sendTransaction(transactionBlob)); } public void setMessageEncoding(final String messageEncoding) @@ -415,25 +429,53 @@ private TransactionBlobFactory getTransactionFactory() return transactionBlobFactory; } - private T retryingClient(final SolanaClientResponse response) + private T retryClient(final SolanaClientResponseSupplier supplier) { - for (int i = 0; i < 10; i++) + final SolanaClientResponse solanaClientResponse = getResponseWithRetry(supplier); + + // if no error expected must response is success + assertThat(solanaClientResponse.isSuccess()).isTrue(); + return solanaClientResponse.getResponse(); + } + + private SolanaClientResponse retryClient(final SolanaClientResponseSupplier supplier, final OptionalInt expectedErrorCode) + { + final SolanaClientResponse response = getResponseWithRetry(supplier); + if (expectedErrorCode.isPresent()) { - if (response.isSuccess()) - { - return response.getResponse(); - } - else if (response.getError().isRecoverable()) + assertThat(response.isSuccess()).isFalse(); + } + return response; + } + + private SolanaClientResponse getResponseWithRetry(final SolanaClientResponseSupplier supplier) + { + int retrys = 10; + while (true) + { + try { - LockSupport.parkNanos(100_000); + return supplier.get(); } - else + catch (final SolanaJsonRpcClientException e) { - throw new RuntimeException(String.format("The error was not recoverable so not retrying. The error code was %s, with message %s.", - response.getError().getErrorMessage(), - response.getError().getErrorCode())); + retrys -= 1; + LOGGER.error("An unexpected error occurred.", e); + if (!e.isRecoverable()) + { + throw new RuntimeException("JSON RPC error is unrecoverable.", e); + } + if (retrys == 0) + { + throw new RuntimeException("Exhausted retry attempts, throwing the most recent JSON RPC error.", e); + } + LockSupport.parkNanos(Duration.ofSeconds(2).toNanos()); } } - throw new RuntimeException("Unable to get a response after retry."); + } + + public interface SolanaClientResponseSupplier + { + SolanaClientResponse get() throws SolanaJsonRpcClientException; } } diff --git a/message-encoding/src/test-support/java/com/lmax/solana4j/SolanaNodeDsl.java b/message-encoding/src/test-support/java/com/lmax/solana4j/SolanaNodeDsl.java index 3562913b..7c2a4a5a 100644 --- a/message-encoding/src/test-support/java/com/lmax/solana4j/SolanaNodeDsl.java +++ b/message-encoding/src/test-support/java/com/lmax/solana4j/SolanaNodeDsl.java @@ -12,7 +12,7 @@ import com.lmax.solana4j.assertion.Waiter; import com.lmax.solana4j.client.SolanaClient; import com.lmax.solana4j.client.api.AccountInfo; -import com.lmax.solana4j.client.api.TransactionData; +import com.lmax.solana4j.client.api.SolanaClientResponse; import com.lmax.solana4j.client.api.TransactionResponse; import com.lmax.solana4j.domain.Sol; import com.lmax.solana4j.domain.TestKeyPair; @@ -33,6 +33,7 @@ import java.util.Arrays; import java.util.List; import java.util.Objects; +import java.util.OptionalInt; import java.util.stream.Collectors; import static com.lmax.solana4j.programs.SystemProgram.MINT_ACCOUNT_LENGTH; @@ -415,7 +416,7 @@ public void createMultiSigAccount(final String... args) addressLookupTables ); - Waiter.waitFor(Condition.isNotNull(() -> solanaDriver.getTransactionResponse(transactionSignature).getTransaction())); + Waiter.waitFor(Condition.isNotNull(() -> solanaDriver.getTransactionResponse(transactionSignature))); } public void verifyMultiSigAccount(final String... args) @@ -597,7 +598,7 @@ public void createAssociatedTokenAccount(final String... args) addressLookupTables ); - Waiter.waitFor(Condition.isNotNull(() -> solanaDriver.getTransactionResponse(transactionSignature).getTransaction())); + Waiter.waitFor(Condition.isNotNull(() -> solanaDriver.getTransactionResponse(transactionSignature))); } public void verifyAssociatedTokenAccount(final String... args) @@ -686,7 +687,7 @@ public void setTokenAccountAuthority(final String... args) List.of(payer, tokenAccountOldAuthority), addressLookupTables); - Waiter.waitFor(Condition.isNotNull(() -> solanaDriver.getTransactionResponse(transactionSignature).getTransaction())); + Waiter.waitFor(Condition.isNotNull(() -> solanaDriver.getTransactionResponse(transactionSignature))); } public void setComputeUnits(final String... args) @@ -697,19 +698,37 @@ public void setComputeUnits(final String... args) new RequiredArg("computeUnitPrice"), new RequiredArg("payer"), new OptionalArg("rememberTransactionAs"), - new OptionalArg("expectedError") + new OptionalArg("expectedErrorCode") ); final int computeUnitLimit = params.valueAsInt("computeUnitLimit"); final int computeUnitPrice = params.valueAsInt("computeUnitPrice"); final TestKeyPair payer = testContext.data(TestDataType.TEST_KEY_PAIR).lookup(params.value("payer")); final String rememberTransactionAs = params.value("rememberTransactionAs"); + final OptionalInt expectedErrorCode = params.valuesAsOptionalInt("expectedErrorCode"); - final String transactionSignature = solanaDriver.setComputeUnits(computeUnitLimit, computeUnitPrice, payer); + final SolanaClientResponse solanaClientResponse = solanaDriver.setComputeUnits( + computeUnitLimit, + computeUnitPrice, + payer, + expectedErrorCode); - Waiter.waitFor(Condition.isNotNull(() -> solanaDriver.getTransactionResponse(transactionSignature).getTransaction())); + if (expectedErrorCode.isPresent()) + { + assertThat(solanaClientResponse.isSuccess()).withFailMessage( + "We expected an error but received a successful response from the client." + ).isFalse(); + assertThat(solanaClientResponse.getError().getErrorCode()).isEqualTo(expectedErrorCode.getAsInt()); + } + else + { + assertThat(solanaClientResponse.isSuccess()).withFailMessage( + "We expected a succes but received a an error from the client." + ).isTrue(); + Waiter.waitFor(Condition.isNotNull(() -> solanaDriver.getTransactionResponse(solanaClientResponse.getResponse()))); - testContext.data(TestDataType.TRANSACTION_ID).store(rememberTransactionAs, transactionSignature); + testContext.data(TestDataType.TRANSACTION_ID).store(rememberTransactionAs, solanaClientResponse.getResponse()); + } } public void setBpfUpgradeableProgramUpgradeAuthority(final String... args) @@ -745,7 +764,7 @@ public void setBpfUpgradeableProgramUpgradeAuthority(final String... args) List.of(payer, new TestKeyPair(oldUpgradeAuthorityPublicKey, oldUpgradeAuthorityPrivateKey)), addressLookupTables); - Waiter.waitFor(Condition.isNotNull(() -> solanaDriver.getTransactionResponse(transactionSignature).getTransaction())); + Waiter.waitFor(Condition.isNotNull(() -> solanaDriver.getTransactionResponse(transactionSignature))); } public void verifyBpfUpgradeableAccount(final String... args) @@ -791,9 +810,9 @@ private void waitForSlot(final long slot) Waiter.waitFor(Condition.isTrue(() -> solanaDriver.getSlot() > slot)); } - private Condition transactionFinalized(final String transactionSignature) + private Condition transactionFinalized(final String transactionSignature) { - return Condition.isNotNull(() -> solanaDriver.getTransactionResponse(transactionSignature).getTransaction()); + return Condition.isNotNull(() -> solanaDriver.getTransactionResponse(transactionSignature)); } private AddressLookupTable storeAddressLookupTable(final TestPublicKey lookupTableAddress, final String lookupTableAlias)