Skip to content
This repository has been archived by the owner on Jan 18, 2024. It is now read-only.

Commit

Permalink
Add send_transaction2 support (#8)
Browse files Browse the repository at this point in the history
* add SendTransaction2Args, send_transaction2 rpc method, calls send_transaction2 unit test

* add text-encoding dev dependency to run unit tests

* update api.transact

* minor

* update GetInfoResult

* add transactWithRetry unit test

* add useOldRPC and useOldSendRPC options

* minor

* rm retryTrx, make like cleos, use retry block num and retry irreversible, add assertion, pass retry blocks if not irreversible, make num blocks optional param

* update json rpc unit tests with blocking 10 blocks and blocking irreversible

* update node test, add irreversible check, update to 10 blocks

* update README.md, add '?' for optional params

* use 30 blocks versus 180 for irreversible

* use send_transaction2 rpc by default without retry trx feature

* enable failure trace default, make default send_transaction2 rpc

Co-authored-by: NatPDeveloper <you@example.com>
  • Loading branch information
NatPDeveloper and NatPDeveloper authored Aug 8, 2022
1 parent 0255c3c commit 251beab
Show file tree
Hide file tree
Showing 8 changed files with 294 additions and 7 deletions.
10 changes: 9 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,14 @@ const api = new Api({ rpc, signatureProvider, textDecoder: new TextDecoder(), te

`transact()` is used to sign and push transactions onto the blockchain with an optional configuration object parameter. This parameter can override the default value of `broadcast: true`, and can be used to fill TAPOS fields given `blocksBehind` and `expireSeconds`. Given no configuration options, transactions are expected to be unpacked with TAPOS fields (`expiration`, `ref_block_num`, `ref_block_prefix`) and will automatically be broadcast onto the chain.

With the introduction of Mandel v3.1 the retry transaction feature also adds 5 new optional fields to the configuration object:

- `useOldRPC`: use old RPC `push_transaction`, rather than new RPC send_transaction
- `useOldSendRPC`: use old RPC `/v1/chain/send_transaction`, rather than new RPC `/v1/chain/send_transaction2`
- `returnFailureTrace`: return partial traces on failed transactions
- `retryTrxNumBlocks`: request node to retry transaction until in a block of given height, blocking call
- `retryIrreversible`: request node to retry transaction until it is irreversible or expires, blocking call

```js
(async () => {
const result = await api.transact({
Expand All @@ -104,7 +112,7 @@ const api = new Api({ rpc, signatureProvider, textDecoder: new TextDecoder(), te
}]
}, {
blocksBehind: 3,
expireSeconds: 30,
expireSeconds: 30
});
console.dir(result);
})();
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
"jest-fetch-mock": "^3.0.3",
"none": "^1.0.0",
"rimraf": "^3.0.2",
"text-encoding": "^0.7.0",
"ts-jest": "^26.5.6",
"ts-loader": "^9.2.3",
"typedoc": "^0.23.5",
Expand Down
105 changes: 100 additions & 5 deletions src/eosjs-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

import { AbiProvider, AuthorityProvider, BinaryAbi, CachedAbi, SignatureProvider } from './eosjs-api-interfaces';
import { JsonRpc } from './eosjs-jsonrpc';
import { Abi, GetInfoResult, PushTransactionArgs } from './eosjs-rpc-interfaces';
import { Abi, GetInfoResult, PushTransactionArgs, SendTransaction2Args } from './eosjs-rpc-interfaces';
import * as ser from './eosjs-serialize';

const abiAbi = require('../src/abi.abi.json');
Expand Down Expand Up @@ -223,20 +223,60 @@ export class Api {
* Named Parameters:
* * `broadcast`: broadcast this transaction?
* * `sign`: sign this transaction?
* * `useOldRPC`: use old RPC push_transaction, rather than new RPC send_transaction?
* * `useOldSendRPC`: use old RPC /v1/chain/send_transaction, rather than new RPC /v1/chain/send_transaction2?
* * `returnFailureTrace`: return partial traces on failed transactions?
* * `retryTrxNumBlocks`: request node to retry transaction until in a block of given height, blocking call?
* * `retryIrreversible`: request node to retry transaction until it is irreversible or expires, blocking call?
* * If both `blocksBehind` and `expireSeconds` are present,
* then fetch the block which is `blocksBehind` behind head block,
* use it as a reference for TAPoS, and expire the transaction `expireSeconds` after that block's time.
* @returns node response if `broadcast`, `{signatures, serializedTransaction}` if `!broadcast`
*/
public async transact(transaction: any, { broadcast = true, sign = true, blocksBehind, expireSeconds }:
{ broadcast?: boolean; sign?: boolean; blocksBehind?: number; expireSeconds?: number; } = {}): Promise<any> {
public async transact(transaction: any, {
broadcast = true,
sign = true,
blocksBehind,
expireSeconds,
useOldRPC = false,
useOldSendRPC = false,
returnFailureTrace = true,
retryTrxNumBlocks = 0,
retryIrreversible = false
}:
{
broadcast?: boolean;
sign?: boolean;
blocksBehind?: number;
expireSeconds?: number;
useOldRPC?: boolean;
useOldSendRPC?: boolean;
returnFailureTrace?: boolean;
retryTrxNumBlocks?: number;
retryIrreversible?: boolean;
} = {}
): Promise<any> {
let info: GetInfoResult;

if (!this.chainId) {
info = await this.rpc.get_info();
this.chainId = info.chain_id;
}

const isRetry = retryIrreversible || retryTrxNumBlocks > 0;

if(isRetry) {
if(info.server_version_string < "v3.1.0") {
throw new Error(`Retry transaction feature unavailable for nodeos :${info.server_version_string}`);
}
if (useOldRPC || useOldSendRPC) {
throw new Error('Retry transaction feature not compatible with old RPC');
}
if(retryIrreversible && retryTrxNumBlocks > 0) {
throw new Error('Must specify retry irreversible or until given block height');
}
}

if (typeof blocksBehind === 'number' && expireSeconds) { // use config fields to generate TAPOS if they exist
if (!info) {
info = await this.rpc.get_info();
Expand Down Expand Up @@ -273,12 +313,35 @@ export class Api {
});
}
if (broadcast) {
return this.pushSignedTransaction(pushTransactionArgs);
if(isRetry) {
let params: SendTransaction2Args = {
return_failure_trace: returnFailureTrace,
retry_trx: true,
transaction: pushTransactionArgs
}
if(retryTrxNumBlocks > 0) {
params = {
...params,
retry_trx_num_blocks: retryTrxNumBlocks
}
}
return this.sendSignedTransaction2(params);
} else if(useOldSendRPC) {
return this.sendSignedTransaction(pushTransactionArgs);
} else if(useOldRPC) {
return this.pushSignedTransaction(pushTransactionArgs);
} else {
return this.sendSignedTransaction2({
return_failure_trace: returnFailureTrace,
retry_trx: false,
transaction: pushTransactionArgs
});
}
}
return pushTransactionArgs;
}

/** Broadcast a signed transaction */
/** Broadcast a signed transaction using push_transaction RPC */
public async pushSignedTransaction(
{ signatures, serializedTransaction, serializedContextFreeData }: PushTransactionArgs
): Promise<any> {
Expand All @@ -289,6 +352,38 @@ export class Api {
});
}

/** Broadcast a signed transaction using send_transaction RPC */
public async sendSignedTransaction(
{ signatures, serializedTransaction, serializedContextFreeData }: PushTransactionArgs
): Promise<any> {
return this.rpc.send_transaction({
signatures,
serializedTransaction,
serializedContextFreeData
});
}

/** Broadcast a signed transaction using send_transaction2 RPC*/
public async sendSignedTransaction2(
{
return_failure_trace,
retry_trx,
retry_trx_num_blocks,
transaction: { signatures, serializedTransaction, serializedContextFreeData }
}: SendTransaction2Args
): Promise<any> {
return this.rpc.send_transaction2({
return_failure_trace,
retry_trx,
retry_trx_num_blocks,
transaction: {
signatures,
serializedTransaction,
serializedContextFreeData
}
});
}

// eventually break out into TransactionValidator class
private hasRequiredTaposFields({ expiration, ref_block_num, ref_block_prefix }: any): boolean {
return !!(expiration && typeof(ref_block_num) === 'number' && typeof(ref_block_prefix) === 'number');
Expand Down
24 changes: 23 additions & 1 deletion src/eosjs-jsonrpc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

import { AbiProvider, AuthorityProvider, AuthorityProviderArgs, BinaryAbi } from './eosjs-api-interfaces';
import { base64ToBinary, convertLegacyPublicKeys } from './eosjs-numeric';
import { GetAbiResult, GetBlockResult, GetCodeResult, GetInfoResult, GetRawCodeAndAbiResult, PushTransactionArgs, GetBlockHeaderStateResult } from "./eosjs-rpc-interfaces" // tslint:disable-line
import { GetAbiResult, GetBlockResult, GetCodeResult, GetInfoResult, GetRawCodeAndAbiResult, PushTransactionArgs, GetBlockHeaderStateResult, SendTransaction2Args } from "./eosjs-rpc-interfaces" // tslint:disable-line
import { RpcError } from './eosjs-rpcerror';

function arrayToHex(data: Uint8Array) {
Expand Down Expand Up @@ -212,6 +212,28 @@ export class JsonRpc implements AuthorityProvider, AbiProvider {
});
}

/** Send a serialized transaction2 */
public async send_transaction2(
{
return_failure_trace,
retry_trx,
retry_trx_num_blocks,
transaction: { signatures, serializedTransaction, serializedContextFreeData }
}: SendTransaction2Args
): Promise<any> {
return await this.fetch('/v1/chain/send_transaction2', {
return_failure_trace,
retry_trx,
retry_trx_num_blocks,
transaction: {
signatures,
compression: 0,
packed_context_free_data: arrayToHex(serializedContextFreeData || new Uint8Array(0)),
packed_trx: arrayToHex(serializedTransaction),
}
});
}

/** Raw call to `/v1/db_size/get` */
public async db_size_get() { return await this.fetch('/v1/db_size/get', {}); }

Expand Down
16 changes: 16 additions & 0 deletions src/eosjs-rpc-interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,14 @@ export interface GetInfoResult {
virtual_block_net_limit: number;
block_cpu_limit: number;
block_net_limit: number;
server_version_string: string;
fork_db_head_block_num: number,
fork_db_head_block_id: string,
server_full_version_string: string,
total_cpu_weight: string,
total_net_weight: string,
earliest_available_block_num: number,
last_irreversible_block_time: string
}

/** Return value of `/v1/chain/get_raw_code_and_abi` */
Expand All @@ -120,3 +128,11 @@ export interface PushTransactionArgs {
serializedTransaction: Uint8Array;
serializedContextFreeData?: Uint8Array;
}

/** Arguments for `send_transaction2` */
export interface SendTransaction2Args {
return_failure_trace: boolean,
retry_trx: boolean,
retry_trx_num_blocks?: number,
transaction: PushTransactionArgs
}
86 changes: 86 additions & 0 deletions src/tests/eosjs-jsonrpc.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -606,6 +606,92 @@ describe('JSON RPC', () => {
expect(fetch).toBeCalledWith(endpoint + expPath, expParams);
});

it('calls send_transaction2 irreversible', async () => {
const expPath = '/v1/chain/send_transaction2';
const signatures = [
'George Washington',
'John Hancock',
'Abraham Lincoln',
];
const serializedTransaction = new Uint8Array([
0, 16, 32, 128, 255,
]);

const expReturn = { data: '12345' };
const callParams = {
return_failure_trace: false,
retry_trx: false,
transaction: {
signatures,
serializedTransaction,
}
};
const expParams = {
body: JSON.stringify({
return_failure_trace: false,
retry_trx: false,
transaction: {
signatures,
compression: 0,
packed_context_free_data: '',
packed_trx: '00102080ff',
}
}),
method: 'POST',
};

fetchMock.once(JSON.stringify(expReturn));

const response = await jsonRpc.send_transaction2(callParams);

expect(response).toEqual(expReturn);
expect(fetch).toBeCalledWith(endpoint + expPath, expParams);
});

it('calls send_transaction2 10 blocks', async () => {
const expPath = '/v1/chain/send_transaction2';
const signatures = [
'George Washington',
'John Hancock',
'Abraham Lincoln',
];
const serializedTransaction = new Uint8Array([
0, 16, 32, 128, 255,
]);

const expReturn = { data: '12345' };
const callParams = {
return_failure_trace: false,
retry_trx: false,
retry_trx_num_blocks: 10,
transaction: {
signatures,
serializedTransaction,
}
};
const expParams = {
body: JSON.stringify({
return_failure_trace: false,
retry_trx: false,
retry_trx_num_blocks: 10,
transaction: {
signatures,
compression: 0,
packed_context_free_data: '',
packed_trx: '00102080ff',
}
}),
method: 'POST',
};

fetchMock.once(JSON.stringify(expReturn));

const response = await jsonRpc.send_transaction2(callParams);

expect(response).toEqual(expReturn);
expect(fetch).toBeCalledWith(endpoint + expPath, expParams);
});

it('calls db_size_get', async () => {
const expPath = '/v1/db_size/get';
const expReturn = { data: '12345' };
Expand Down
Loading

0 comments on commit 251beab

Please sign in to comment.