aboutsummaryrefslogtreecommitdiffstats
path: root/packages/subproviders/src
diff options
context:
space:
mode:
authorJacob Evans <jacob@dekz.net>2018-01-31 13:19:47 +0800
committerJacob Evans <jacob@dekz.net>2018-02-02 08:06:22 +0800
commitead990a734e0caf0ce0e5d0297c487756894acf2 (patch)
tree6adac8f131439c4fec897fb98c2f5626aa8c7548 /packages/subproviders/src
parentd4631e14b2203bfd95b995d25819d8d9cb834336 (diff)
downloaddexon-sol-tools-ead990a734e0caf0ce0e5d0297c487756894acf2.tar
dexon-sol-tools-ead990a734e0caf0ce0e5d0297c487756894acf2.tar.gz
dexon-sol-tools-ead990a734e0caf0ce0e5d0297c487756894acf2.tar.bz2
dexon-sol-tools-ead990a734e0caf0ce0e5d0297c487756894acf2.tar.lz
dexon-sol-tools-ead990a734e0caf0ce0e5d0297c487756894acf2.tar.xz
dexon-sol-tools-ead990a734e0caf0ce0e5d0297c487756894acf2.tar.zst
dexon-sol-tools-ead990a734e0caf0ce0e5d0297c487756894acf2.zip
Nonce tracker subprovider
Caches the nonce when a request to getTransactionCount is made and increments the pending nonce after successful transactions
Diffstat (limited to 'packages/subproviders/src')
-rw-r--r--packages/subproviders/src/globals.d.ts21
-rw-r--r--packages/subproviders/src/index.ts1
-rw-r--r--packages/subproviders/src/subproviders/nonce_tracker.ts112
3 files changed, 134 insertions, 0 deletions
diff --git a/packages/subproviders/src/globals.d.ts b/packages/subproviders/src/globals.d.ts
index 53457fa24..595bae89e 100644
--- a/packages/subproviders/src/globals.d.ts
+++ b/packages/subproviders/src/globals.d.ts
@@ -1,3 +1,4 @@
+
declare module 'dirty-chai';
declare module 'es6-promisify';
@@ -13,7 +14,9 @@ declare module 'ethereumjs-tx' {
public r: Buffer;
public s: Buffer;
public v: Buffer;
+ public nonce: Buffer;
public serialize(): Buffer;
+ public getSenderAddress(): Buffer;
constructor(txParams: any);
}
export = EthereumTx;
@@ -97,6 +100,24 @@ declare module 'web3-provider-engine' {
}
export = Web3ProviderEngine;
}
+declare module 'web3-provider-engine/util/rpc-cache-utils' {
+ class ProviderEngineRpcUtils {
+ public static blockTagForPayload(payload: any): string|null;
+ }
+ export = ProviderEngineRpcUtils;
+}
+declare module 'web3-provider-engine/subproviders/fixture' {
+ import * as Web3 from 'web3';
+ class FixtureSubprovider {
+ constructor(staticResponses: any);
+ public handleRequest(
+ payload: Web3.JSONRPCRequestPayload,
+ next: () => void,
+ end: (err: Error | null, data?: any) => void,
+ ): void;
+ }
+ export = FixtureSubprovider;
+}
// hdkey declarations
declare module 'hdkey' {
diff --git a/packages/subproviders/src/index.ts b/packages/subproviders/src/index.ts
index 720c4362f..4b3be4efd 100644
--- a/packages/subproviders/src/index.ts
+++ b/packages/subproviders/src/index.ts
@@ -9,6 +9,7 @@ import { LedgerEthereumClient } from './types';
export { InjectedWeb3Subprovider } from './subproviders/injected_web3';
export { RedundantRPCSubprovider } from './subproviders/redundant_rpc';
export { LedgerSubprovider } from './subproviders/ledger';
+export { NonceTrackerSubprovider } from './subproviders/nonce_tracker';
export { ECSignature, LedgerWalletSubprovider, LedgerCommunicationClient } from './types';
/**
diff --git a/packages/subproviders/src/subproviders/nonce_tracker.ts b/packages/subproviders/src/subproviders/nonce_tracker.ts
new file mode 100644
index 000000000..540a91771
--- /dev/null
+++ b/packages/subproviders/src/subproviders/nonce_tracker.ts
@@ -0,0 +1,112 @@
+import { promisify } from '@0xproject/utils';
+import * as _ from 'lodash';
+
+import EthereumTx = require('ethereumjs-tx');
+import ethUtil = require('ethereumjs-util');
+import providerEngineUtils = require('web3-provider-engine/util/rpc-cache-utils');
+
+import { JSONRPCPayload } from '../types';
+
+import { Subprovider } from './subprovider';
+
+const NONCE_TOO_LOW_ERROR_MESSAGE = 'Transaction nonce is too low';
+
+export class NonceTrackerSubprovider extends Subprovider {
+ private _nonceCache: { [address: string]: string } = {};
+ private static _reconstructTransaction(payload: JSONRPCPayload): EthereumTx {
+ const raw = payload.params[0];
+ const transactionData = ethUtil.stripHexPrefix(raw);
+ const rawData = new Buffer(transactionData, 'hex');
+ return new EthereumTx(rawData);
+ }
+ private static _determineAddress(payload: JSONRPCPayload): string {
+ switch (payload.method) {
+ case 'eth_getTransactionCount':
+ return payload.params[0].toLowerCase();
+ case 'eth_sendRawTransaction':
+ const transaction = NonceTrackerSubprovider._reconstructTransaction(payload);
+ return `0x${transaction.getSenderAddress().toString('hex')}`.toLowerCase();
+ default:
+ throw new Error('Invalid Method');
+ }
+ }
+ constructor() {
+ super();
+ }
+ // tslint:disable-next-line:async-suffix
+ public async handleRequest(
+ payload: JSONRPCPayload,
+ next: (callback?: (err: Error | null, result: any, cb: any) => void) => void,
+ end: (err: Error | null, data?: any) => void,
+ ): Promise<void> {
+ switch (payload.method) {
+ case 'eth_getTransactionCount':
+ const blockTag = providerEngineUtils.blockTagForPayload(payload);
+ if (!_.isNull(blockTag) && blockTag === 'pending') {
+ const address = NonceTrackerSubprovider._determineAddress(payload);
+ const cachedResult = this._nonceCache[address];
+ if (cachedResult) {
+ end(null, cachedResult);
+ return;
+ } else {
+ next((requestError: Error | null, requestResult: any, cb: any) => {
+ if (_.isNull(requestError)) {
+ this._nonceCache[address] = requestResult as string;
+ }
+ cb();
+ return;
+ });
+ return;
+ }
+ } else {
+ next();
+ return;
+ }
+ case 'eth_sendRawTransaction':
+ return next(async (sendTransactionError: Error | null, txResult: any, cb: any) => {
+ if (_.isNull(sendTransactionError)) {
+ this._handleSuccessfulTransaction(payload);
+ } else {
+ await this._handleSendTransactionErrorAsync(payload, sendTransactionError);
+ }
+ cb();
+ });
+ default:
+ return next();
+ }
+ }
+ private _handleSuccessfulTransaction(payload: JSONRPCPayload): void {
+ const address = NonceTrackerSubprovider._determineAddress(payload);
+ const transaction = NonceTrackerSubprovider._reconstructTransaction(payload);
+ // Increment the nonce from the previous successfully submitted transaction
+ let nonce = ethUtil.bufferToInt(transaction.nonce);
+ nonce++;
+ let nextHexNonce = nonce.toString(16);
+ if (nextHexNonce.length % 2) {
+ nextHexNonce = `0${nextHexNonce}`;
+ }
+ nextHexNonce = `0x${nextHexNonce}`;
+ this._nonceCache[address] = nextHexNonce;
+ }
+ private async _handleSendTransactionErrorAsync(payload: JSONRPCPayload, err: Error): Promise<void> {
+ const address = NonceTrackerSubprovider._determineAddress(payload);
+ if (this._nonceCache[address]) {
+ if (_.includes(err.message, NONCE_TOO_LOW_ERROR_MESSAGE)) {
+ await this._handleNonceTooLowErrorAsync(address);
+ }
+ }
+ }
+ private async _handleNonceTooLowErrorAsync(address: string): Promise<void> {
+ const oldNonceInt = ethUtil.bufferToInt(new Buffer(this._nonceCache[address], 'hex'));
+ delete this._nonceCache[address];
+ const nonceResult = await this.emitPayloadAsync({
+ method: 'eth_getTransactionCount',
+ params: [address, 'pending'],
+ });
+ const nonce = nonceResult.result;
+ const latestNonceInt = ethUtil.bufferToInt(new Buffer(nonce, 'hex'));
+ if (latestNonceInt > oldNonceInt) {
+ this._nonceCache[address] = nonce;
+ }
+ }
+}