Skip to content

Commit

Permalink
fix: adding simulation on failed transactions (#133)
Browse files Browse the repository at this point in the history
After a failed transaction, using simulate to extract more information about what went wrong on debug mode.
  • Loading branch information
negar-abbasi authored Sep 21, 2023
1 parent c95d629 commit 4e3c933
Show file tree
Hide file tree
Showing 7 changed files with 478 additions and 91 deletions.
4 changes: 3 additions & 1 deletion docs/capabilities/app-client.md
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,9 @@ If you want to go a step further and automatically issue a [dry run transaction]
algokit.Config.configure({ debug: true })
```

If you do that then the exception will have the `traces` property within the underlying exception will have key information from the dry run within it and this will get populated into the `led.traces` property of the thrown error.
> ⚠️ **Note:** The "dry run" feature has been deprecated and is now replaced by the "simulation" feature. Please refer to the [Simulation Documentation](https://algorand.github.io/js-algorand-sdk/classes/modelsv2.SimulateTransactionResult.html) for more details.
If you do that then the exception will have the `traces` property within the underlying exception will have key information from the simulation within it and this will get populated into the `led.traces` property of the thrown error.

## `AppClientCallParams`

Expand Down
54 changes: 40 additions & 14 deletions docs/code/modules/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@
- [mnemonicAccountFromEnvironment](index.md#mnemonicaccountfromenvironment)
- [multisigAccount](index.md#multisigaccount)
- [performAtomicTransactionComposerDryrun](index.md#performatomictransactioncomposerdryrun)
- [performAtomicTransactionComposerSimulate](index.md#performatomictransactioncomposersimulate)
- [performTemplateSubstitution](index.md#performtemplatesubstitution)
- [performTemplateSubstitutionAndCompile](index.md#performtemplatesubstitutionandcompile)
- [randomAccount](index.md#randomaccount)
Expand Down Expand Up @@ -172,7 +173,7 @@ the estimated rate.

#### Defined in

[src/transaction.ts:397](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/transaction.ts#L397)
[src/transaction.ts:426](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/transaction.ts#L426)

___

Expand Down Expand Up @@ -228,7 +229,7 @@ Allows for control of fees on a `Transaction` or `SuggestedParams` object

#### Defined in

[src/transaction.ts:420](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/transaction.ts#L420)
[src/transaction.ts:449](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/transaction.ts#L449)

___

Expand Down Expand Up @@ -343,7 +344,7 @@ the transaction note ready for inclusion in a transaction

#### Defined in

[src/transaction.ts:38](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/transaction.ts#L38)
[src/transaction.ts:39](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/transaction.ts#L39)

___

Expand Down Expand Up @@ -1270,7 +1271,7 @@ The array of transactions with signers

#### Defined in

[src/transaction.ts:452](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/transaction.ts#L452)
[src/transaction.ts:481](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/transaction.ts#L481)

___

Expand Down Expand Up @@ -1519,7 +1520,7 @@ The public address

#### Defined in

[src/transaction.ts:59](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/transaction.ts#L59)
[src/transaction.ts:60](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/transaction.ts#L60)

___

Expand All @@ -1544,7 +1545,7 @@ A transaction signer

#### Defined in

[src/transaction.ts:69](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/transaction.ts#L69)
[src/transaction.ts:70](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/transaction.ts#L70)

___

Expand All @@ -1569,7 +1570,7 @@ The suggested transaction parameters

#### Defined in

[src/transaction.ts:443](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/transaction.ts#L443)
[src/transaction.ts:472](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/transaction.ts#L472)

___

Expand All @@ -1595,7 +1596,7 @@ A TransactionWithSigner object.

#### Defined in

[src/transaction.ts:82](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/transaction.ts#L82)
[src/transaction.ts:83](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/transaction.ts#L83)

___

Expand Down Expand Up @@ -1906,7 +1907,32 @@ The dryrun result

#### Defined in

[src/transaction.ts:277](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/transaction.ts#L277)
[src/transaction.ts:278](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/transaction.ts#L278)

___

### performAtomicTransactionComposerSimulate

▸ **performAtomicTransactionComposerSimulate**(`atc`, `algod`): `Promise`<`SimulateResponse`\>

Performs a simulation of the transactions loaded into the given AtomicTransactionComposer.

#### Parameters

| Name | Type | Description |
| :------ | :------ | :------ |
| `atc` | `AtomicTransactionComposer` | The AtomicTransactionComposer with transaction(s) loaded. |
| `algod` | `default` | An Algod client to perform the simulation. |

#### Returns

`Promise`<`SimulateResponse`\>

The simulation result, which includes various details about how the transactions would be processed.

#### Defined in

[src/transaction.ts:293](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/transaction.ts#L293)

___

Expand Down Expand Up @@ -2089,7 +2115,7 @@ An object with transaction IDs, transactions, group transaction ID (`groupTransa

#### Defined in

[src/transaction.ts:189](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/transaction.ts#L189)
[src/transaction.ts:190](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/transaction.ts#L190)

___

Expand All @@ -2114,7 +2140,7 @@ An object with transaction IDs, transactions, group transaction ID (`groupTransa

#### Defined in

[src/transaction.ts:295](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/transaction.ts#L295)
[src/transaction.ts:324](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/transaction.ts#L324)

___

Expand Down Expand Up @@ -2142,7 +2168,7 @@ An object with transaction (`transaction`) and (if `skipWaiting` is `false` or `

#### Defined in

[src/transaction.ts:145](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/transaction.ts#L145)
[src/transaction.ts:146](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/transaction.ts#L146)

___

Expand All @@ -2167,7 +2193,7 @@ The signed transaction as a `Uint8Array`

#### Defined in

[src/transaction.ts:125](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/transaction.ts#L125)
[src/transaction.ts:126](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/transaction.ts#L126)

___

Expand Down Expand Up @@ -2319,4 +2345,4 @@ Throws an error if the transaction is not confirmed or rejected in the next `tim

#### Defined in

[src/transaction.ts:340](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/transaction.ts#L340)
[src/transaction.ts:369](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/transaction.ts#L369)
6 changes: 4 additions & 2 deletions src/__snapshots__/app-deploy.spec.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ INFO: Existing app test found by creator ACCOUNT_1, with app id APP_1 and versio
INFO: Detected a TEAL update in app APP_1 for creator ACCOUNT_1
WARN: App is not deletable and onUpdate=ReplaceApp, will attempt to create new app and delete old app, delete will most likely fail
INFO: Deploying a new test app for ACCOUNT_1; deploying app with version 2.0.
WARN: Deleting existing test app with id APP_1 from ACCOUNT_1 account."
WARN: Deleting existing test app with id APP_1 from ACCOUNT_1 account.
INFO: Received error executing Atomic Transaction Composer, for more information enable the debug flag"
`;

exports[`deploy-app Deploy failure for replacement of schema broken app fails if onSchemaBreak = Fail 1`] = `
Expand Down Expand Up @@ -72,7 +73,8 @@ INFO: Existing app test found by creator ACCOUNT_1, with app id APP_1 and versio
WARN: Detected a breaking app schema change in app APP_1: | [{"from":{"global":{"numUint":3,"numByteSlice":2,"attribute_map":{"numUint":"num-uint","numByteSlice":"num-byte-slice"}},"local":{"numUint":2,"numByteSlice":2,"attribute_map":{"numUint":"num-uint","numByteSlice":"num-byte-slice"}}},"to":{"global":{"numUint":3,"numByteSlice":3,"attribute_map":{"numUint":"num-uint","numByteSlice":"num-byte-slice"}},"local":{"numUint":2,"numByteSlice":2,"attribute_map":{"numUint":"num-uint","numByteSlice":"num-byte-slice"}}}}]
INFO: App is not deletable but onSchemaBreak=ReplaceApp, will attempt to delete app, delete will most likely fail
INFO: Deploying a new test app for ACCOUNT_1; deploying app with version 2.0.
WARN: Deleting existing test app with id APP_1 from ACCOUNT_1 account."
WARN: Deleting existing test app with id APP_1 from ACCOUNT_1 account.
INFO: Received error executing Atomic Transaction Composer, for more information enable the debug flag"
`;

exports[`deploy-app Deploy update to immutable updated app fails 1`] = `
Expand Down
31 changes: 31 additions & 0 deletions src/transaction.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,37 @@ describe('transaction', () => {
expect((e as Error).message).toEqual(`Transaction ${txn.txID()} not confirmed after 5 rounds`)
}
})

test('Transaction fails in debug mode, error is enriched using simulate', async () => {
const { algod, testAccount } = localnet.context
const txn1 = await getTestTransaction(1)
const txn2 = await getTestTransaction(9999999999999) // This will fail due to fee being too high

try {
await algokit.sendGroupOfTransactions(
{
transactions: [
{
transaction: txn1,
signer: testAccount,
},
{
transaction: txn2,
signer: testAccount,
},
],
},
algod,
)
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} catch (e: any) {
expect(e.traces[0].message).toEqual(
`transaction ${txn2.txID()}: overspend (account ${
testAccount.addr
}, data {AccountBaseData:{Status:Offline MicroAlgos:{Raw:9998000} RewardsBase:0 RewardedMicroAlgos:{Raw:0} AuthAddr:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAY5HFKQ TotalAppSchema:{_struct:{} NumUint:0 NumByteSlice:0} TotalExtraAppPages:0 TotalAppParams:0 TotalAppLocalStates:0 TotalAssetParams:0 TotalAssets:0 TotalBoxes:0 TotalBoxBytes:0} VotingData:{VoteID:[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0] SelectionID:[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0] StateProofID:[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0] VoteFirstValid:0 VoteLastValid:0 VoteKeyDilution:0}}, tried to spend {9999999999999})`,
)
}
})
})

describe('transaction node encoder', () => {
Expand Down
49 changes: 39 additions & 10 deletions src/transaction.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import algosdk, {
Algodv2,
AtomicTransactionComposer,
EncodedSignedTransaction,
modelsv2,
SuggestedParams,
Transaction,
Expand Down Expand Up @@ -245,25 +246,25 @@ export const sendAtomicTransactionComposer = async function (atcSend: AtomicTran
} as SendAtomicTransactionComposerResults
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} catch (e: any) {
Config.logger.info('Received error executing Atomic Transaction Composer, for more information enable the debug flag')
if (Config.debug && typeof e === 'object') {
e.traces = []
Config.logger.debug(
'Received error executing Atomic Transaction Composer and debug flag enabled; attempting dry run to get more information',
'Received error executing Atomic Transaction Composer and debug flag enabled; attempting simulation to get more information',
)
const dryrun = await performAtomicTransactionComposerDryrun(atc, algod)

for (const txn of dryrun.txns) {
if (txn.appCallRejected()) {
const simulate = await performAtomicTransactionComposerSimulate(atc, algod)
if (simulate.txnGroups[0].failedAt) {
for (const txn of simulate.txnGroups[0].txnResults) {
e.traces.push({
trace: txn.appTrace(),
cost: txn.cost,
logs: txn.logs,
messages: txn.appCallMessages,
trace: txn.execTrace?.get_obj_for_encoding(),
appBudget: txn.appBudgetConsumed,
logicSigBudget: txn.logicSigBudgetConsumed,
logs: txn.txnResult.logs,
message: simulate.txnGroups[0].failureMessage,
})
}
}
}

throw e
}
}
Expand All @@ -283,6 +284,34 @@ export async function performAtomicTransactionComposerDryrun(atc: AtomicTransact
return new algosdk.DryrunResult(await algod.dryrun(dryrun).do())
}

/**
* Performs a simulation of the transactions loaded into the given AtomicTransactionComposer.
* @param atc The AtomicTransactionComposer with transaction(s) loaded.
* @param algod An Algod client to perform the simulation.
* @returns The simulation result, which includes various details about how the transactions would be processed.
*/
export async function performAtomicTransactionComposerSimulate(atc: AtomicTransactionComposer, algod: Algodv2) {
const unsignedTransactionsSigners = atc.buildGroup()
const decodedSignedTransactions = unsignedTransactionsSigners.map((ts) => algosdk.encodeUnsignedSimulateTransaction(ts.txn))

const simulateRequest = new modelsv2.SimulateRequest({
allowEmptySignatures: true,
allowMoreLogging: true,
execTraceConfig: new modelsv2.SimulateTraceConfig({
enable: true,
scratchChange: true,
stackChange: true,
}),
txnGroups: [
new modelsv2.SimulateRequestTransactionGroup({
txns: decodedSignedTransactions.map((txn) => algosdk.decodeObj(txn)) as EncodedSignedTransaction[],
}),
],
})
const simulateResult = await algod.simulateTransactions(simulateRequest).do()
return simulateResult
}

/**
* Signs and sends a group of [up to 16](https://developer.algorand.org/docs/get-details/atomic_transfers/#create-transactions) transactions to the chain
*
Expand Down
Loading

0 comments on commit 4e3c933

Please sign in to comment.