Skip to content

Commit 44229fc

Browse files
Merge branch 'master' into ps/chore/upgrade-fuel-core-to-0.44.0
2 parents 0fffb2e + 4e018a5 commit 44229fc

File tree

5 files changed

+193
-27
lines changed

5 files changed

+193
-27
lines changed

.changeset/hungry-webs-join.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"@fuel-ts/account": patch
3+
"@fuel-ts/errors": patch
4+
---
5+
6+
chore: handling for malformed response body

apps/docs/src/guide/errors/index.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -353,3 +353,7 @@ This error occurs when there are duplicate entries for the same asset ID with di
353353

354354
1. The `accountCoinQuantities` parameter contains multiple entries for the same asset ID
355355
2. Each entry specifies a different `changeOutputAccount` for the same asset ID
356+
357+
### `RESPONSE_BODY_EMPTY`
358+
359+
This error occurs when the response from the server has an empty body. The issue will generally lie with the connection setup from your environment and the RPC.

packages/account/src/providers/provider.test.ts

Lines changed: 163 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -45,16 +45,13 @@ import { serializeChain, serializeNodeInfo, serializeProviderCache } from './uti
4545
import type { ProviderCacheJson } from './utils/serialization';
4646

4747
const getCustomFetch =
48-
(expectedOperationName: string, expectedResponse: object) =>
48+
(expectedOperationName: string, expectedBody?: BodyInit | null) =>
4949
async (url: string, options: RequestInit | undefined) => {
5050
const graphqlRequest = JSON.parse(options?.body as string);
5151
const { operationName } = graphqlRequest;
5252

5353
if (operationName === expectedOperationName) {
54-
const responseText = JSON.stringify({
55-
data: expectedResponse,
56-
});
57-
const response = Promise.resolve(new Response(responseText, options));
54+
const response = Promise.resolve(new Response(expectedBody, options));
5855

5956
return response;
6057
}
@@ -244,13 +241,16 @@ describe('Provider', () => {
244241
using launched = await setupTestProviderAndWallets();
245242
const { provider } = launched;
246243

247-
const mockProvider = new Provider(provider.url, {
248-
fetch: getCustomFetch('getTransaction', {
244+
const expectedBody = JSON.stringify({
245+
data: {
249246
transaction: {
250247
id: '0x1234567890abcdef',
251248
rawPayload: MOCK_TX_UNKNOWN_RAW_PAYLOAD, // Unknown transaction type
252249
},
253-
}),
250+
},
251+
});
252+
const mockProvider = new Provider(provider.url, {
253+
fetch: getCustomFetch('getTransaction', expectedBody),
254254
});
255255

256256
// Spy on console.warn
@@ -273,9 +273,8 @@ describe('Provider', () => {
273273
using launched = await setupTestProviderAndWallets();
274274
const { provider: nodeProvider } = launched;
275275

276-
// Create a mock provider with custom getTransactions operation
277-
const mockProvider = new Provider(nodeProvider.url, {
278-
fetch: getCustomFetch('getTransactions', {
276+
const expectedBody = JSON.stringify({
277+
data: {
279278
transactions: {
280279
edges: [
281280
{
@@ -298,7 +297,12 @@ describe('Provider', () => {
298297
endCursor: null,
299298
},
300299
},
301-
}),
300+
},
301+
});
302+
303+
// Create a mock provider with custom getTransactions operation
304+
const mockProvider = new Provider(nodeProvider.url, {
305+
fetch: getCustomFetch('getTransactions', expectedBody),
302306
});
303307

304308
// Spy on console.warn
@@ -476,7 +480,10 @@ describe('Provider', () => {
476480
const providerUrl = providerForUrl.url;
477481

478482
const provider = new Provider(providerUrl, {
479-
fetch: getCustomFetch('getVersion', { nodeInfo: { nodeVersion: '0.30.0' } }),
483+
fetch: getCustomFetch(
484+
'getVersion',
485+
JSON.stringify({ data: { nodeInfo: { nodeVersion: '0.30.0' } } })
486+
),
480487
});
481488

482489
expect(await provider.getVersion()).toEqual('0.30.0');
@@ -515,7 +522,10 @@ describe('Provider', () => {
515522
fetchChainAndNodeInfo.mockRestore();
516523

517524
await provider.connect(providerUrl, {
518-
fetch: getCustomFetch('getVersion', { nodeInfo: { nodeVersion: '0.30.0' } }),
525+
fetch: getCustomFetch(
526+
'getVersion',
527+
JSON.stringify({ data: { nodeInfo: { nodeVersion: '0.30.0' } } })
528+
),
519529
});
520530

521531
expect(await provider.getVersion()).toEqual('0.30.0');
@@ -851,10 +861,10 @@ describe('Provider', () => {
851861

852862
const provider = new Provider(nodeProvider.url, {
853863
fetch: async (url, options) =>
854-
getCustomFetch('getMessageProof', { messageProof: MESSAGE_PROOF_RAW_RESPONSE })(
855-
url,
856-
options
857-
),
864+
getCustomFetch(
865+
'getMessageProof',
866+
JSON.stringify({ data: { messageProof: MESSAGE_PROOF_RAW_RESPONSE } })
867+
)(url, options),
858868
});
859869

860870
const transactionId = '0x79c54219a5c910979e5e4c2728df163fa654a1fe03843e6af59daa2c3fcd42ea';
@@ -874,7 +884,10 @@ describe('Provider', () => {
874884

875885
const provider = new Provider(nodeProvider.url, {
876886
fetch: async (url, options) =>
877-
getCustomFetch('getMessageStatus', { messageStatus: messageStatusResponse })(url, options),
887+
getCustomFetch(
888+
'getMessageStatus',
889+
JSON.stringify({ data: { messageStatus: messageStatusResponse } })
890+
)(url, options),
878891
});
879892
const messageStatus = await provider.getMessageStatus(
880893
'0x0000000000000000000000000000000000000000000000000000000000000008'
@@ -2656,6 +2669,137 @@ describe('Provider', () => {
26562669
vi.restoreAllMocks();
26572670
});
26582671

2672+
describe('Non-populated response body', () => {
2673+
// Reset back to default
2674+
afterAll(() => {
2675+
Provider.ENABLE_RPC_CONSISTENCY = true;
2676+
});
2677+
2678+
describe('ENABLE_RPC_CONSISTENCY = false', () => {
2679+
beforeAll(() => {
2680+
Provider.ENABLE_RPC_CONSISTENCY = false;
2681+
});
2682+
2683+
it(
2684+
'should not fall over if the response body is null [non-subscription]',
2685+
{ timeout: 20000 },
2686+
async () => {
2687+
using launched = await setupTestProviderAndWallets();
2688+
const {
2689+
provider: { url },
2690+
} = launched;
2691+
const provider = new Provider(url, {
2692+
fetch: getCustomFetch('estimateGasPrice', null),
2693+
});
2694+
2695+
await expectToThrowFuelError(() => provider.estimateGasPrice(10), {
2696+
code: ErrorCode.RESPONSE_BODY_EMPTY,
2697+
message: 'The response from the server is missing the body',
2698+
metadata: {
2699+
timestamp: expect.any(String),
2700+
request: expect.any(Object),
2701+
response: expect.any(Object),
2702+
},
2703+
});
2704+
}
2705+
);
2706+
2707+
it(
2708+
'should not fall over if the response body is null [subscription]',
2709+
{ timeout: 20000 },
2710+
async () => {
2711+
using launched = await setupTestProviderAndWallets();
2712+
const {
2713+
provider: { url },
2714+
} = launched;
2715+
const provider = new Provider(url, {
2716+
fetch: getCustomFetch('submitAndAwaitStatus', null),
2717+
});
2718+
2719+
const request = new ScriptTransactionRequest();
2720+
2721+
await expectToThrowFuelError(
2722+
() =>
2723+
provider.operations.submitAndAwaitStatus({
2724+
encodedTransaction: hexlify(request.toTransactionBytes()),
2725+
}),
2726+
{
2727+
code: ErrorCode.RESPONSE_BODY_EMPTY,
2728+
message: 'The response from the server is missing the body',
2729+
metadata: {
2730+
timestamp: expect.any(String),
2731+
request: expect.any(Object),
2732+
response: expect.any(Object),
2733+
},
2734+
}
2735+
);
2736+
}
2737+
);
2738+
});
2739+
2740+
describe('ENABLE_RPC_CONSISTENCY = true', () => {
2741+
beforeAll(() => {
2742+
Provider.ENABLE_RPC_CONSISTENCY = true;
2743+
});
2744+
2745+
it(
2746+
'should not fall over if the response body is null [non-subscription]',
2747+
{ timeout: 20000 },
2748+
async () => {
2749+
using launched = await setupTestProviderAndWallets();
2750+
const {
2751+
provider: { url },
2752+
} = launched;
2753+
const provider = new Provider(url, {
2754+
fetch: getCustomFetch('estimateGasPrice', null),
2755+
});
2756+
2757+
await expectToThrowFuelError(() => provider.estimateGasPrice(10), {
2758+
code: ErrorCode.RESPONSE_BODY_EMPTY,
2759+
message: 'The response from the server is missing the body',
2760+
metadata: {
2761+
timestamp: expect.any(String),
2762+
request: expect.any(Object),
2763+
response: expect.any(Object),
2764+
},
2765+
});
2766+
}
2767+
);
2768+
2769+
it(
2770+
'should not fall over if the response body is null [subscription]',
2771+
{ timeout: 20000 },
2772+
async () => {
2773+
using launched = await setupTestProviderAndWallets();
2774+
const {
2775+
provider: { url },
2776+
} = launched;
2777+
const provider = new Provider(url, {
2778+
fetch: getCustomFetch('submitAndAwaitStatus', null),
2779+
});
2780+
2781+
const request = new ScriptTransactionRequest();
2782+
2783+
await expectToThrowFuelError(
2784+
() =>
2785+
provider.operations.submitAndAwaitStatus({
2786+
encodedTransaction: hexlify(request.toTransactionBytes()),
2787+
}),
2788+
{
2789+
code: ErrorCode.RESPONSE_BODY_EMPTY,
2790+
message: 'The response from the server is missing the body',
2791+
metadata: {
2792+
timestamp: expect.any(String),
2793+
request: expect.any(Object),
2794+
response: expect.any(Object),
2795+
},
2796+
}
2797+
);
2798+
}
2799+
);
2800+
});
2801+
});
2802+
26592803
describe('Waiting for transaction statuses', () => {
26602804
const PreconfirmationSuccessStatus = 'PreconfirmationSuccessStatus';
26612805
const SuccessStatus = 'SuccessStatus';

packages/account/src/providers/provider.ts

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -566,7 +566,16 @@ export default class Provider {
566566
Provider.applyBlockHeight(fullRequest, url);
567567
}
568568

569-
return Provider.fetchAndProcessBlockHeight(url, fullRequest, options);
569+
const response = await Provider.fetchAndProcessBlockHeight(url, fullRequest, options);
570+
if (response.body === null) {
571+
throw new FuelError(
572+
ErrorCode.RESPONSE_BODY_EMPTY,
573+
'The response from the server is missing the body',
574+
{ timestamp: new Date().toISOString(), request, response }
575+
);
576+
}
577+
578+
return response;
570579
}, retryOptions);
571580
}
572581

@@ -607,14 +616,16 @@ export default class Provider {
607616
};
608617

609618
for (let retriesLeft = retryOptions.maxRetries; retriesLeft > 0; --retriesLeft) {
610-
const { extensions } = await parseGraphqlResponse({
611-
response,
612-
isSubscription: url.endsWith('-sub'),
613-
});
614-
Provider.setCurrentBlockHeight(url, extensions?.current_fuel_block_height);
619+
if (response.body) {
620+
const { extensions } = await parseGraphqlResponse({
621+
response,
622+
isSubscription: url.endsWith('-sub'),
623+
});
624+
Provider.setCurrentBlockHeight(url, extensions?.current_fuel_block_height);
615625

616-
if (!extensions?.fuel_block_height_precondition_failed) {
617-
break;
626+
if (!extensions?.fuel_block_height_precondition_failed) {
627+
break;
628+
}
618629
}
619630

620631
const retryAttempt = retryOptions.maxRetries - retriesLeft + 1;

packages/errors/src/error-codes.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ export enum ErrorCode {
3737
CONNECTION_REFUSED = 'connection-refused',
3838
INVALID_URL = 'invalid-url',
3939
UNSUPPORTED_FEATURE = 'unsupported-feature',
40+
RESPONSE_BODY_EMPTY = 'response-body-empty',
4041

4142
// wallet
4243
INVALID_PUBLIC_KEY = 'invalid-public-key',

0 commit comments

Comments
 (0)