aboutsummaryrefslogtreecommitdiffstats
path: root/packages/subproviders/src/subproviders/nonce_tracker.ts
blob: 560f2dd68d91e2afb8c1458d8df95a06d30a879e (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
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) {
                        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(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;
        }
    }
}