From f90c0d69b0575c523d19481eed26495877db624f Mon Sep 17 00:00:00 2001 From: nedsalk Date: Tue, 20 Aug 2024 08:25:22 +0200 Subject: [PATCH 01/12] implement until blocker is solved --- .changeset/famous-pans-press.md | 5 +++++ .../src/providers/fuel-graphql-subscriber.ts | 17 ++++++++++++----- packages/account/src/providers/provider.ts | 11 ++++++----- .../transaction-response.ts | 19 ++++++++++++++----- 4 files changed, 37 insertions(+), 15 deletions(-) create mode 100644 .changeset/famous-pans-press.md diff --git a/.changeset/famous-pans-press.md b/.changeset/famous-pans-press.md new file mode 100644 index 00000000000..c167bbc6c04 --- /dev/null +++ b/.changeset/famous-pans-press.md @@ -0,0 +1,5 @@ +--- +"@fuel-ts/account": minor +--- + +feat!: read malleable fields from transaction status on subscription diff --git a/packages/account/src/providers/fuel-graphql-subscriber.ts b/packages/account/src/providers/fuel-graphql-subscriber.ts index 031a75433b3..5ada84b9356 100644 --- a/packages/account/src/providers/fuel-graphql-subscriber.ts +++ b/packages/account/src/providers/fuel-graphql-subscriber.ts @@ -29,7 +29,16 @@ export class FuelGraphqlSubscriber implements AsyncIterator { }); // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - return new FuelGraphqlSubscriber(response.body!.getReader()); + const [errorReader, resultReader] = response.body!.tee().map((stream) => stream.getReader()); + + /** + * If the node threw an error, read it and throw it to the user + * Else just discard the response and return the subscriber below, + * which will have that same response via `resultReader` + */ + await new FuelGraphqlSubscriber(errorReader).next(); + + return new FuelGraphqlSubscriber(resultReader); } private events: Array<{ data: unknown; errors?: { message: string }[] }> = []; @@ -94,10 +103,8 @@ export class FuelGraphqlSubscriber implements AsyncIterator { /** * Gets called when `break` is called in a `for-await-of` loop. */ - async return(): Promise> { - await this.stream.cancel(); - this.stream.releaseLock(); - return { done: true, value: undefined }; + return(): Promise> { + return Promise.resolve({ done: true, value: undefined }); } [Symbol.asyncIterator](): AsyncIterator { diff --git a/packages/account/src/providers/provider.ts b/packages/account/src/providers/provider.ts index c19359886b6..d96c28d2e6e 100644 --- a/packages/account/src/providers/provider.ts +++ b/packages/account/src/providers/provider.ts @@ -761,13 +761,14 @@ Supported fuel-core version: ${supportedVersion}.` if (isTransactionTypeScript(transactionRequest)) { abis = transactionRequest.abis; } + const subscription = await this.operations.submitAndAwait({ encodedTransaction }); - const { - submit: { id: transactionId }, - } = await this.operations.submit({ encodedTransaction }); - this.#cacheInputs(transactionRequest.inputs, transactionId); + this.#cacheInputs( + transactionRequest.inputs, + transactionRequest.getTransactionId(this.getChainId()) + ); - return new TransactionResponse(transactionRequest, this, abis); + return new TransactionResponse(transactionRequest, this, abis, subscription); } /** diff --git a/packages/account/src/providers/transaction-response/transaction-response.ts b/packages/account/src/providers/transaction-response/transaction-response.ts index 93878735add..1ce9a639f9d 100644 --- a/packages/account/src/providers/transaction-response/transaction-response.ts +++ b/packages/account/src/providers/transaction-response/transaction-response.ts @@ -30,6 +30,7 @@ import { arrayify, assertUnreachable } from '@fuel-ts/utils'; import type { GqlMalleableTransactionFieldsFragment, GqlStatusChangeSubscription, + GqlSubmitAndAwaitSubscription, } from '../__generated__/operations'; import type Provider from '../provider'; import type { JsonAbisFromAllCalls, TransactionRequest } from '../transaction-request'; @@ -141,7 +142,12 @@ export class TransactionResponse { * @param tx - The transaction ID or TransactionRequest. * @param provider - The provider. */ - constructor(tx: string | TransactionRequest, provider: Provider, abis?: JsonAbisFromAllCalls) { + constructor( + tx: string | TransactionRequest, + provider: Provider, + abis?: JsonAbisFromAllCalls, + private submitTxSubscription?: AsyncIterable + ) { this.id = typeof tx === 'string' ? tx : tx.getTransactionId(provider.getChainId()); this.provider = provider; @@ -319,11 +325,14 @@ export class TransactionResponse { return; } - const subscription = await this.provider.operations.statusChange({ - transactionId: this.id, - }); + const subscription = + this.submitTxSubscription ?? + (await this.provider.operations.statusChange({ + transactionId: this.id, + })); - for await (const { statusChange } of subscription) { + for await (const sub of subscription) { + const statusChange = 'statusChange' in sub ? sub.statusChange : sub.submitAndAwait; this.status = statusChange; if (statusChange.type === 'SqueezedOutStatus') { this.unsetResourceCache(); From fa22b6b4e256fb91c2ef62bfdc9833f785d8c55e Mon Sep 17 00:00:00 2001 From: nedsalk Date: Mon, 2 Sep 2024 12:42:10 +0200 Subject: [PATCH 02/12] applied fuel-core 0.35.0 --- internal/fuel-core/VERSION | 2 +- .../src/providers/fuel-core-schema.graphql | 32 +++++++++++++++++++ .../account/src/providers/operations.graphql | 6 ++++ packages/account/src/providers/provider.ts | 10 ++++-- .../transaction-response.ts | 6 ++-- .../versions/src/lib/getBuiltinVersions.ts | 2 +- 6 files changed, 51 insertions(+), 7 deletions(-) diff --git a/internal/fuel-core/VERSION b/internal/fuel-core/VERSION index 85e60ed180c..7b52f5e5178 100644 --- a/internal/fuel-core/VERSION +++ b/internal/fuel-core/VERSION @@ -1 +1 @@ -0.34.0 +0.35.0 diff --git a/packages/account/src/providers/fuel-core-schema.graphql b/packages/account/src/providers/fuel-core-schema.graphql index 946ed03d501..1566fd9a206 100644 --- a/packages/account/src/providers/fuel-core-schema.graphql +++ b/packages/account/src/providers/fuel-core-schema.graphql @@ -1098,6 +1098,9 @@ type Query { """ id: RelayedTransactionId! ): RelayedTransactionStatus + consensusParameters(version: Int!): ConsensusParameters! + stateTransitionBytecodeByVersion(version: Int!): StateTransitionBytecode + stateTransitionBytecodeByRoot(root: HexString!): StateTransitionBytecode! } type Receipt { @@ -1219,6 +1222,11 @@ type SqueezedOutStatus { reason: String! } +type StateTransitionBytecode { + root: HexString! + bytecode: UploadedBytecode! +} + type StateTransitionPurpose { root: Bytes32! } @@ -1253,6 +1261,13 @@ type Subscription { Submits transaction to the `TxPool` and await either confirmation or failure. """ submitAndAwait(tx: HexString!): TransactionStatus! + + """ + Submits the transaction to the `TxPool` and returns a stream of events. + Compared to the `submitAndAwait`, the stream also contains ` + SubmittedStatus` as an intermediate state. + """ + submitAndAwaitStatus(tx: HexString!): TransactionStatus! } type SuccessStatus { @@ -1375,6 +1390,23 @@ scalar U64 union UpgradePurpose = ConsensusParametersPurpose | StateTransitionPurpose +type UploadedBytecode { + """ + Combined bytecode of all uploaded subsections. + """ + bytecode: HexString! + + """ + Number of uploaded subsections (if incomplete). + """ + uploadedSubsectionsNumber: Int + + """ + Indicates if the bytecode upload is complete. + """ + completed: Boolean! +} + scalar UtxoId type VariableOutput { diff --git a/packages/account/src/providers/operations.graphql b/packages/account/src/providers/operations.graphql index c40006dbef1..bdb45784fb1 100644 --- a/packages/account/src/providers/operations.graphql +++ b/packages/account/src/providers/operations.graphql @@ -903,6 +903,12 @@ subscription submitAndAwait($encodedTransaction: HexString!) { } } +subscription submitAndAwaitStatus($encodedTransaction: HexString!) { + submitAndAwaitStatus(tx: $encodedTransaction) { + ...transactionStatusSubscriptionFragment + } +} + subscription statusChange($transactionId: TransactionId!) { statusChange(id: $transactionId) { ...transactionStatusSubscriptionFragment diff --git a/packages/account/src/providers/provider.ts b/packages/account/src/providers/provider.ts index 5ca5982df5f..e447807514b 100644 --- a/packages/account/src/providers/provider.ts +++ b/packages/account/src/providers/provider.ts @@ -369,13 +369,19 @@ type NodeInfoCache = Record; type Operations = ReturnType; -type SdkOperations = Omit & { +type SdkOperations = Omit< + Operations, + 'submitAndAwait' | 'statusChange' | 'submitAndAwaitStatus' +> & { submitAndAwait: ( ...args: Parameters ) => Promise>; statusChange: ( ...args: Parameters ) => Promise>; + submitAndAwaitStatus: ( + ...args: Parameters + ) => Promise>; }; /** @@ -761,7 +767,7 @@ Supported fuel-core version: ${supportedVersion}.` if (isTransactionTypeScript(transactionRequest)) { abis = transactionRequest.abis; } - const subscription = await this.operations.submitAndAwait({ encodedTransaction }); + const subscription = await this.operations.submitAndAwaitStatus({ encodedTransaction }); this.#cacheInputs( transactionRequest.inputs, diff --git a/packages/account/src/providers/transaction-response/transaction-response.ts b/packages/account/src/providers/transaction-response/transaction-response.ts index 1ce9a639f9d..845d41a7263 100644 --- a/packages/account/src/providers/transaction-response/transaction-response.ts +++ b/packages/account/src/providers/transaction-response/transaction-response.ts @@ -30,7 +30,7 @@ import { arrayify, assertUnreachable } from '@fuel-ts/utils'; import type { GqlMalleableTransactionFieldsFragment, GqlStatusChangeSubscription, - GqlSubmitAndAwaitSubscription, + GqlSubmitAndAwaitStatusSubscription, } from '../__generated__/operations'; import type Provider from '../provider'; import type { JsonAbisFromAllCalls, TransactionRequest } from '../transaction-request'; @@ -146,7 +146,7 @@ export class TransactionResponse { tx: string | TransactionRequest, provider: Provider, abis?: JsonAbisFromAllCalls, - private submitTxSubscription?: AsyncIterable + private submitTxSubscription?: AsyncIterable ) { this.id = typeof tx === 'string' ? tx : tx.getTransactionId(provider.getChainId()); @@ -332,7 +332,7 @@ export class TransactionResponse { })); for await (const sub of subscription) { - const statusChange = 'statusChange' in sub ? sub.statusChange : sub.submitAndAwait; + const statusChange = 'statusChange' in sub ? sub.statusChange : sub.submitAndAwaitStatus; this.status = statusChange; if (statusChange.type === 'SqueezedOutStatus') { this.unsetResourceCache(); diff --git a/packages/versions/src/lib/getBuiltinVersions.ts b/packages/versions/src/lib/getBuiltinVersions.ts index 109b111d0f1..4efe3a99e5f 100644 --- a/packages/versions/src/lib/getBuiltinVersions.ts +++ b/packages/versions/src/lib/getBuiltinVersions.ts @@ -3,7 +3,7 @@ import type { Versions } from './types'; export function getBuiltinVersions(): Versions { return { FORC: '0.63.3', - FUEL_CORE: '0.34.0', + FUEL_CORE: '0.35.0', FUELS: '0.94.3', }; } From 1797323e0a8952d37407c16b5f08d754341ad861 Mon Sep 17 00:00:00 2001 From: nedsalk Date: Mon, 2 Sep 2024 14:57:39 +0200 Subject: [PATCH 03/12] bump version --- packages/account/src/providers/provider.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/account/src/providers/provider.test.ts b/packages/account/src/providers/provider.test.ts index c40cee101ed..d5a29a0f884 100644 --- a/packages/account/src/providers/provider.test.ts +++ b/packages/account/src/providers/provider.test.ts @@ -143,7 +143,7 @@ describe('Provider', () => { const version = await provider.getVersion(); - expect(version).toEqual('0.34.0'); + expect(version).toEqual('0.35.0'); }); it('can call()', async () => { From 96eef81b4db34ddf439aa04f97b9c2b315e56e47 Mon Sep 17 00:00:00 2001 From: nedsalk Date: Wed, 4 Sep 2024 14:44:15 +0200 Subject: [PATCH 04/12] changeset update --- .changeset/famous-pans-press.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/famous-pans-press.md b/.changeset/famous-pans-press.md index c167bbc6c04..0b7357379b2 100644 --- a/.changeset/famous-pans-press.md +++ b/.changeset/famous-pans-press.md @@ -2,4 +2,4 @@ "@fuel-ts/account": minor --- -feat!: read malleable fields from transaction status on subscription +feat: use `submitAndAwaitStatus` to submit transactions From 21ca14e251e30897ae40194104edc20405a5493b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nedim=20Salki=C4=87?= Date: Wed, 4 Sep 2024 15:58:28 +0200 Subject: [PATCH 05/12] Update famous-pans-press.md --- .changeset/famous-pans-press.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/famous-pans-press.md b/.changeset/famous-pans-press.md index 0b7357379b2..bf5f5cdc04e 100644 --- a/.changeset/famous-pans-press.md +++ b/.changeset/famous-pans-press.md @@ -1,5 +1,5 @@ --- -"@fuel-ts/account": minor +"@fuel-ts/account": patch --- feat: use `submitAndAwaitStatus` to submit transactions From e6d47989efc72721ab25fc5194156d9505b8b4d9 Mon Sep 17 00:00:00 2001 From: nedsalk Date: Thu, 5 Sep 2024 08:58:15 +0200 Subject: [PATCH 06/12] test: add edge-case test case to verify failure --- packages/fuel-gauge/src/edge-cases.test.ts | 32 ++++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/packages/fuel-gauge/src/edge-cases.test.ts b/packages/fuel-gauge/src/edge-cases.test.ts index cd26d09cb5c..f9803ab99a1 100644 --- a/packages/fuel-gauge/src/edge-cases.test.ts +++ b/packages/fuel-gauge/src/edge-cases.test.ts @@ -1,6 +1,14 @@ -import { TransactionResponse, Wallet } from 'fuels'; -import { launchTestNode } from 'fuels/test-utils'; +import { + ErrorCode, + FuelError, + ScriptTransactionRequest, + TransactionResponse, + Wallet, + WalletUnlocked, +} from 'fuels'; +import { expectToThrowFuelError, launchTestNode } from 'fuels/test-utils'; +import { PredicateFalse } from '../test/typegen'; import { CollisionInFnNamesFactory } from '../test/typegen/contracts'; import { launchTestContract } from './utils'; @@ -53,4 +61,24 @@ describe('Edge Cases', () => { // we leave this intentionally empty so that we test that the subscription will end the loop when the connection is closed } }); + + test('Sending a failing transaction throws immediately', async () => { + using launched = await launchTestNode(); + const { + wallets: [funder], + } = launched; + + const predicate = new PredicateFalse({ provider: launched.provider }); + await funder.transfer(predicate.address, 1_000_000); + + const transferTx = await predicate.createTransfer(WalletUnlocked.generate().address, 1); + + await expectToThrowFuelError( + () => launched.provider.sendTransaction(transferTx), + new FuelError( + ErrorCode.INVALID_REQUEST, + 'Invalid transaction data: PredicateVerificationFailed(Panic(PredicateReturnedNonOne))' + ) + ); + }); }); From f152eb67cd8c0c974ac0e8425dcbd89bce53260b Mon Sep 17 00:00:00 2001 From: nedsalk Date: Thu, 5 Sep 2024 09:09:39 +0200 Subject: [PATCH 07/12] improve type handling of subscriptions --- packages/account/src/providers/provider.ts | 29 +++++++++++----------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/packages/account/src/providers/provider.ts b/packages/account/src/providers/provider.ts index ba4b323d5c1..1db99589de6 100644 --- a/packages/account/src/providers/provider.ts +++ b/packages/account/src/providers/provider.ts @@ -379,21 +379,20 @@ type ChainInfoCache = Record; */ type NodeInfoCache = Record; -type Operations = ReturnType; - -type SdkOperations = Omit< - Operations, - 'submitAndAwait' | 'statusChange' | 'submitAndAwaitStatus' -> & { - submitAndAwait: ( - ...args: Parameters - ) => Promise>; - statusChange: ( - ...args: Parameters - ) => Promise>; - submitAndAwaitStatus: ( - ...args: Parameters - ) => Promise>; +// eslint-disable-next-line @typescript-eslint/no-explicit-any +type PromisifyReturn any> = ( + ...args: Parameters +) => Promise>; + +type Ops = ReturnType; + +type Operations = { + [K in keyof Ops]: ReturnType extends AsyncIterable + ? PromisifyReturn // Wrap subscriptions in a promise to match return of `FuelGraphqlSubscriber` + : Ops[K]; +}; + +type SdkOperations = Operations & { getBlobs: (variables: { blobIds: string[] }) => Promise<{ blob: { id: string } | null }[]>; }; From 4e5a8e7fcb3e572460e80fd3dab62f8c51926248 Mon Sep 17 00:00:00 2001 From: nedsalk Date: Thu, 5 Sep 2024 09:27:12 +0200 Subject: [PATCH 08/12] add deprecation warning for `submitAndAwait` --- packages/account/src/providers/provider.ts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/packages/account/src/providers/provider.ts b/packages/account/src/providers/provider.ts index 1db99589de6..1069ec45f5d 100644 --- a/packages/account/src/providers/provider.ts +++ b/packages/account/src/providers/provider.ts @@ -392,7 +392,15 @@ type Operations = { : Ops[K]; }; -type SdkOperations = Operations & { +type SdkOperations = Omit & { + /** + * This method is DEPRECATED and will be REMOVED in v1. + * + * This method will hang until the transaction is fully processed, as described in https://github.com/FuelLabs/fuel-core/issues/2108. + * + * Please use the `submitAndAwaitStatus` method instead. + */ + submitAndAwait: PromisifyReturn; getBlobs: (variables: { blobIds: string[] }) => Promise<{ blob: { id: string } | null }[]>; }; From b8253e48aef797fdb4414d5159942068045481f3 Mon Sep 17 00:00:00 2001 From: nedsalk Date: Thu, 5 Sep 2024 09:34:17 +0200 Subject: [PATCH 09/12] better test --- packages/fuel-gauge/src/edge-cases.test.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/fuel-gauge/src/edge-cases.test.ts b/packages/fuel-gauge/src/edge-cases.test.ts index f9803ab99a1..d29807433bb 100644 --- a/packages/fuel-gauge/src/edge-cases.test.ts +++ b/packages/fuel-gauge/src/edge-cases.test.ts @@ -1,6 +1,7 @@ import { ErrorCode, FuelError, + hexlify, ScriptTransactionRequest, TransactionResponse, Wallet, @@ -62,7 +63,7 @@ describe('Edge Cases', () => { } }); - test('Sending a failing transaction throws immediately', async () => { + test('Submitting a failing transaction via submitAndAwaitStatus subscription throws immediately', async () => { using launched = await launchTestNode(); const { wallets: [funder], @@ -74,7 +75,10 @@ describe('Edge Cases', () => { const transferTx = await predicate.createTransfer(WalletUnlocked.generate().address, 1); await expectToThrowFuelError( - () => launched.provider.sendTransaction(transferTx), + () => + launched.provider.operations.submitAndAwaitStatus({ + encodedTransaction: hexlify(transferTx.toTransactionBytes()), + }), new FuelError( ErrorCode.INVALID_REQUEST, 'Invalid transaction data: PredicateVerificationFailed(Panic(PredicateReturnedNonOne))' From f5076fdb2935fc1400a9d61f0a09d0fcb0b57bc8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nedim=20Salki=C4=87?= Date: Thu, 5 Sep 2024 09:49:26 +0200 Subject: [PATCH 10/12] Update edge-cases.test.ts --- packages/fuel-gauge/src/edge-cases.test.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/fuel-gauge/src/edge-cases.test.ts b/packages/fuel-gauge/src/edge-cases.test.ts index d29807433bb..2aa850602f3 100644 --- a/packages/fuel-gauge/src/edge-cases.test.ts +++ b/packages/fuel-gauge/src/edge-cases.test.ts @@ -2,7 +2,6 @@ import { ErrorCode, FuelError, hexlify, - ScriptTransactionRequest, TransactionResponse, Wallet, WalletUnlocked, From 4c417e355fe9bd98e2c45736e4f6a8aa7ce0b34b Mon Sep 17 00:00:00 2001 From: nedsalk Date: Thu, 5 Sep 2024 10:56:10 +0200 Subject: [PATCH 11/12] prettier formatting --- packages/fuel-gauge/src/edge-cases.test.ts | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/packages/fuel-gauge/src/edge-cases.test.ts b/packages/fuel-gauge/src/edge-cases.test.ts index 2aa850602f3..580b6df9dbd 100644 --- a/packages/fuel-gauge/src/edge-cases.test.ts +++ b/packages/fuel-gauge/src/edge-cases.test.ts @@ -1,11 +1,4 @@ -import { - ErrorCode, - FuelError, - hexlify, - TransactionResponse, - Wallet, - WalletUnlocked, -} from 'fuels'; +import { ErrorCode, FuelError, hexlify, TransactionResponse, Wallet, WalletUnlocked } from 'fuels'; import { expectToThrowFuelError, launchTestNode } from 'fuels/test-utils'; import { PredicateFalse } from '../test/typegen'; From b56889a7a6be02cc856f57d33df20f9671543054 Mon Sep 17 00:00:00 2001 From: nedsalk Date: Thu, 5 Sep 2024 12:00:44 +0200 Subject: [PATCH 12/12] revert type generics --- packages/account/src/providers/provider.ts | 28 +++++++++++----------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/packages/account/src/providers/provider.ts b/packages/account/src/providers/provider.ts index 1069ec45f5d..48341f340b9 100644 --- a/packages/account/src/providers/provider.ts +++ b/packages/account/src/providers/provider.ts @@ -379,20 +379,12 @@ type ChainInfoCache = Record; */ type NodeInfoCache = Record; -// eslint-disable-next-line @typescript-eslint/no-explicit-any -type PromisifyReturn any> = ( - ...args: Parameters -) => Promise>; +type Operations = ReturnType; -type Ops = ReturnType; - -type Operations = { - [K in keyof Ops]: ReturnType extends AsyncIterable - ? PromisifyReturn // Wrap subscriptions in a promise to match return of `FuelGraphqlSubscriber` - : Ops[K]; -}; - -type SdkOperations = Omit & { +type SdkOperations = Omit< + Operations, + 'submitAndAwait' | 'statusChange' | 'submitAndAwaitStatus' +> & { /** * This method is DEPRECATED and will be REMOVED in v1. * @@ -400,7 +392,15 @@ type SdkOperations = Omit & { * * Please use the `submitAndAwaitStatus` method instead. */ - submitAndAwait: PromisifyReturn; + submitAndAwait: ( + ...args: Parameters + ) => Promise>; + statusChange: ( + ...args: Parameters + ) => Promise>; + submitAndAwaitStatus: ( + ...args: Parameters + ) => Promise>; getBlobs: (variables: { blobIds: string[] }) => Promise<{ blob: { id: string } | null }[]>; };