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 132x 132x 132x 132x 132x 132x 132x 132x 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(); } } |