Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 | 42x 42x 42x 42x 42x 131x 131x 131x 131x 131x 131x 131x 131x 25x 25x 29x 1x 1x 28x 25x 26x 9x 1x | /* eslint-disable @typescript-eslint/no-explicit-any */
import {
Cardano,
CardanoNodeUtil,
HealthCheckResponse,
OutsideOfValidityIntervalData,
ProviderError,
ProviderFailure,
Serialization,
SubmitTxArgs,
TxSubmissionError,
TxSubmissionErrorCode,
TxSubmitProvider
} from '@cardano-sdk/core';
import { ConnectionStatus, ConnectionStatusTracker } from './util';
import { Observable, combineLatest, filter, firstValueFrom, from, mergeMap, take, tap } from 'rxjs';
import { RetryBackoffConfig, retryBackoff } from 'backoff-rxjs';
export interface RetryingTxSubmitProviderProps {
retryBackoffConfig: RetryBackoffConfig;
}
export type TipSlot = Pick<Cardano.Tip, 'slot'>;
export interface RetryingTxSubmitProviderDependencies {
txSubmitProvider: TxSubmitProvider;
tip$: Observable<TipSlot>;
connectionStatus$: ConnectionStatusTracker;
}
/**
* Wraps a `TxSubmitProvider` to enchance it's `submitTx` with the following functionality:
* - Immediately rejects if network tip is already >= `ValidityInterval.invalidHereafter`
* - Awaits for the following conditions before submitting:
* - Network tip is ahead of tx body `ValidityInterval.invalidBefore`.
* - Connection status is 'up'.
* - Re-submits transactions that failed to submit due to connection or recoverable provider issue.
*/
export class SmartTxSubmitProvider implements TxSubmitProvider {
readonly #txSubmitProvider: TxSubmitProvider;
readonly #tip$: Observable<TipSlot>;
readonly #retryBackoffConfig: RetryBackoffConfig;
readonly #connectionStatus$: ConnectionStatusTracker;
constructor(
{ retryBackoffConfig }: RetryingTxSubmitProviderProps,
{ connectionStatus$, tip$, txSubmitProvider }: RetryingTxSubmitProviderDependencies
) {
this.#txSubmitProvider = txSubmitProvider;
this.#tip$ = tip$;
this.#connectionStatus$ = connectionStatus$;
this.#retryBackoffConfig = retryBackoffConfig;
}
submitTx(args: SubmitTxArgs): Promise<void> {
const {
body: { validityInterval }
} = Serialization.deserializeTx(args.signedTransaction);
const onlineAndWithinValidityInterval$ = combineLatest([this.#connectionStatus$, this.#tip$]).pipe(
tap(([_, { slot }]) => {
if (slot >= (validityInterval?.invalidHereafter || Number.POSITIVE_INFINITY)) {
const data: OutsideOfValidityIntervalData = {
currentSlot: slot,
validityInterval: {
invalidBefore: validityInterval?.invalidBefore,
invalidHereafter: validityInterval?.invalidHereafter
}
};
throw new ProviderError(
ProviderFailure.BadRequest,
new TxSubmissionError(
TxSubmissionErrorCode.OutsideOfValidityInterval,
data,
'Not submitting transaction due to validity interval'
)
);
}
}),
filter(
([connectionStatus, { slot }]) =>
connectionStatus === ConnectionStatus.up && slot >= (validityInterval?.invalidBefore || 0)
),
take(1)
);
return firstValueFrom(
onlineAndWithinValidityInterval$.pipe(
mergeMap(() => from(this.#txSubmitProvider.submitTx(args))),
retryBackoff({
...this.#retryBackoffConfig,
shouldRetry: (error) =>
CardanoNodeUtil.isProviderError(error) &&
[ProviderFailure.Unhealthy, ProviderFailure.ConnectionFailure].includes(error.reason)
})
)
);
}
healthCheck(): Promise<HealthCheckResponse> {
return this.#txSubmitProvider.healthCheck();
}
}
|