Remove re-fetch of transaction count on error

This commit is contained in:
Jacob Evans
2018-02-05 14:27:58 -08:00
parent dae6f28f8a
commit fc3058c1e2
5 changed files with 41 additions and 33 deletions

View File

@@ -2,7 +2,7 @@
## v0.4.1 - _Febuary 2, 2018_
* Added NonceTrackerSubprovider
* Added NonceTrackerSubprovider (#355)
## v0.4.0 - _January 28, 2018_

View File

@@ -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,

View File

@@ -4,42 +4,49 @@ 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 {
BlockParamLiteral,
ErrorCallback,
JSONRPCPayload,
NonceSubproviderErrors,
OptionalNextCallback,
} from '../types';
import { Subprovider } from './subprovider';
const NONCE_TOO_LOW_ERROR_MESSAGE = 'Transaction nonce is too low';
export type OptionalNextCallback = (callback?: (err: Error | null, result: any, cb: any) => void) => void;
export type ErrorCallback = (err: Error | null, data?: any) => void;
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('Invalid transaction: empty parameters');
throw new Error(NonceSubproviderErrors.EmptyParametersFound);
}
const rawData = ethUtil.toBuffer(raw);
return new EthereumTx(rawData);
const transaction = new EthereumTx(rawData);
return transaction;
}
private static _determineAddress(payload: JSONRPCPayload): string {
let address: string;
switch (payload.method) {
case 'eth_getTransactionCount':
return payload.params[0].toLowerCase();
address = payload.params[0].toLowerCase();
return address;
case 'eth_sendRawTransaction':
const transaction = NonceTrackerSubprovider._reconstructTransaction(payload);
return `0x${transaction.getSenderAddress().toString('hex')}`.toLowerCase();
address = `0x${transaction.getSenderAddress().toString('hex')}`.toLowerCase();
return address;
default:
throw new Error(`Invalid Method: ${payload.method}`);
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 blockTag = providerEngineUtils.blockTagForPayload(payload);
if (!_.isNull(blockTag) && blockTag === 'pending') {
const requestDefaultBlock = providerEngineUtils.blockTagForPayload(payload);
if (requestDefaultBlock === BlockParamLiteral.Pending) {
const address = NonceTrackerSubprovider._determineAddress(payload);
const cachedResult = this._nonceCache[address];
if (!_.isUndefined(cachedResult)) {
@@ -56,11 +63,11 @@ export class NonceTrackerSubprovider extends Subprovider {
return next();
}
case 'eth_sendRawTransaction':
return next(async (sendTransactionError: Error | null, txResult: any, cb: any) => {
return next((sendTransactionError: Error | null, txResult: any, cb: any) => {
if (_.isNull(sendTransactionError)) {
this._handleSuccessfulTransaction(payload);
} else {
await this._handleSendTransactionErrorAsync(payload, sendTransactionError);
this._handleSendTransactionError(payload, sendTransactionError);
}
cb();
});
@@ -81,25 +88,10 @@ export class NonceTrackerSubprovider extends Subprovider {
nextHexNonce = `0x${nextHexNonce}`;
this._nonceCache[address] = nextHexNonce;
}
private async _handleSendTransactionErrorAsync(payload: JSONRPCPayload, err: Error): Promise<void> {
private _handleSendTransactionError(payload: JSONRPCPayload, err: Error): 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;
if (this._nonceCache[address] && _.includes(err.message, NONCE_TOO_LOW_ERROR_MESSAGE)) {
delete this._nonceCache[address];
}
}
}

View File

@@ -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,

View File

@@ -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;