Skip to content

Commit 4474fab

Browse files
authored
Merge pull request #1005 from input-output-hk/feat/lw-9128-conway-era-compatible-sdk-clients
Feat/lw 9128 conway era compatible sdk clients
2 parents 02a0eff + 267acf5 commit 4474fab

File tree

39 files changed

+937
-276
lines changed

39 files changed

+937
-276
lines changed

compose/common.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ x-provider-server-environment: &provider-server-environment
6666
SERVICE_NAMES: ${SERVICE_NAMES:-asset,chain-history,handle,network-info,rewards,stake-pool,tx-submit,utxo}
6767
SUBMIT_API_URL: ${SUBMIT_API_URL:-http://cardano-submit-api:8090/}
6868
USE_BLOCKFROST: ${USE_BLOCKFROST:-false}
69-
USE_SUBMIT_API: ${USE_SUBMIT_API:-true}
69+
USE_SUBMIT_API: ${USE_SUBMIT_API:-false}
7070

7171
x-sdk-environment: &sdk-environment
7272
LOGGER_MIN_SEVERITY: ${LOGGER_MIN_SEVERITY:-info}

packages/cardano-services-client/src/TxSubmitProvider/TxSubmitApiProvider.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { Cardano, ProviderError, ProviderFailure, SubmitTxArgs, TxBodyCBOR, TxSubmitProvider } from '@cardano-sdk/core';
22
import { Logger } from 'ts-log';
33
import { hexStringToBuffer } from '@cardano-sdk/util';
4+
import { mapCardanoTxSubmitError } from './cardanoTxSubmitErrorMapper';
45
import axios, { AxiosInstance } from 'axios';
56

67
export class TxSubmitApiProvider implements TxSubmitProvider {
@@ -42,7 +43,7 @@ export class TxSubmitApiProvider implements TxSubmitProvider {
4243

4344
if (typeof status === 'number' && status >= 400 && status < 500) this.#healthStatus = true;
4445

45-
throw new ProviderError(ProviderFailure.BadRequest, null, data as string);
46+
throw new ProviderError(ProviderFailure.BadRequest, mapCardanoTxSubmitError(data), data as string);
4647
}
4748

4849
throw new ProviderError(ProviderFailure.Unknown, error, 'submitting tx');
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
/* eslint-disable wrap-regex */
2+
import { TxSubmissionError, TxSubmissionErrorCode } from '@cardano-sdk/core';
3+
4+
export const mapCardanoTxSubmitError = (errorData: unknown): TxSubmissionError | null => {
5+
if (typeof errorData === 'string') {
6+
if (/outsideofvalidity/i.test(errorData)) {
7+
return new TxSubmissionError(TxSubmissionErrorCode.OutsideOfValidityInterval, null, errorData);
8+
}
9+
if (/valuenotconserved/i.test(errorData)) {
10+
return new TxSubmissionError(TxSubmissionErrorCode.ValueNotConserved, null, errorData);
11+
}
12+
if (/nonadacollateral/i.test(errorData)) {
13+
return new TxSubmissionError(TxSubmissionErrorCode.NonAdaCollateral, null, errorData);
14+
}
15+
if (/incompletewithdrawals/i.test(errorData)) {
16+
return new TxSubmissionError(TxSubmissionErrorCode.IncompleteWithdrawals, null, errorData);
17+
}
18+
}
19+
return null;
20+
};

packages/cardano-services-client/src/TxSubmitProvider/txSubmitHttpProvider.ts

Lines changed: 43 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,16 @@
11
/* eslint-disable @typescript-eslint/no-explicit-any */
22
import {
3-
CardanoNodeErrors,
3+
CardanoNodeUtil,
4+
GeneralCardanoNodeError,
5+
GeneralCardanoNodeErrorCode,
46
HandleOwnerChangeError,
57
HttpProviderConfigPaths,
68
ProviderError,
79
ProviderFailure,
8-
TxSubmitProvider
10+
TxSubmissionError,
11+
TxSubmissionErrorCode,
12+
TxSubmitProvider,
13+
reasonToProviderFailure
914
} from '@cardano-sdk/core';
1015
import { CreateHttpProviderConfig, createHttpProvider } from '../HttpProvider';
1116
import { apiVersion } from '../version';
@@ -17,25 +22,43 @@ const paths: HttpProviderConfigPaths<TxSubmitProvider> = {
1722
submitTx: '/submit'
1823
};
1924

20-
const toTxSubmissionError = (error: any): CardanoNodeErrors.TxSubmissionError | null => {
21-
if (typeof error === 'object' && typeof error?.name === 'string' && typeof error?.message === 'string') {
22-
const rawError = error as CardanoNodeErrors.TxSubmissionError;
25+
/**
26+
* Takes an unknown error param.
27+
* Returns an instance of TxSubmissionError or GeneralCardanoNodeError from the error if the error
28+
* is an object, with an undefined or valid TxSubmissionError or GeneralCardanoNodeError code, and
29+
* a string message.
30+
* Returns null otherwise.
31+
*/
32+
const toTxSubmissionError = (error: any): TxSubmissionError | GeneralCardanoNodeError | null => {
33+
if (typeof error === 'object' && error !== null && typeof error?.message === 'string') {
34+
if (CardanoNodeUtil.isTxSubmissionErrorCode(error.code)) {
35+
return Object.setPrototypeOf(error, TxSubmissionError.prototype);
36+
}
2337

24-
const txSubmissionErrorName = rawError.name as keyof typeof CardanoNodeErrors.TxSubmissionErrors;
25-
const ErrorClass = CardanoNodeErrors.TxSubmissionErrors[txSubmissionErrorName];
26-
if (ErrorClass) {
27-
Object.setPrototypeOf(error, ErrorClass.prototype);
28-
return error;
38+
if (CardanoNodeUtil.isGeneralCardanoNodeErrorCode(error.code)) {
39+
return error instanceof GeneralCardanoNodeError
40+
? error
41+
: new GeneralCardanoNodeError(error.code, error.data || null, error.message);
2942
}
30-
if (rawError.name === CardanoNodeErrors.UnknownTxSubmissionError.name) {
31-
Object.setPrototypeOf(error, CardanoNodeErrors.UnknownTxSubmissionError.prototype);
32-
return error;
43+
44+
if (error.code === undefined || error.code === null) {
45+
return new GeneralCardanoNodeError(GeneralCardanoNodeErrorCode.Unknown, error?.data || null, error.message);
3346
}
34-
return new CardanoNodeErrors.UnknownTxSubmissionError(error);
3547
}
3648
return null;
3749
};
3850

51+
const codeToProviderFailure = (code: GeneralCardanoNodeErrorCode | TxSubmissionErrorCode) => {
52+
switch (code) {
53+
case GeneralCardanoNodeErrorCode.Unknown:
54+
return ProviderFailure.Unknown;
55+
case GeneralCardanoNodeErrorCode.ServerNotReady:
56+
return ProviderFailure.ServerUnavailable;
57+
default:
58+
return ProviderFailure.BadRequest;
59+
}
60+
};
61+
3962
/**
4063
* Connect to a Cardano Services HttpServer instance with the service available
4164
*
@@ -55,19 +78,20 @@ export const txSubmitHttpProvider = (config: CreateHttpProviderConfig<TxSubmitPr
5578
}
5679
case 'submitTx': {
5780
if (typeof error === 'object' && typeof error.innerError === 'object') {
81+
// Ogmios errors have inner error. Parse that to get the real error
5882
const txSubmissionError = toTxSubmissionError(error.innerError);
5983
if (txSubmissionError) {
60-
const failure =
61-
txSubmissionError instanceof CardanoNodeErrors.UnknownTxSubmissionError
62-
? ProviderFailure.Unknown
63-
: ProviderFailure.BadRequest;
64-
throw new ProviderError(failure, txSubmissionError);
84+
throw new ProviderError(codeToProviderFailure(txSubmissionError.code), txSubmissionError);
6585
}
6686

6787
if (error.name === 'HandleOwnerChangeError') {
6888
Object.setPrototypeOf(error, HandleOwnerChangeError);
6989
}
7090
}
91+
// No inner error. Use the outer reason to determine the error type.
92+
if (error.reason && typeof error.reason === 'string') {
93+
throw new ProviderError(reasonToProviderFailure(error.reason), error);
94+
}
7195
}
7296
}
7397
throw new ProviderError(ProviderFailure.Unknown, error);
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import { CardanoNodeUtil, TxSubmissionErrorCode } from '@cardano-sdk/core';
2+
import { mapCardanoTxSubmitError } from '../../src/TxSubmitProvider/cardanoTxSubmitErrorMapper';
3+
4+
describe('mapCardanoTxSubmitError', () => {
5+
describe('stringish errors', () => {
6+
it('can map OutsideOfValidityIntervalError to TxSubmissionErrorCode.OutsideOfValidityInterval', () => {
7+
const errorData = 'blah blah OutsideOfValidity blah blah';
8+
expect(CardanoNodeUtil.isOutsideOfValidityIntervalError(mapCardanoTxSubmitError(errorData))).toBeTruthy();
9+
});
10+
11+
it('can map ValueNotConservedError to TxSubmissionErrorCode.ValueNotConserved', () => {
12+
const errorData = 'blah blah ValueNotConserved blah blah';
13+
expect(CardanoNodeUtil.isValueNotConservedError(mapCardanoTxSubmitError(errorData))).toBeTruthy();
14+
});
15+
16+
it('can map NonAdaCollateralError to TxSubmissionErrorCode.NonAdaCollateral', () => {
17+
const errorData = 'blah blah NonAdaCollateral blah blah';
18+
expect(mapCardanoTxSubmitError(errorData)?.code).toEqual(TxSubmissionErrorCode.NonAdaCollateral);
19+
});
20+
21+
it('can map IncompleteWithdrawalsError to TxSubmissionErrorCode.IncompleteWithdrawals', () => {
22+
const errorData = 'blah blah IncompleteWithdrawals blah blah';
23+
expect(CardanoNodeUtil.isIncompleteWithdrawalsError(mapCardanoTxSubmitError(errorData))).toBeTruthy();
24+
});
25+
26+
it('returns null for unknown errors', () => {
27+
const errorData = 'blah blah unknown blah blah';
28+
expect(mapCardanoTxSubmitError(errorData)).toBeNull();
29+
});
30+
});
31+
});

packages/cardano-services-client/test/TxSubmitProvider/txSubmitHttpProvider.test.ts

Lines changed: 35 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,11 @@
1-
import { CardanoNodeErrors, ProviderError, ProviderFailure } from '@cardano-sdk/core';
1+
import {
2+
GeneralCardanoNodeError,
3+
GeneralCardanoNodeErrorCode,
4+
ProviderError,
5+
ProviderFailure,
6+
TxSubmissionError,
7+
TxSubmissionErrorCode
8+
} from '@cardano-sdk/core';
29
import { bufferToHexString } from '@cardano-sdk/util';
310
import { config } from '../util';
411
import { handleProviderMocks } from '@cardano-sdk/util-dev';
@@ -53,19 +60,24 @@ describe('txSubmitHttpProvider', () => {
5360

5461
describe('errors', () => {
5562
const testError =
56-
(bodyError: Error, providerFailure: ProviderFailure, providerErrorType: unknown) => async () => {
63+
(
64+
bodyError: Error | null,
65+
providerFailure: ProviderFailure,
66+
providerErrorType: unknown,
67+
reason?: ProviderFailure
68+
) =>
69+
async () => {
5770
try {
5871
axiosMock.onPost().replyOnce(() => {
59-
throw handleProviderMocks.axiosError(bodyError);
72+
throw handleProviderMocks.axiosError(bodyError, reason);
6073
});
6174
const provider = txSubmitHttpProvider(config);
6275
await provider.submitTx({ signedTransaction: emptyUintArrayAsHexString });
6376
throw new Error('Expected to throw');
6477
} catch (error) {
6578
if (error instanceof ProviderError) {
6679
expect(error.reason).toBe(providerFailure);
67-
const innerError = error.innerError as CardanoNodeErrors.TxSubmissionError;
68-
expect(innerError).toBeInstanceOf(providerErrorType);
80+
expect(error.innerError).toBeInstanceOf(providerErrorType);
6981
} else {
7082
throw new TypeError('Expected ProviderError');
7183
}
@@ -75,30 +87,42 @@ describe('txSubmitHttpProvider', () => {
7587
it(
7688
'rehydrates errors',
7789
testError(
78-
new CardanoNodeErrors.TxSubmissionErrors.BadInputsError({ badInputs: [] }),
90+
new TxSubmissionError(TxSubmissionErrorCode.EmptyInputSet, null, ''),
7991
ProviderFailure.BadRequest,
80-
CardanoNodeErrors.TxSubmissionErrors.BadInputsError
92+
TxSubmissionError
8193
)
8294
);
8395

8496
it(
8597
'maps unrecognized errors to UnknownTxSubmissionError',
86-
testError(new Error('Unknown error'), ProviderFailure.Unknown, CardanoNodeErrors.UnknownTxSubmissionError)
98+
testError(new Error('Unknown error'), ProviderFailure.Unknown, GeneralCardanoNodeError)
99+
);
100+
101+
it(
102+
'uses reason to determine ProviderFailure type when innerError is missing',
103+
testError(null, ProviderFailure.BadRequest, ProviderError)
104+
);
105+
106+
it(
107+
'non-providerError reason is mapped to ProviderFailure.Unknown when innerError is missing',
108+
testError(null, ProviderFailure.Unknown, ProviderError, 'invalidReason' as ProviderFailure)
87109
);
88110

89111
it('does not re-wrap UnknownTxSubmissionError', async () => {
90112
expect.assertions(3);
91113
axiosMock.onPost().replyOnce(() => {
92-
throw handleProviderMocks.axiosError(new CardanoNodeErrors.UnknownTxSubmissionError('Unknown error'));
114+
throw handleProviderMocks.axiosError(
115+
new GeneralCardanoNodeError(GeneralCardanoNodeErrorCode.Unknown, null, '')
116+
);
93117
});
94118
const provider = txSubmitHttpProvider(config);
95119
try {
96120
await provider.submitTx({ signedTransaction: emptyUintArrayAsHexString });
97121
// eslint-disable-next-line @typescript-eslint/no-explicit-any
98122
} catch (error: any) {
99123
expect(error).toBeInstanceOf(ProviderError);
100-
expect(error.innerError).toBeInstanceOf(CardanoNodeErrors.UnknownTxSubmissionError);
101-
expect(error.innerError.innerError.name).not.toBe(CardanoNodeErrors.UnknownTxSubmissionError.name);
124+
expect(error.innerError).toBeInstanceOf(GeneralCardanoNodeError);
125+
expect(error.innerError.innerError).toBeUndefined();
102126
}
103127
});
104128
});

packages/cardano-services/src/TxSubmit/TxSubmitHttpService.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import {
2-
Cardano,
2+
CardanoNodeUtil,
33
ProviderError,
44
ProviderFailure,
55
TxSubmitProvider,
@@ -47,7 +47,7 @@ export class TxSubmitHttpService extends HttpService {
4747
if (!isHealthy) {
4848
return HttpServer.sendJSON(res, new ProviderError(ProviderFailure.Unhealthy, firstError), 503);
4949
}
50-
if (Cardano.util.asTxSubmissionError(error)) {
50+
if (CardanoNodeUtil.asTxSubmissionError(error)) {
5151
return HttpServer.sendJSON(res, new ProviderError(ProviderFailure.BadRequest, firstError), 400);
5252
}
5353
return HttpServer.sendJSON(res, new ProviderError(ProviderFailure.Unknown, firstError), 500);

packages/cardano-services/test/TxSubmit/TxSubmitHttpService.test.ts

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
/* eslint-disable @typescript-eslint/no-explicit-any */
22
import { APPLICATION_JSON, CONTENT_TYPE, HttpServer, HttpServerConfig, TxSubmitHttpService } from '../../src';
3-
import { CardanoNodeErrors, ProviderError, TxSubmitProvider } from '@cardano-sdk/core';
43
import { CreateHttpProviderConfig, txSubmitHttpProvider } from '@cardano-sdk/cardano-services-client';
54
import { FATAL, createLogger } from 'bunyan';
65
import { OgmiosTxSubmitProvider } from '@cardano-sdk/ogmios';
6+
import { ProviderError, TxSubmissionError, TxSubmissionErrorCode, TxSubmitProvider } from '@cardano-sdk/core';
77
import { bufferToHexString, fromSerializableObject } from '@cardano-sdk/util';
88
import { getPort } from 'get-port-please';
99
import { logger } from '@cardano-sdk/util-dev';
@@ -172,7 +172,13 @@ describe('TxSubmitHttpService', () => {
172172

173173
describe('healthy but failing submission', () => {
174174
describe('/submit', () => {
175-
const stubErrors = [new CardanoNodeErrors.TxSubmissionErrors.BadInputsError({ badInputs: [] })];
175+
const stubErrors = [
176+
new TxSubmissionError(
177+
TxSubmissionErrorCode.NonEmptyRewardAccount,
178+
{ nonEmptyRewardAccountBalance: { lovelace: 10n } },
179+
'Bad inputs'
180+
)
181+
];
176182

177183
beforeAll(async () => {
178184
txSubmitProvider = txSubmitProviderMock(
@@ -193,14 +199,15 @@ describe('TxSubmitHttpService', () => {
193199
});
194200

195201
it('rehydrates errors when used with TxSubmitHttpProvider', async () => {
196-
expect.assertions(2);
202+
expect.assertions(3);
197203
const clientProvider = txSubmitHttpProvider(clientConfig);
198204
try {
199205
await clientProvider.submitTx({ signedTransaction: emptyUintArrayAsHexString });
200206
} catch (error: any) {
201207
if (error instanceof ProviderError) {
202-
const innerError = error.innerError as CardanoNodeErrors.TxSubmissionError;
203-
expect(innerError).toBeInstanceOf(CardanoNodeErrors.TxSubmissionErrors.BadInputsError);
208+
const innerError = error.innerError as TxSubmissionError;
209+
expect(innerError).toBeInstanceOf(TxSubmissionError);
210+
expect(innerError.code).toBe(stubErrors[0].code);
204211
expect(innerError.message).toBe(stubErrors[0].message);
205212
}
206213
}

packages/core/src/Cardano/types/Certificate.ts

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,9 @@ export enum CertificateType {
2727
ResignCommitteeCold = 'ResignCommitteeColdCertificate',
2828
RegisterDelegateRepresentative = 'RegisterDelegateRepresentativeCertificate',
2929
UnregisterDelegateRepresentative = 'UnregisterDelegateRepresentativeCertificate',
30-
UpdateDelegateRepresentative = 'UpdateDelegateRepresentativeCertificate'
30+
UpdateDelegateRepresentative = 'UpdateDelegateRepresentativeCertificate',
31+
RegisterCcHotKey = 'RegisterCcHotKeyCertificate',
32+
RetireCc = 'RetireCcCertificate'
3133
}
3234

3335
// Conway Certificates
@@ -106,6 +108,14 @@ export interface UpdateDelegateRepresentativeCertificate {
106108
anchor: Anchor | null;
107109
}
108110

111+
export interface RegisterCcHotKeyCertificate {
112+
__typename: CertificateType.RegisterCcHotKey;
113+
}
114+
115+
export interface RetireCcCertificate {
116+
__typename: CertificateType.RetireCc;
117+
}
118+
109119
/** To be deprecated in the Era after conway replaced by <NewStakeAddressCertificate> */
110120
export interface StakeAddressCertificate {
111121
__typename: CertificateType.StakeRegistration | CertificateType.StakeDeregistration;
@@ -173,7 +183,9 @@ export type Certificate =
173183
| ResignCommitteeColdCertificate
174184
| RegisterDelegateRepresentativeCertificate
175185
| UnRegisterDelegateRepresentativeCertificate
176-
| UpdateDelegateRepresentativeCertificate;
186+
| UpdateDelegateRepresentativeCertificate
187+
| RegisterCcHotKeyCertificate
188+
| RetireCcCertificate;
177189

178190
/**
179191
* Creates a stake key registration certificate from a given reward account.

0 commit comments

Comments
 (0)