Skip to content

Commit 62010d2

Browse files
authored
Merge pull request #1606 from input-output-hk/fix/base-wallet-now-rejects-txs-outside-validity-interval
fix(wallet): base wallet will now immediately throw if tx is outside validity interval
2 parents 81782c0 + ac35a1f commit 62010d2

File tree

3 files changed

+95
-0
lines changed

3 files changed

+95
-0
lines changed

packages/wallet/src/Wallets/BaseWallet.ts

+31
Original file line numberDiff line numberDiff line change
@@ -65,9 +65,14 @@ import {
6565
EpochInfo,
6666
EraSummary,
6767
HandleProvider,
68+
OutsideOfValidityIntervalData,
69+
ProviderError,
70+
ProviderFailure,
6871
RewardAccountInfoProvider,
6972
RewardsProvider,
7073
Serialization,
74+
TxSubmissionError,
75+
TxSubmissionErrorCode,
7176
TxSubmitProvider,
7277
UtxoProvider
7378
} from '@cardano-sdk/core';
@@ -697,6 +702,32 @@ export class BaseWallet implements ObservableWallet {
697702
{ mightBeAlreadySubmitted }: SubmitTxOptions = {}
698703
): Promise<Cardano.TransactionId> {
699704
this.#logger.debug(`Submitting transaction ${outgoingTx.id}`);
705+
706+
// TODO: Workaround while we resolve LW-12394
707+
const { validityInterval } = outgoingTx.body;
708+
if (validityInterval?.invalidHereafter) {
709+
const slot = await firstValueFrom(this.tip$.pipe(map((tip) => tip.slot)));
710+
711+
if (slot >= validityInterval?.invalidHereafter) {
712+
const data: OutsideOfValidityIntervalData = {
713+
currentSlot: slot,
714+
validityInterval: {
715+
invalidBefore: validityInterval?.invalidBefore,
716+
invalidHereafter: validityInterval?.invalidHereafter
717+
}
718+
};
719+
720+
throw new ProviderError(
721+
ProviderFailure.BadRequest,
722+
new TxSubmissionError(
723+
TxSubmissionErrorCode.OutsideOfValidityInterval,
724+
data,
725+
'Not submitting transaction due to validity interval'
726+
)
727+
);
728+
}
729+
}
730+
700731
this.#newTransactions.submitting$.next(outgoingTx);
701732
try {
702733
await this.txSubmitProvider.submitTx({

packages/wallet/test/PersonalWallet/methods.test.ts

+13
Original file line numberDiff line numberDiff line change
@@ -470,6 +470,19 @@ describe('BaseWallet methods', () => {
470470
expect(await txPending).toEqual(outgoingTx);
471471
});
472472

473+
it('throws if transaction is outside validity interval', async () => {
474+
const draftTx = await wallet.initializeTx(props);
475+
draftTx.body.validityInterval = {
476+
invalidHereafter: Cardano.Slot(1_000_000)
477+
};
478+
const tx = await wallet.finalizeTx({ tx: draftTx });
479+
480+
await expect(wallet.submitTx(tx)).rejects.toThrow();
481+
482+
const txInFlight = await firstValueFrom(wallet.transactions.outgoing.inFlight$);
483+
expect(txInFlight).toEqual([]);
484+
});
485+
473486
it('does not re-serialize the transaction to compute transaction id', async () => {
474487
// This transaction produces a different ID when round-tripping serialisation
475488
const cbor = Serialization.TxCBOR(

packages/wallet/test/services/TransactionsTracker.test.ts

+51
Original file line numberDiff line numberDiff line change
@@ -907,6 +907,57 @@ describe('TransactionsTracker', () => {
907907
});
908908
});
909909

910+
// TODO: Will be useful for LW-12394 investigation
911+
it('emits timeout for transactions outside of the validity interval as failed$', async () => {
912+
const outgoingTx = toOutgoingTx(queryTransactionsResult.pageResults[0]);
913+
914+
createTestScheduler().run(({ cold, hot, expectObservable }) => {
915+
const tip$ = hot<Cardano.Tip>('-a---|', {
916+
a: {
917+
blockNo: Cardano.BlockNo(1),
918+
hash: '' as Cardano.BlockId,
919+
slot: Cardano.Slot(outgoingTx.body.validityInterval!.invalidHereafter! * 2)
920+
}
921+
});
922+
const failedTx = { ...outgoingTx, id: 'x' as Cardano.TransactionId };
923+
const submitting$ = cold('-a---|', { a: failedTx });
924+
const pending$ = cold('-----|', { a: failedTx });
925+
const transactionsSource$ = cold<Cardano.HydratedTx[]>('a--b-|', { a: [], b: [queryTransactionsResult.pageResults[0]] });
926+
const failedToSubmit$ = hot<FailedTx>('-----|', { a: { ...failedTx, reason: TransactionFailure.FailedToSubmit } });
927+
const signed$ = hot<WitnessedTx>('----|', {});
928+
const transactionsTracker = createTransactionsTracker(
929+
{
930+
addresses$,
931+
chainHistoryProvider,
932+
historicalTransactionsFetchLimit,
933+
inFlightTransactionsStore,
934+
logger,
935+
newTransactions: {
936+
failedToSubmit$,
937+
pending$,
938+
signed$,
939+
submitting$
940+
},
941+
retryBackoffConfig,
942+
signedTransactionsStore,
943+
tip$,
944+
transactionsHistoryStore: transactionsStore
945+
},
946+
{
947+
rollback$: NEVER,
948+
transactionsSource$
949+
}
950+
);
951+
expectObservable(transactionsTracker.outgoing.submitting$).toBe('-a---|', { a: failedTx });
952+
expectObservable(transactionsTracker.outgoing.pending$).toBe('-----|');
953+
expectObservable(transactionsTracker.outgoing.inFlight$).toBe('a(bc)|', { a: [], b: [failedTx], c: [] });
954+
expectObservable(transactionsTracker.outgoing.onChain$).toBe('-----|', { a: [failedTx] });
955+
expectObservable(transactionsTracker.outgoing.failed$).toBe('-a---|', {
956+
a: { reason: TransactionFailure.Timeout, ...failedTx }
957+
});
958+
});
959+
});
960+
910961
it('emits at all relevant observable properties on transaction that failed to submit and merges reemit failures', async () => {
911962
const outgoingTx = toOutgoingTx(queryTransactionsResult.pageResults[0]);
912963
const outgoingTxReemit = toOutgoingTx(queryTransactionsResult.pageResults[1]);

0 commit comments

Comments
 (0)