Skip to content

Commit

Permalink
Add a pollTransaction variation to retry getTransactions (#1092)
Browse files Browse the repository at this point in the history
  • Loading branch information
Shaptic authored Oct 28, 2024
1 parent c75fe78 commit 36fa2af
Show file tree
Hide file tree
Showing 3 changed files with 87 additions and 6 deletions.
7 changes: 6 additions & 1 deletion src/rpc/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,12 @@
export * from "./api";

// soroban-client classes to expose
export { RpcServer as Server, Durability } from "./server";
export {
RpcServer as Server,
BasicSleepStrategy,
LinearSleepStrategy,
Durability
} from "./server";
export { default as AxiosClient } from "./axios";
export { parseRawSimulation, parseRawEvents } from "./parsers";
export * from "./transaction";
Expand Down
82 changes: 77 additions & 5 deletions src/rpc/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import {
parseRawTransactions,
parseTransactionInfo,
} from './parsers';
import { Utils } from '../utils';

/**
* Default transaction submission timeout for RPC requests, in milliseconds
Expand Down Expand Up @@ -84,6 +85,11 @@ export namespace RpcServer {
limit?: number;
}

export interface PollingOptions {
attempts?: number;
sleepStrategy?: SleepStrategy;
}

export interface ResourceLeeway {
cpuInstructions: number;
}
Expand All @@ -95,6 +101,22 @@ export namespace RpcServer {
}
}

const DEFAULT_GET_TRANSACTION_TIMEOUT: number = 30;

/// A strategy that will sleep 1 second each time
export const BasicSleepStrategy: SleepStrategy =
(_iter: number) => 1000;

/// A strategy that will sleep 1 second longer on each attempt
export const LinearSleepStrategy: SleepStrategy =
(iter: number) => 1000 * iter;

/**
* A function that returns the number of *milliseconds* to sleep
* on a given `iter`ation.
*/
export type SleepStrategy = (iter: number) => number;

function findCreatedAccountSequenceInTransactionMeta(
meta: xdr.TransactionMeta
): string {
Expand Down Expand Up @@ -453,17 +475,67 @@ export class RpcServer {
);
}


/**
* Poll for a particular transaction with certain parameters.
*
* After submitting a transaction, clients can use this to poll for
* transaction completion and return a definitive state of success or failure.
*
* @param {string} hash the transaction you're polling for
* @param {number} [opts.attempts] (optional) the number of attempts to make
* before returning the last-seen status. By default or on invalid inputs,
* try 5 times.
* @param {SleepStrategy} [opts.sleepStrategy] (optional) the amount of time
* to wait for between each attempt. By default, sleep for 1 second between
* each attempt.
*
* @return {Promise<Api.GetTransactionsResponse>} the response after a "found"
* response (which may be success or failure) or the last response obtained
* after polling the maximum number of specified attempts.
*
* @example
* const h = "c4515e3bdc0897f21cc5dbec8c82cf0a936d4741cb74a8e158eb51b9fb00411a";
* const txStatus = await server.pollTransaction(h, {
* attempts: 100, // I'm a maniac
* sleepStrategy: rpc.LinearSleepStrategy
* }); // this will take 5,050 seconds to complete
*/
public async pollTransaction(
hash: string,
opts?: RpcServer.PollingOptions
): Promise<Api.GetTransactionResponse> {
let maxAttempts: number = (
(opts?.attempts ?? 0) < 1
? DEFAULT_GET_TRANSACTION_TIMEOUT
: (opts?.attempts ?? DEFAULT_GET_TRANSACTION_TIMEOUT)
); // "positive and defined user value or default"

let foundInfo: Api.GetTransactionResponse;
for (let attempt = 1; attempt < maxAttempts; attempt++) {
foundInfo = await this.getTransaction(hash);
if (foundInfo.status !== Api.GetTransactionStatus.NOT_FOUND) {
return foundInfo;
}

await Utils.sleep((opts?.sleepStrategy ?? BasicSleepStrategy)(attempt));
}

return foundInfo!;
}

/**
* Fetch the details of a submitted transaction.
*
* After submitting a transaction, clients should poll this to tell when the
* transaction has completed.
*
* @param {string} hash Hex-encoded hash of the transaction to check
* @returns {Promise<Api.GetTransactionResponse>} The status,
* result, and other details about the transaction
* @returns {Promise<Api.GetTransactionResponse>} The status, result, and
* other details about the transaction
*
* @see {@link https://developers.stellar.org/docs/data/rpc/api-reference/methods/getTransaction | getTransaction docs}
* @see
* {@link https://developers.stellar.org/docs/data/rpc/api-reference/methods/getTransaction | getTransaction docs}
*
* @example
* const transactionHash = "c4515e3bdc0897f21cc5dbec8c82cf0a936d4741cb74a8e158eb51b9fb00411a";
Expand All @@ -480,8 +552,8 @@ export class RpcServer {
): Promise<Api.GetTransactionResponse> {
return this._getTransaction(hash).then((raw) => {
const foundInfo: Omit<
Api.GetSuccessfulTransactionResponse,
keyof Api.GetMissingTransactionResponse
Api.GetSuccessfulTransactionResponse,
keyof Api.GetMissingTransactionResponse
> = {} as any;

if (raw.status !== Api.GetTransactionStatus.NOT_FOUND) {
Expand Down
4 changes: 4 additions & 0 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,8 @@ export class Utils {
now <= Number.parseInt(maxTime, 10) + gracePeriod
);
}

static sleep(ms: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, ms));
}
}

0 comments on commit 36fa2af

Please sign in to comment.