From d4295f7acb4185e63569b4e31628da75a7955ff7 Mon Sep 17 00:00:00 2001 From: George Date: Thu, 14 Sep 2023 12:14:49 -0700 Subject: [PATCH] Properly parse `simulateTransaction` response variations (#146) * The docs say it's a string, not an integer: https://soroban.stellar.org/api/methods/getEvents * Update numbers to strings in various ledger fields * Update schema detection code to work w/ RPC --- src/soroban_rpc.ts | 20 ++++---- src/transaction.ts | 24 ++-------- test/unit/server/simulate_transaction_test.js | 47 ++++++++++++++++++- 3 files changed, 61 insertions(+), 30 deletions(-) diff --git a/src/soroban_rpc.ts b/src/soroban_rpc.ts index 901d1fa6..74fab8a3 100644 --- a/src/soroban_rpc.ts +++ b/src/soroban_rpc.ts @@ -68,9 +68,9 @@ export namespace SorobanRpc { interface GetAnyTransactionResponse { status: GetTransactionStatus; - latestLedger: number; + latestLedger: string; latestLedgerCloseTime: number; - oldestLedger: number; + oldestLedger: string; oldestLedgerCloseTime: number; } @@ -98,9 +98,9 @@ export namespace SorobanRpc { export interface RawGetTransactionResponse { status: GetTransactionStatus; - latestLedger: number; + latestLedger: string; latestLedgerCloseTime: number; - oldestLedger: number; + oldestLedger: string; oldestLedgerCloseTime: number; // the fields below are set if status is SUCCESS @@ -122,7 +122,7 @@ export namespace SorobanRpc { } export interface GetEventsResponse { - latestLedger: number; + latestLedger: string; events: EventResponse[]; } @@ -186,7 +186,7 @@ export namespace SorobanRpc { id: string; /** always present: the LCL known to the server when responding */ - latestLedger: number; + latestLedger: string; /** * The field is always present, but may be empty in cases where: @@ -195,6 +195,9 @@ export namespace SorobanRpc { * @see {@link humanizeEvents} */ events: xdr.DiagnosticEvent[]; + + /** a private field to mark the schema as parsed */ + _parsed: boolean; } /** Includes simplified fields only present on success. */ @@ -245,7 +248,8 @@ export namespace SorobanRpc { export function isSimulationRestore(sim: SimulateTransactionResponse): sim is SimulateTransactionRestoreResponse { - return isSimulationSuccess(sim) && 'restorePreamble' in sim; + return isSimulationSuccess(sim) && 'restorePreamble' in sim && + !!sim.restorePreamble.transactionData; } interface RawSimulateHostFunctionResult { @@ -258,7 +262,7 @@ export namespace SorobanRpc { /** @see https://soroban.stellar.org/api/methods/simulateTransaction#returns */ export interface RawSimulateTransactionResponse { id: string; - latestLedger: number; + latestLedger: string; error?: string; // this is an xdr.SorobanTransactionData in base64 transactionData?: string; diff --git a/src/transaction.ts b/src/transaction.ts index 8d09669b..126803b2 100644 --- a/src/transaction.ts +++ b/src/transaction.ts @@ -121,6 +121,7 @@ export function parseRawSimulation( // shared across all responses let base: SorobanRpc.BaseSimulateTransactionResponse = { + _parsed: true, id: sim.id, latestLedger: sim.latestLedger, events: sim.events?.map( @@ -171,7 +172,7 @@ function parseSuccessful( ) }; - if (!sim.restorePreamble) { + if (!sim.restorePreamble || sim.restorePreamble.transactionData === '') { return success; } @@ -187,29 +188,12 @@ function parseSuccessful( }; } - -function isSimulationRaw( +export function isSimulationRaw( sim: | SorobanRpc.SimulateTransactionResponse | SorobanRpc.RawSimulateTransactionResponse ): sim is SorobanRpc.RawSimulateTransactionResponse { - const asGud = sim as SorobanRpc.SimulateTransactionRestoreResponse; - const asRaw = sim as SorobanRpc.RawSimulateTransactionResponse; - - // lazy checks to determine type: check existence of parsed-only fields note - return ( - asRaw.restorePreamble !== undefined || - !( - asGud.restorePreamble !== undefined || - asGud.result !== undefined || - typeof asGud.transactionData !== 'string' - ) || - (asRaw.error !== undefined && ( - !asRaw.events?.length || - typeof asRaw.events![0] === 'string' - )) || - (asRaw.results ?? []).length > 0 - ); + return !(sim as SorobanRpc.SimulateTransactionResponse)._parsed; } function isSorobanTransaction(tx: Transaction): boolean { diff --git a/test/unit/server/simulate_transaction_test.js b/test/unit/server/simulate_transaction_test.js index ef2c40db..2ad6a9fb 100644 --- a/test/unit/server/simulate_transaction_test.js +++ b/test/unit/server/simulate_transaction_test.js @@ -26,7 +26,8 @@ describe('Server#simulateTransaction', function () { ), retval: xdr.ScVal.fromXDR(simulationResponse.results[0].xdr, 'base64') }, - cost: simulationResponse.cost + cost: simulationResponse.cost, + _parsed: true }; beforeEach(function () { @@ -167,7 +168,8 @@ function cloneSimulation(sim) { ), retval: xdr.ScVal.fromXDR(sim.result.retval.toXDR()) }, - cost: sim.cost + cost: sim.cost, + _parsed: sim._parsed }; } @@ -235,3 +237,44 @@ function invokeSimulationResponseWithRestoration(address) { } }; } + +describe('works with real responses', function () { + const schema = { + transactionData: + 'AAAAAAAAAAIAAAAGAAAAAa/6eoLeofDK5ksPljSZ7t/rAj/XR18e40fCB9LBugstAAAAFAAAAAEAAAAHqA0LEZLq3WL+N3rBQLTWuPqdV3Vv6XIAGeBJaz1wMdsAAAAAABg1gAAAAxwAAAAAAAAAAAAAAAk=', + minResourceFee: '27889', + events: [ + 'AAAAAQAAAAAAAAAAAAAAAgAAAAAAAAADAAAADwAAAAdmbl9jYWxsAAAAAA0AAAAgr/p6gt6h8MrmSw+WNJnu3+sCP9dHXx7jR8IH0sG6Cy0AAAAPAAAABWhlbGxvAAAAAAAADwAAAAVBbG9oYQAAAA==', + 'AAAAAQAAAAAAAAABr/p6gt6h8MrmSw+WNJnu3+sCP9dHXx7jR8IH0sG6Cy0AAAACAAAAAAAAAAIAAAAPAAAACWZuX3JldHVybgAAAAAAAA8AAAAFaGVsbG8AAAAAAAAQAAAAAQAAAAIAAAAPAAAABUhlbGxvAAAAAAAADwAAAAVBbG9oYQAAAA==' + ], + results: [ + { + auth: [], + xdr: 'AAAAEAAAAAEAAAACAAAADwAAAAVIZWxsbwAAAAAAAA8AAAAFQWxvaGEAAAA=' + } + ], + cost: { + cpuInsns: '1322134', + memBytes: '1207047' + }, + restorePreamble: { + transactionData: '', + minResourceFee: '0' + }, + latestLedger: '2634' + }; + + it('parses the schema', function () { + expect(SorobanClient.isSimulationRaw(schema)).to.be.true; + + const parsed = SorobanClient.parseRawSimulation(schema); + + expect(parsed.results).to.be.undefined; + expect(parsed.result.auth).to.be.empty; + expect(parsed.result.retval).to.be.instanceOf(xdr.ScVal); + expect(parsed.transactionData).to.be.instanceOf(SorobanDataBuilder); + expect(parsed.events).to.be.lengthOf(2); + expect(parsed.events[0]).to.be.instanceOf(xdr.DiagnosticEvent); + expect(parsed.restorePreamble).to.be.undefined; + }); +});