Skip to content

Commit 104dde8

Browse files
authored
Implement transaction/dry-run (#236)
* Initial impl transaction/dry-run * Update docs with crates links
1 parent 7c66738 commit 104dde8

File tree

8 files changed

+200
-0
lines changed

8 files changed

+200
-0
lines changed

openapi/openapi-proposal.yaml

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -351,6 +351,10 @@ paths:
351351
responses:
352352
"200":
353353
description: successful operation
354+
content:
355+
application/json:
356+
schema:
357+
$ref: '#/components/schemas/TransactionDryRun'
354358
"500":
355359
description: failed to dry-run transaction
356360
content:
@@ -1737,6 +1741,42 @@ components:
17371741
items:
17381742
type: string
17391743
format: ss58
1744+
TransactionDryRun:
1745+
type: object
1746+
properties:
1747+
resultType:
1748+
type: string
1749+
enum:
1750+
- DispatchOutcome
1751+
- TransactionValidityError
1752+
description: Either `DispatchOutcome` if the transaction is valid or `TransactionValidityError` if the result is invalid.
1753+
result:
1754+
type: string
1755+
enum:
1756+
- Ok
1757+
- CannotLookup
1758+
- NoUnsignedValidator
1759+
- Custom(u8)
1760+
- Call
1761+
- Payment
1762+
- Future
1763+
- Stale
1764+
- BadProof
1765+
- AncientBirthBlock
1766+
- ExhaustsResources
1767+
- BadMandatory
1768+
- MandatoryDispatch
1769+
description: 'If there was an error it will be the cause of the error. If the
1770+
transaction executed correctly it will be `Ok: []`'
1771+
validityErrorType:
1772+
type: string
1773+
enum:
1774+
- InvalidTransaction
1775+
- UnknownTransaction
1776+
description: >-
1777+
References:
1778+
- `UnknownTransaction`: https://crates.parity.io/sp_runtime/transaction_validity/enum.UnknownTransaction.html
1779+
- `InvalidTransaction`: https://crates.parity.io/sp_runtime/transaction_validity/enum.InvalidTransaction.html
17401780
Transaction:
17411781
type: object
17421782
properties:
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import { ApiPromise } from '@polkadot/api';
2+
import { BadRequest } from 'http-errors';
3+
4+
import { TransactionDryRunService } from '../../services';
5+
import { IPostRequestHandler, ITx } from '../../types/requests';
6+
import AbstractController from '../AbstractController';
7+
8+
export default class TransactionDryRunController extends AbstractController<
9+
TransactionDryRunService
10+
> {
11+
constructor(api: ApiPromise) {
12+
super(api, '/transaction/dry-run', new TransactionDryRunService(api));
13+
this.initRoutes();
14+
}
15+
16+
protected initRoutes(): void {
17+
this.router.post(
18+
this.path,
19+
TransactionDryRunController.catchWrap(this.dryRunTransaction)
20+
);
21+
}
22+
23+
private dryRunTransaction: IPostRequestHandler<ITx> = async (
24+
{ body: { tx }, query: { at } },
25+
res
26+
): Promise<void> => {
27+
if (!tx) {
28+
throw new BadRequest('Missing field `tx` on request body.');
29+
}
30+
31+
const hash =
32+
typeof at === 'string'
33+
? await this.getHashForBlock(at)
34+
: await this.api.rpc.chain.getFinalizedHead();
35+
36+
TransactionDryRunController.sanitizedSend(
37+
res,
38+
await this.service.dryRuntExtrinsic(hash, tx)
39+
);
40+
};
41+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
export { default as TransactionFeeEstimate } from './TransactionFeeEstimateController';
22
export { default as TransactionSubmit } from './TransactionSubmitController';
33
export { default as TransactionMaterial } from './TransactionMaterialController';
4+
export { default as TransactionDryRun } from './TransactionDryRunController';

src/main.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ async function main() {
8080
const txArtifactsController = new controllers.TransactionMaterial(api);
8181
const txFeeEstimateController = new controllers.TransactionFeeEstimate(api);
8282
const txSubmitController = new controllers.TransactionSubmit(api);
83+
const transactionDryRunController = new controllers.TransactionDryRun(api);
8384

8485
// Create our App
8586
const app = new App({
@@ -101,6 +102,7 @@ async function main() {
101102
txArtifactsController,
102103
txFeeEstimateController,
103104
txSubmitController,
105+
transactionDryRunController,
104106
],
105107
postMiddleware: [
106108
middleware.txError,
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
import { BlockHash } from '@polkadot/types/interfaces';
2+
3+
import {
4+
ITransactionDryRun,
5+
TransactionResultType,
6+
ValidityErrorType,
7+
} from '../../types/responses';
8+
import { AbstractService } from '../AbstractService';
9+
import { extractCauseAndStack } from './extractCauseAndStack';
10+
11+
/**
12+
* Dry run an extrinsic.
13+
*
14+
* Returns:
15+
* - `at`:
16+
* - `hash`: The block's hash.
17+
* - `height`: The block's height.
18+
* - `dryRunResult`:
19+
* - `resultType`: Either `DispatchOutcome` if the construction is valid
20+
* or `TransactionValidityError` if the transaction has invalid construction.
21+
* - `result`: If there was an error it will be the cause of the error. If the
22+
* transaction executed correctly it will be `Ok: []`.
23+
* - `validityErrorType`: Only present if the `resultType` is
24+
* `TransactionValidityError`. Either `InvalidTransaction` or `UnknownTransaction`.
25+
*
26+
* References:
27+
* - `UnknownTransaction`: https://crates.parity.io/sp_runtime/transaction_validity/enum.UnknownTransaction.html
28+
* - `InvalidTransaction`: https://crates.parity.io/sp_runtime/transaction_validity/enum.InvalidTransaction.html
29+
*/
30+
export class TransactionDryRunService extends AbstractService {
31+
async dryRuntExtrinsic(
32+
hash: BlockHash,
33+
extrinsic: string
34+
): Promise<ITransactionDryRun> {
35+
const api = await this.ensureMeta(hash);
36+
37+
try {
38+
const [applyExtrinsicResult, { number }] = await Promise.all([
39+
api.rpc.system.dryRun(extrinsic, hash),
40+
api.rpc.chain.getHeader(hash),
41+
]);
42+
43+
let dryRunResult;
44+
if (applyExtrinsicResult.isOk) {
45+
dryRunResult = {
46+
resultType: TransactionResultType.DispatchOutcome,
47+
result: applyExtrinsicResult.asOk,
48+
};
49+
} else {
50+
const { asError } = applyExtrinsicResult;
51+
dryRunResult = {
52+
resultType: TransactionResultType.TransactionValidityError,
53+
result: asError.isInvalid
54+
? asError.asInvalid
55+
: asError.asUnknown,
56+
validityErrorType: asError.isInvalid
57+
? ValidityErrorType.Invalid
58+
: ValidityErrorType.Unknown,
59+
};
60+
}
61+
62+
return {
63+
at: {
64+
hash,
65+
height: number.unwrap().toString(10),
66+
},
67+
dryRunResult,
68+
};
69+
} catch (err) {
70+
const { cause, stack } = extractCauseAndStack(err);
71+
72+
throw {
73+
at: {
74+
hash,
75+
},
76+
error: 'Unable to dry-run transaction',
77+
extrinsic,
78+
cause,
79+
stack,
80+
};
81+
}
82+
}
83+
}

src/services/transaction/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
export * from './TransactionSubmitService';
22
export * from './TransactionFeeEstimateService';
33
export * from './TransactionMaterialService';
4+
export * from './TransactionDryRunService';
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import {
2+
DispatchOutcome,
3+
InvalidTransaction,
4+
UnknownTransaction,
5+
} from '@polkadot/types/interfaces';
6+
7+
import { IAt } from '.';
8+
9+
export enum TransactionResultType {
10+
TransactionValidityError = 'TransactionValidityError',
11+
DispatchOutcome = 'DispatchOutcome',
12+
}
13+
14+
export enum ValidityErrorType {
15+
Invalid = 'InvalidTransaction',
16+
Unknown = 'UnknownTransaction',
17+
}
18+
19+
export type TransactionResult =
20+
| DispatchOutcome
21+
| InvalidTransaction
22+
| UnknownTransaction;
23+
24+
export interface ITransactionDryRun {
25+
at: IAt;
26+
dryRunResult: {
27+
resultType: TransactionResultType;
28+
result: TransactionResult;
29+
validityErrorType?: ValidityErrorType;
30+
};
31+
}

src/types/responses/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,4 @@ export * from './AccountStakingPayouts';
1717
export * from './NodeNetwork';
1818
export * from './NodeVersion';
1919
export * from './NodeTransactionPool';
20+
export * from './TransactionDryRun';

0 commit comments

Comments
 (0)