Skip to content

Commit

Permalink
Automatically decode XDR values in getTransaction (#129)
Browse files Browse the repository at this point in the history
* Separate response type into status-based interfaces
  • Loading branch information
Shaptic authored Aug 21, 2023
1 parent 2bd3b09 commit 4eedb93
Show file tree
Hide file tree
Showing 6 changed files with 248 additions and 64 deletions.
15 changes: 10 additions & 5 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,21 @@ A breaking change should be clearly marked in this log.


### Breaking Changes
* The minimum supported NodeJS version is now Node 16.
* `Server.prepareTransaction` now returns a `TransactionBuilder` instance rather than an immutable `Transaction`, in order to facilitate modifying your transaction after assembling it alongside the simulation response ([https://github.com/stellar/js-soroban-client/pull/127](#127)).
- The intent is to avoid cloning the transaction again (via `TransactionBuilder.cloneFrom`) if you need to modify parameters such as the storage access footprint.
- To migrate your code, just call `.build()` on the return value.
* The RPC response schemas for simulation have been upgraded to parse the base64-encoded XDR automatically. The full interface changes are in the pull request ([https://github.com/stellar/js-soroban-client/pull/127](#127)), but succinctly:
* The RPC response schemas for simulation (see `Server.simulateTransaction()`) have been upgraded to parse the base64-encoded XDR automatically. The full interface changes are in the pull request ([https://github.com/stellar/js-soroban-client/pull/127](#127)), but succinctly:
- `SimulateTransactionResponse` -> `RawSimulateTransactionResponse`
- `SimulateHostFunctionResult` -> `RawSimulateHostFunctionResult`
- Now, `SimulateTransactionResponse` and `SimulateHostFunctionResult` now include the full, decoded XDR structures instead of raw, base64-encoded strings for the relevant fields (e.g. `SimulateTransactionResponse.transactionData` is now an instance of `SorobanDataBuilder`, `events` is now an `xdr.DiagnosticEvent[]` [try out `humanizeEvents` for a friendlier representation of this field])
- The `SimulateTransactionResponse.results[]` field has been moved to `SimulateTransactionResponse.result?`, since it will always be exactly zero or one result.

Not all schemas have been broken in this manner in order to facilitate user feedback on this approach. Please add your :+1: or :-1: to [#128](https://github.com/stellar/js-soroban-client/issues/128) to provide your perspective on whether or not we should do this for the other response schemas.
- Now, `SimulateTransactionResponse` and `SimulateHostFunctionResult` include the full, decoded XDR structures instead of raw, base64-encoded strings for the relevant fields (e.g. `SimulateTransactionResponse.transactionData` is now an instance of `SorobanDataBuilder`, `events` is now an `xdr.DiagnosticEvent[]` [try out `humanizeEvents` for a friendlier representation of this field]).
- The `SimulateTransactionResponse.results[]` field has been moved to `SimulateTransactionResponse.result?`, since there will always be exactly zero or one result.
* The RPC response schemas for retrieving transaction details (`Server.getTransaction()`) have been upgraded to parse the base64-encoded XDR automatically. The full interface changes are in the pull request ([https://github.com/stellar/js-soroban-client/pull/129](#129)), but succinctly:
- `GetTransactionResponse` -> `RawGetTransactionResponse`
- All of the `*Xdr` properties are now full, decoded XDR structures.
- There is a new `returnValue` field which is a decoded `xdr.ScVal`, present iff the transaction was a successful Soroban function invocation.

Not all schemas have been broken in this manner in order to facilitate user feedback on this approach. Please add your :+1: or :-1: to [#128](https://github.com/stellar/js-soroban-client/issues/128) to vote on whether or not we should do this for the other response schemas.


## v0.10.1
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@
"@types/chai": "^4.3.5",
"@types/detect-node": "^2.0.0",
"@types/eventsource": "^1.1.2",
"@types/lodash": "^4.14.192",
"@types/lodash": "^4.14.197",
"@types/mocha": "^10.0.1",
"@types/node": "^20.4.2",
"@types/randombytes": "^2.0.0",
Expand Down
101 changes: 72 additions & 29 deletions src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@ export interface GetEventsRequest {
* Specifies the durability namespace of contract-related ledger entries.
*/
export enum Durability {
Temporary = 'temporary',
Persistent = 'persistent',
Temporary = "temporary",
Persistent = "persistent",
}

/**
Expand Down Expand Up @@ -182,7 +182,7 @@ export class Server {
): Promise<SorobanRpc.LedgerEntryResult> {
// coalesce `contract` param variants to an ScAddress
let scAddress: xdr.ScAddress;
if (typeof contract === 'string') {
if (typeof contract === "string") {
scAddress = new Contract(contract).address().toScAddress();
} else if (contract instanceof Address) {
scAddress = contract.toScAddress();
Expand Down Expand Up @@ -211,24 +211,30 @@ export class Server {
contract: scAddress,
key,
durability: xdrDurability,
bodyType: xdr.ContractEntryBodyType.dataEntry() // expirationExtension is internal
})
bodyType: xdr.ContractEntryBodyType.dataEntry(), // expirationExtension is internal
}),
).toXDR("base64");

return jsonrpc.post<SorobanRpc.GetLedgerEntriesResponse>(
this.serverURL.toString(),
"getLedgerEntries",
[contractKey],
).then(response => {
return jsonrpc
.post<SorobanRpc.GetLedgerEntriesResponse>(
this.serverURL.toString(),
"getLedgerEntries",
[contractKey],
)
.then((response) => {
const ledgerEntries = response.entries ?? [];
if (ledgerEntries.length !== 1) {
return Promise.reject({
code: 404,
message: `Contract data not found. Contract: ${Address.fromScAddress(scAddress).toString()}, Key: ${key.toXDR("base64")}, Durability: ${durability}`,
});
}
return ledgerEntries[0];
});
if (ledgerEntries.length !== 1) {
return Promise.reject({
code: 404,
message: `Contract data not found. Contract: ${Address.fromScAddress(
scAddress,
).toString()}, Key: ${key.toXDR(
"base64",
)}, Durability: ${durability}`,
});
}
return ledgerEntries[0];
});
}

/**
Expand Down Expand Up @@ -287,18 +293,55 @@ export class Server {
*
* @param {string} hash - The hex-encoded hash of the transaction to check.
*
* @returns {Promise<SorobanRpc.GetTransactionResponse>} Returns a
* promise to the {@link SorobanRpc.GetTransactionResponse} object
* with the status, result, and other details about the transaction.
* @returns {Promise<SorobanRpc.GetTransactionResponse>} Returns a promise to
* the {@link SorobanRpc.GetTransactionResponse} object with the status,
* result, and other details about the transaction. Raw XDR fields are
* parsed into their appropriate structures wherever possible.
*/
public async getTransaction(
hash: string,
): Promise<SorobanRpc.GetTransactionResponse> {
return await jsonrpc.post(
const raw = await jsonrpc.post<SorobanRpc.RawGetTransactionResponse>(
this.serverURL.toString(),
"getTransaction",
hash,
);

let successInfo: Omit<
SorobanRpc.GetSuccessfulTransactionResponse,
keyof SorobanRpc.GetFailedTransactionResponse
> = {} as any;

if (raw.status === SorobanRpc.GetTransactionStatus.SUCCESS) {
const meta = xdr.TransactionMeta.fromXDR(raw.resultMetaXdr!, "base64");
successInfo = {
ledger: raw.ledger!,
createdAt: raw.createdAt!,
applicationOrder: raw.applicationOrder!,
feeBump: raw.feeBump!,
envelopeXdr: xdr.TransactionEnvelope.fromXDR(
raw.envelopeXdr!,
"base64",
),
resultXdr: xdr.TransactionResult.fromXDR(raw.resultXdr!, "base64"),
resultMetaXdr: meta,
...(meta.switch() === 3 &&
meta.v3().sorobanMeta() !== null && {
returnValue: meta.v3().sorobanMeta()?.returnValue(),
}),
};
}

const result: SorobanRpc.GetTransactionResponse = {
status: raw.status,
latestLedger: raw.latestLedger,
latestLedgerCloseTime: raw.latestLedgerCloseTime,
oldestLedger: raw.oldestLedger,
oldestLedgerCloseTime: raw.oldestLedgerCloseTime,
...successInfo,
};

return result;
}

/**
Expand Down Expand Up @@ -347,8 +390,6 @@ export class Server {
// is an ScSymbol and the last is a U32.
//
// The difficulty comes in matching up the correct integer primitives.
//
// It also means this library will rely on the XDR definitions.
return await jsonrpc.postObject(this.serverURL.toString(), "getEvents", {
filters: request.filters ?? [],
pagination: {
Expand Down Expand Up @@ -449,11 +490,13 @@ export class Server {
public async simulateTransaction(
transaction: Transaction | FeeBumpTransaction,
): Promise<SorobanRpc.SimulateTransactionResponse> {
return await jsonrpc.post<SorobanRpc.RawSimulateTransactionResponse>(
this.serverURL.toString(),
"simulateTransaction",
transaction.toXDR(),
).then((raw) => parseRawSimulation(raw));
return await jsonrpc
.post<SorobanRpc.RawSimulateTransactionResponse>(
this.serverURL.toString(),
"simulateTransaction",
transaction.toXDR(),
)
.then((raw) => parseRawSimulation(raw));
}

/**
Expand Down
43 changes: 41 additions & 2 deletions src/soroban_rpc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,48 @@ export namespace SorobanRpc {
protocolVersion: string;
}

export type GetTransactionStatus = "SUCCESS" | "NOT_FOUND" | "FAILED";
export enum GetTransactionStatus {
SUCCESS = "SUCCESS",
NOT_FOUND = "NOT_FOUND",
FAILED = "FAILED"
}

export type GetTransactionResponse =
| GetSuccessfulTransactionResponse
| GetFailedTransactionResponse
| GetMissingTransactionResponse;

interface GetAnyTransactionResponse {
status: GetTransactionStatus;
latestLedger: number;
latestLedgerCloseTime: number;
oldestLedger: number;
oldestLedgerCloseTime: number;
}

export interface GetMissingTransactionResponse extends GetAnyTransactionResponse {
status: GetTransactionStatus.NOT_FOUND;
}

export interface GetFailedTransactionResponse extends GetAnyTransactionResponse {
status: GetTransactionStatus.FAILED;
}

export interface GetSuccessfulTransactionResponse extends GetAnyTransactionResponse {
status: GetTransactionStatus.SUCCESS;

ledger: number;
createdAt: number;
applicationOrder: number;
feeBump: boolean;
envelopeXdr: xdr.TransactionEnvelope;
resultXdr: xdr.TransactionResult;
resultMetaXdr: xdr.TransactionMeta;

returnValue?: xdr.ScVal; // present iff resultMeta is a v3
}

export interface GetTransactionResponse {
export interface RawGetTransactionResponse {
status: GetTransactionStatus;
latestLedger: number;
latestLedgerCloseTime: number;
Expand Down
Loading

0 comments on commit 4eedb93

Please sign in to comment.