diff --git a/concordium-sdk/src/main/java/com/concordium/sdk/ClientV2.java b/concordium-sdk/src/main/java/com/concordium/sdk/ClientV2.java index b78aa3b02..e6b49d7eb 100644 --- a/concordium-sdk/src/main/java/com/concordium/sdk/ClientV2.java +++ b/concordium-sdk/src/main/java/com/concordium/sdk/ClientV2.java @@ -140,6 +140,7 @@ public Iterator getBlocks(int timeoutMillis) { * * @param timeoutMillis Timeout for the request in Milliseconds. * @return {@link Iterator} + * */ public Iterator getFinalizedBlocks(int timeoutMillis) { val grpcOutput = this.server(timeoutMillis) @@ -308,6 +309,9 @@ public AccountNonce getNextAccountSequenceNumber(AccountAddress address) { * * @param transactionHash The transaction {@link Hash} * @return The {@link BlockItemStatus} + * @throws io.grpc.StatusRuntimeException with {@link io.grpc.Status.Code}:
    + *
  • {@link io.grpc.Status#NOT_FOUND} if the transaction is not known to the node. + *
*/ public BlockItemStatus getBlockItemStatus(Hash transactionHash) { val grpcOutput = this.server() @@ -889,27 +893,73 @@ public ImmutableList getWinningBakersEpoch(EpochQuery epochQuery) * @param timeoutMillis the number of milliseconds to wait before throwing an exception. * @return {@link FinalizedBlockItem} of the transaction. * @throws io.grpc.StatusRuntimeException with {@link io.grpc.Status.Code}:
    - *
  • {@link io.grpc.Status#DEADLINE_EXCEEDED} if the timeout is exceeded. - *
  • {@link io.grpc.Status#NOT_FOUND} if the transaction is not known to the node. + *
  • {@link io.grpc.Status#NOT_FOUND} if the transaction is not known to the node after the timeout is exceeded. + *
  • {@link io.grpc.Status#DEADLINE_EXCEEDED} if the transaction is not finalized after the timeout is exceeded. *
*/ public FinalizedBlockItem waitUntilFinalized(Hash transactionHash, int timeoutMillis) { - BlockItemStatus status = this.getBlockItemStatus(transactionHash); - if (status.getStatus() == Status.FINALIZED) { - return status.getFinalizedBlockItem().get(); + try { + BlockItemStatus status = this.getBlockItemStatus(transactionHash); + if (status.getStatus() == Status.FINALIZED) { + return status.getFinalizedBlockItem().get(); + } + } catch (io.grpc.StatusRuntimeException e) { + // If the transaction is not found we wait for it. + ignoreExceptionIfCode(e, io.grpc.Status.Code.NOT_FOUND); } - Iterator finalizedBlockStream = this.getFinalizedBlocks(timeoutMillis); - while (finalizedBlockStream.hasNext()) { - finalizedBlockStream.next(); - // We check if the transaction is finalized every time a new block is finalized. - BlockItemStatus newStatus = this.getBlockItemStatus(transactionHash); - if (newStatus.getStatus() == Status.FINALIZED) { - return newStatus.getFinalizedBlockItem().get(); + return listenForFinalizedTransaction(transactionHash, timeoutMillis); + } + + /** + * Helper function for {@link ClientV2#waitUntilFinalized(Hash, int)}. Listens for finalized blocks and returns the {@link FinalizedBlockItem} of the transaction when available. + * @param transactionHash the {@link Hash} of the transaction to wait for. + * @param timeoutMillis the number of milliseconds to wait before throwing an exception. + * @return {@link FinalizedBlockItem} of the transaction. + * @throws io.grpc.StatusRuntimeException with {@link io.grpc.Status.Code}:
    + *
  • {@link io.grpc.Status#NOT_FOUND} if the transaction is not known to the node after the timeout is exceeded. + *
  • {@link io.grpc.Status#DEADLINE_EXCEEDED} if the transaction is not finalized after the timeout is exceeded. + *
+ */ + private FinalizedBlockItem listenForFinalizedTransaction(Hash transactionHash, int timeoutMillis) { + io.grpc.StatusRuntimeException notFound = null; + try { + Iterator finalizedBlockStream = this.getFinalizedBlocks(timeoutMillis); + while (finalizedBlockStream.hasNext()) { + finalizedBlockStream.next(); + // We check if the transaction is finalized every time a new block is finalized. + try { + BlockItemStatus newStatus = this.getBlockItemStatus(transactionHash); + if (newStatus.getStatus() == Status.FINALIZED) { + return newStatus.getFinalizedBlockItem().get(); + } + } catch (io.grpc.StatusRuntimeException e) { + ignoreExceptionIfCode(e, io.grpc.Status.Code.NOT_FOUND); + notFound = e; // If the transaction is still not found after the timeout is exceeded, we throw this exception. + } + } + } catch (io.grpc.StatusRuntimeException e) { + ignoreExceptionIfCode(e, io.grpc.Status.Code.DEADLINE_EXCEEDED); + if (notFound != null ) { // The block was not found. + throw notFound; } + throw e; } throw new IllegalStateException("No more finalized blocks"); // This should never happen as finalized blocks keep being streamed. } + /** + * Helper method for allowing {@link io.grpc.StatusRuntimeException} with specified {@link io.grpc.Status.Code} without throwing the exception. + * @param e the {@link io.grpc.StatusRuntimeException}. + * @param code the {@link io.grpc.Status.Code} to allow. + * @throws io.grpc.StatusRuntimeException if the code of the exception does not match the provided code. + */ + private void ignoreExceptionIfCode(io.grpc.StatusRuntimeException e, io.grpc.Status.Code code) { + if (e.getStatus().getCode().equals(code)) { + return; + } + throw e; + } + /** * Get a {@link QueriesGrpc.QueriesBlockingStub} with a timeout * The timeout is the one specified in via the {@link Connection} object used to