@@ -5,7 +5,10 @@ import {
5
5
GeneralCardanoNodeErrorCode ,
6
6
Milliseconds ,
7
7
StateQueryError ,
8
- StateQueryErrorCode
8
+ StateQueryErrorCode ,
9
+ TxCBOR ,
10
+ TxSubmissionError ,
11
+ TxSubmissionErrorCode
9
12
} from '@cardano-sdk/core' ;
10
13
import { Connection , InteractionContext , Mirror , createConnectionObject , safeJSON } from '@cardano-ogmios/client' ;
11
14
import { HEALTH_RESPONSE_BODY } from '../mocks/util' ;
@@ -16,11 +19,12 @@ import {
16
19
MockedChainSynchronization ,
17
20
MockedLedgerStateQueryClient ,
18
21
MockedSocket ,
22
+ MockedTransactionSubmission ,
19
23
ogmiosEraSummaries
20
24
} from './util' ;
21
- import { NextBlockResponse , RollForward } from '@cardano-ogmios/schema' ;
25
+ import { NextBlockResponse , RollForward , SubmitTransactionFailureEraMismatch } from '@cardano-ogmios/schema' ;
22
26
import { OgmiosObservableCardanoNode } from '../../src' ;
23
- import { combineLatest , delay as delayEmission , firstValueFrom , mergeMap , of } from 'rxjs' ;
27
+ import { combineLatest , delay as delayEmission , firstValueFrom , mergeMap , of , toArray } from 'rxjs' ;
24
28
import { generateRandomHexString , logger } from '@cardano-sdk/util-dev' ;
25
29
import { mockGenesisShelley , mockShelleyBlock } from '../ogmiosToCore/testData' ;
26
30
import delay from 'delay' ;
@@ -30,6 +34,9 @@ jest.mock('@cardano-ogmios/client', () => {
30
34
return {
31
35
...original ,
32
36
ChainSynchronization : { } ,
37
+ TransactionSubmission : {
38
+ submitTransaction : jest . fn ( )
39
+ } ,
33
40
createInteractionContext : jest . fn ( ) ,
34
41
createLedgerStateQueryClient : jest . fn ( ) ,
35
42
getServerHealth : jest . fn ( )
@@ -42,6 +49,7 @@ describe('ObservableOgmiosCardanoNode', () => {
42
49
let getServerHealth : MockGetServerHealth ;
43
50
let socket : MockedSocket ;
44
51
let chainSynchronization : MockedChainSynchronization ;
52
+ let TransactionSubmission : MockedTransactionSubmission ;
45
53
let createInteractionContext : MockCreateInteractionContext ;
46
54
47
55
const tip = {
@@ -56,7 +64,8 @@ describe('ObservableOgmiosCardanoNode', () => {
56
64
createInteractionContext,
57
65
createLedgerStateQueryClient,
58
66
getServerHealth,
59
- ChainSynchronization : chainSynchronization
67
+ ChainSynchronization : chainSynchronization ,
68
+ TransactionSubmission
60
69
} = require ( '@cardano-ogmios/client' ) ) ;
61
70
ledgerStateQueryClient = {
62
71
eraSummaries : jest . fn ( ) as MockedLedgerStateQueryClient [ 'eraSummaries' ] ,
@@ -104,6 +113,7 @@ describe('ObservableOgmiosCardanoNode', () => {
104
113
createInteractionContext . mockReset ( ) ;
105
114
createLedgerStateQueryClient . mockReset ( ) ;
106
115
getServerHealth . mockReset ( ) ;
116
+ TransactionSubmission . submitTransaction . mockReset ( ) ;
107
117
} ) ;
108
118
109
119
describe ( 'LSQs on QueryUnavailableInCurrentEra' , ( ) => {
@@ -155,7 +165,6 @@ describe('ObservableOgmiosCardanoNode', () => {
155
165
} ) ;
156
166
} ) ;
157
167
158
- // TODO: this passes when run individually but not when running entire test suite
159
168
it ( 'opaquely reconnects when connection is refused' , async ( ) => {
160
169
createInteractionContext . mockRejectedValueOnce ( { name : 'WebSocketClosed' } ) ;
161
170
const node = new OgmiosObservableCardanoNode ( { connectionConfig$ : of ( connection ) } , { logger } ) ;
@@ -222,4 +231,66 @@ describe('ObservableOgmiosCardanoNode', () => {
222
231
expect ( result . ok ) . toBe ( false ) ;
223
232
} ) ;
224
233
} ) ;
234
+
235
+ describe ( 'submitTx' , ( ) => {
236
+ let node : OgmiosObservableCardanoNode ;
237
+ const submitTxMaxRetries = 2 ;
238
+
239
+ beforeEach ( ( ) => {
240
+ node = new OgmiosObservableCardanoNode (
241
+ {
242
+ connectionConfig$ : of ( connection ) ,
243
+ submitTxQueryRetryConfig : { initialInterval : 1 , maxRetries : submitTxMaxRetries }
244
+ } ,
245
+ { logger }
246
+ ) ;
247
+ } ) ;
248
+
249
+ describe ( 'successful submission' , ( ) => {
250
+ it ( 'emits transaction id and completes' , async ( ) => {
251
+ TransactionSubmission . submitTransaction . mockResolvedValueOnce ( 'id' ) ;
252
+ await expect ( firstValueFrom ( node . submitTx ( 'cbor' as TxCBOR ) . pipe ( toArray ( ) ) ) ) . resolves . toEqual ( [ 'id' ] ) ;
253
+ expect ( TransactionSubmission . submitTransaction ) . toBeCalledTimes ( 1 ) ;
254
+ } ) ;
255
+ } ) ;
256
+
257
+ describe ( 'submission error' , ( ) => {
258
+ it ( 'maps error to core type' , async ( ) => {
259
+ TransactionSubmission . submitTransaction . mockRejectedValueOnce ( {
260
+ code : 3005 ,
261
+ data : { ledgerEra : 'shelley' , queryEra : 'alonzo' } ,
262
+ message : 'Era mismatch'
263
+ } as SubmitTransactionFailureEraMismatch ) ;
264
+ await expect ( firstValueFrom ( node . submitTx ( 'cbor' as TxCBOR ) ) ) . rejects . toThrowError (
265
+ expect . objectContaining ( {
266
+ code : TxSubmissionErrorCode . EraMismatch ,
267
+ name : TxSubmissionError . name
268
+ } )
269
+ ) ;
270
+ expect ( TransactionSubmission . submitTransaction ) . toBeCalledTimes ( 1 ) ;
271
+ } ) ;
272
+ } ) ;
273
+
274
+ describe ( 'connection error' , ( ) => {
275
+ it ( 'attempts to resubmit opaquely' , async ( ) => {
276
+ TransactionSubmission . submitTransaction
277
+ . mockRejectedValueOnce ( { code : 'ECONNREFUSED' } )
278
+ . mockResolvedValueOnce ( 'id' ) ;
279
+ await expect ( firstValueFrom ( node . submitTx ( 'cbor' as TxCBOR ) ) ) . resolves . toBe ( 'id' ) ;
280
+ expect ( TransactionSubmission . submitTransaction ) . toBeCalledTimes ( 2 ) ;
281
+ } ) ;
282
+
283
+ it ( 'rejects after maxRetries attempts to submit' , async ( ) => {
284
+ const error = { code : 'ECONNREFUSED' } ;
285
+ TransactionSubmission . submitTransaction . mockRejectedValue ( error ) ;
286
+
287
+ await expect ( firstValueFrom ( node . submitTx ( 'cbor' as TxCBOR ) ) ) . rejects . toThrowError (
288
+ expect . objectContaining ( {
289
+ code : GeneralCardanoNodeErrorCode . ConnectionFailure
290
+ } )
291
+ ) ;
292
+ expect ( TransactionSubmission . submitTransaction ) . toBeCalledTimes ( submitTxMaxRetries + 1 ) ;
293
+ } ) ;
294
+ } ) ;
295
+ } ) ;
225
296
} ) ;
0 commit comments