diff options
Diffstat (limited to 'packages/subproviders/src')
-rw-r--r-- | packages/subproviders/src/globals.d.ts | 20 | ||||
-rw-r--r-- | packages/subproviders/src/index.ts | 1 | ||||
-rw-r--r-- | packages/subproviders/src/subproviders/ledger.ts | 2 | ||||
-rw-r--r-- | packages/subproviders/src/subproviders/nonce_tracker.ts | 97 | ||||
-rw-r--r-- | packages/subproviders/src/subproviders/redundant_rpc.ts | 1 | ||||
-rw-r--r-- | packages/subproviders/src/types.ts | 13 |
6 files changed, 134 insertions, 0 deletions
diff --git a/packages/subproviders/src/globals.d.ts b/packages/subproviders/src/globals.d.ts index 53457fa24..6f344dcd3 100644 --- a/packages/subproviders/src/globals.d.ts +++ b/packages/subproviders/src/globals.d.ts @@ -13,7 +13,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 +99,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/ledger.ts b/packages/subproviders/src/subproviders/ledger.ts index 7267a793e..5966a88bb 100644 --- a/packages/subproviders/src/subproviders/ledger.ts +++ b/packages/subproviders/src/subproviders/ledger.ts @@ -60,6 +60,8 @@ export class LedgerSubprovider extends Subprovider { public setPathIndex(pathIndex: number) { this._derivationPathIndex = pathIndex; } + // Required to implement this public interface which doesn't conform to our linting rule. + // tslint:disable-next-line:async-suffix public async handleRequest( payload: Web3.JSONRPCRequestPayload, next: () => void, diff --git a/packages/subproviders/src/subproviders/nonce_tracker.ts b/packages/subproviders/src/subproviders/nonce_tracker.ts new file mode 100644 index 000000000..2f94ea581 --- /dev/null +++ b/packages/subproviders/src/subproviders/nonce_tracker.ts @@ -0,0 +1,97 @@ +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 { + BlockParamLiteral, + ErrorCallback, + JSONRPCPayload, + NonceSubproviderErrors, + OptionalNextCallback, +} 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]; + if (_.isUndefined(raw)) { + throw new Error(NonceSubproviderErrors.EmptyParametersFound); + } + const rawData = ethUtil.toBuffer(raw); + const transaction = new EthereumTx(rawData); + return transaction; + } + private static _determineAddress(payload: JSONRPCPayload): string { + let address: string; + switch (payload.method) { + case 'eth_getTransactionCount': + address = payload.params[0].toLowerCase(); + return address; + case 'eth_sendRawTransaction': + const transaction = NonceTrackerSubprovider._reconstructTransaction(payload); + address = `0x${transaction.getSenderAddress().toString('hex')}`.toLowerCase(); + return address; + default: + throw new Error(NonceSubproviderErrors.CannotDetermineAddressFromPayload); + } + } + // Required to implement this public interface which doesn't conform to our linting rule. + // tslint:disable-next-line:async-suffix + public async handleRequest(payload: JSONRPCPayload, next: OptionalNextCallback, end: ErrorCallback): Promise<void> { + switch (payload.method) { + case 'eth_getTransactionCount': + const requestDefaultBlock = providerEngineUtils.blockTagForPayload(payload); + if (requestDefaultBlock === BlockParamLiteral.Pending) { + const address = NonceTrackerSubprovider._determineAddress(payload); + const cachedResult = this._nonceCache[address]; + if (!_.isUndefined(cachedResult)) { + return end(null, cachedResult); + } else { + return next((requestError: Error | null, requestResult: any, cb: any) => { + if (_.isNull(requestError)) { + this._nonceCache[address] = requestResult as string; + } + cb(); + }); + } + } else { + return next(); + } + case 'eth_sendRawTransaction': + return next((sendTransactionError: Error | null, txResult: any, cb: any) => { + if (_.isNull(sendTransactionError)) { + this._handleSuccessfulTransaction(payload); + } else { + this._handleSendTransactionError(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 _handleSendTransactionError(payload: JSONRPCPayload, err: Error): void { + const address = NonceTrackerSubprovider._determineAddress(payload); + if (this._nonceCache[address] && _.includes(err.message, NONCE_TOO_LOW_ERROR_MESSAGE)) { + delete this._nonceCache[address]; + } + } +} diff --git a/packages/subproviders/src/subproviders/redundant_rpc.ts b/packages/subproviders/src/subproviders/redundant_rpc.ts index a3cb463a8..5a94f93d7 100644 --- a/packages/subproviders/src/subproviders/redundant_rpc.ts +++ b/packages/subproviders/src/subproviders/redundant_rpc.ts @@ -35,6 +35,7 @@ export class RedundantRPCSubprovider extends Subprovider { }); }); } + // Required to implement this public interface which doesn't conform to our linting rule. // tslint:disable-next-line:async-suffix public async handleRequest( payload: JSONRPCPayload, diff --git a/packages/subproviders/src/types.ts b/packages/subproviders/src/types.ts index 3db8be943..86b118767 100644 --- a/packages/subproviders/src/types.ts +++ b/packages/subproviders/src/types.ts @@ -112,3 +112,16 @@ export enum LedgerSubproviderErrors { SenderInvalidOrNotSupplied = 'SENDER_INVALID_OR_NOT_SUPPLIED', MultipleOpenConnectionsDisallowed = 'MULTIPLE_OPEN_CONNECTIONS_DISALLOWED', } + +export enum NonceSubproviderErrors { + EmptyParametersFound = 'EMPTY_PARAMETERS_FOUND', + CannotDetermineAddressFromPayload = 'CANNOT_DETERMINE_ADDRESS_FROM_PAYLOAD', +} + +// Re-defined BlockParamLiteral here, rather than import it from 0x.js. +export enum BlockParamLiteral { + Pending = 'pending', +} + +export type OptionalNextCallback = (callback?: (err: Error | null, result: any, cb: any) => void) => void; +export type ErrorCallback = (err: Error | null, data?: any) => void; |