Merge pull request #1080 from 0xProject/upgradeBlockstream

Fix dropped events issue in Order-watcher and Contract-wrappers subscriptions
This commit is contained in:
Fabio Berger
2018-09-25 14:59:37 +01:00
committed by GitHub
10 changed files with 166 additions and 75 deletions

View File

@@ -1,21 +1,36 @@
[
{
"timestamp": 1537875740,
"version": "2.0.0",
"changes": [
{
"note":
"Fixes dropped events in subscriptions by fetching logs by blockHash instead of blockNumber. Support for fetching by blockHash was added in Geth > v1.8.13 and Parity > v2.1.0. Infura works too.",
"pr": 1080
},
{
"note":
"Fix misunderstanding about blockstream interface callbacks and pass the raw JSON RPC responses to it",
"pr": 1080
}
]
},
{
"version": "1.0.5",
"changes": [
{
"note": "Dependencies updated"
}
]
],
"timestamp": 1537875740
},
{
"timestamp": 1537541580,
"version": "1.0.4",
"changes": [
{
"note": "Dependencies updated"
}
]
],
"timestamp": 1537541580
},
{
"version": "1.0.3",

View File

@@ -82,7 +82,7 @@
"@0xproject/utils": "^1.0.10",
"@0xproject/web3-wrapper": "^3.0.0",
"ethereum-types": "^1.0.7",
"ethereumjs-blockstream": "5.0.0",
"ethereumjs-blockstream": "6.0.0",
"ethereumjs-util": "^5.1.1",
"ethers": "3.0.22",
"js-sha3": "^0.7.0",

View File

@@ -1,14 +1,14 @@
import { AbiDecoder, intervalUtils, logUtils } from '@0xproject/utils';
import { Web3Wrapper } from '@0xproject/web3-wrapper';
import { marshaller, Web3Wrapper } from '@0xproject/web3-wrapper';
import {
BlockParamLiteral,
BlockWithoutTransactionData,
ContractAbi,
ContractArtifact,
FilterObject,
LogEntry,
LogWithDecodedArgs,
RawLog,
RawLogEntry,
} from 'ethereum-types';
import { Block, BlockAndLogStreamer, Log } from 'ethereumjs-blockstream';
import * as _ from 'lodash';
@@ -158,7 +158,8 @@ export abstract class ContractWrapper {
return addressIfExists;
}
}
private _onLogStateChanged<ArgsType extends ContractEventArgs>(isRemoved: boolean, log: LogEntry): void {
private _onLogStateChanged<ArgsType extends ContractEventArgs>(isRemoved: boolean, rawLog: RawLogEntry): void {
const log: LogEntry = marshaller.unmarshalLog(rawLog);
_.forEach(this._filters, (filter: FilterObject, filterToken: string) => {
if (filterUtils.matchesFilter(log, filter)) {
const decodedLog = this._tryToDecodeLogOrNoop(log) as LogWithDecodedArgs<ArgsType>;
@@ -175,8 +176,8 @@ export abstract class ContractWrapper {
throw new Error(ContractWrappersError.SubscriptionAlreadyPresent);
}
this._blockAndLogStreamerIfExists = new BlockAndLogStreamer(
this._getBlockOrNullAsync.bind(this),
this._web3Wrapper.getLogsAsync.bind(this._web3Wrapper),
this._blockstreamGetBlockOrNullAsync.bind(this),
this._blockstreamGetLogsAsync.bind(this),
ContractWrapper._onBlockAndLogStreamerError.bind(this, isVerbose),
);
const catchAllLogFilter = {};
@@ -196,12 +197,30 @@ export abstract class ContractWrapper {
);
}
// This method only exists in order to comply with the expected interface of Blockstream's constructor
private async _getBlockOrNullAsync(): Promise<BlockWithoutTransactionData | null> {
const blockIfExists = await this._web3Wrapper.getBlockIfExistsAsync.bind(this._web3Wrapper);
if (_.isUndefined(blockIfExists)) {
return null;
}
return blockIfExists;
private async _blockstreamGetBlockOrNullAsync(hash: string): Promise<Block | null> {
const shouldIncludeTransactionData = false;
const blockOrNull = await this._web3Wrapper.sendRawPayloadAsync<Block | null>({
method: 'eth_getBlockByHash',
params: [hash, shouldIncludeTransactionData],
});
return blockOrNull;
}
// This method only exists in order to comply with the expected interface of Blockstream's constructor
private async _blockstreamGetLatestBlockOrNullAsync(): Promise<Block | null> {
const shouldIncludeTransactionData = false;
const blockOrNull = await this._web3Wrapper.sendRawPayloadAsync<Block | null>({
method: 'eth_getBlockByNumber',
params: [BlockParamLiteral.Latest, shouldIncludeTransactionData],
});
return blockOrNull;
}
// This method only exists in order to comply with the expected interface of Blockstream's constructor
private async _blockstreamGetLogsAsync(filterOptions: FilterObject): Promise<RawLogEntry[]> {
const logs = await this._web3Wrapper.sendRawPayloadAsync<RawLogEntry[]>({
method: 'eth_getLogs',
params: [filterOptions],
});
return logs as RawLogEntry[];
}
// HACK: This should be a package-scoped method (which doesn't exist in TS)
// We don't want this method available in the public interface for all classes
@@ -221,14 +240,14 @@ export abstract class ContractWrapper {
delete this._blockAndLogStreamerIfExists;
}
private async _reconcileBlockAsync(): Promise<void> {
const latestBlockIfExists = await this._web3Wrapper.getBlockIfExistsAsync(BlockParamLiteral.Latest);
if (_.isUndefined(latestBlockIfExists)) {
const latestBlockOrNull = await this._blockstreamGetLatestBlockOrNullAsync();
if (_.isNull(latestBlockOrNull)) {
return; // noop
}
// We need to coerce to Block type cause Web3.Block includes types for mempool blocks
if (!_.isUndefined(this._blockAndLogStreamerIfExists)) {
// If we clear the interval while fetching the block - this._blockAndLogStreamer will be undefined
await this._blockAndLogStreamerIfExists.reconcileNewBlock((latestBlockIfExists as any) as Block);
await this._blockAndLogStreamerIfExists.reconcileNewBlock(latestBlockOrNull);
}
}
}

View File

@@ -181,6 +181,7 @@ export interface CallData extends CallTxDataBase {
export interface FilterObject {
fromBlock?: number | string;
toBlock?: number | string;
blockHash?: string;
address?: string;
topics?: LogTopic[];
}

View File

@@ -1,21 +1,36 @@
[
{
"timestamp": 1537875740,
"version": "2.0.0",
"changes": [
{
"note":
"Fixes dropped events issue by fetching logs by blockHash instead of blockNumber. Support for fetching by blockHash was added in Geth > v1.8.13 and Parity > v2.1.0. Infura works too.",
"pr": 1080
},
{
"note":
"Fix misunderstanding about blockstream interface callbacks and pass the raw JSON RPC responses to it",
"pr": 1080
}
]
},
{
"version": "1.0.5",
"changes": [
{
"note": "Dependencies updated"
}
]
],
"timestamp": 1537875740
},
{
"timestamp": 1537541580,
"version": "1.0.4",
"changes": [
{
"note": "Dependencies updated"
}
]
],
"timestamp": 1537541580
},
{
"version": "1.0.3",

View File

@@ -82,7 +82,7 @@
"@0xproject/web3-wrapper": "^3.0.0",
"bintrees": "^1.0.2",
"ethereum-types": "^1.0.7",
"ethereumjs-blockstream": "5.0.0",
"ethereumjs-blockstream": "6.0.0",
"ethers": "3.0.22",
"lodash": "^4.17.5"
},

View File

@@ -1,6 +1,6 @@
import { intervalUtils, logUtils } from '@0xproject/utils';
import { Web3Wrapper } from '@0xproject/web3-wrapper';
import { BlockParamLiteral, BlockWithoutTransactionData, LogEntry, Provider } from 'ethereum-types';
import { marshaller, Web3Wrapper } from '@0xproject/web3-wrapper';
import { BlockParamLiteral, FilterObject, LogEntry, Provider, RawLogEntry } from 'ethereum-types';
import { Block, BlockAndLogStreamer, Log } from 'ethereumjs-blockstream';
import * as _ from 'lodash';
@@ -20,7 +20,6 @@ enum LogEventState {
*/
export class EventWatcher {
private readonly _web3Wrapper: Web3Wrapper;
private readonly _stateLayer: BlockParamLiteral;
private readonly _isVerbose: boolean;
private _blockAndLogStreamerIfExists: BlockAndLogStreamer<Block, Log> | undefined;
private _blockAndLogStreamIntervalIfExists?: NodeJS.Timer;
@@ -35,7 +34,6 @@ export class EventWatcher {
) {
this._isVerbose = isVerbose;
this._web3Wrapper = new Web3Wrapper(provider);
this._stateLayer = stateLayer;
this._pollingIntervalMs = _.isUndefined(pollingIntervalIfExistsMs)
? DEFAULT_EVENT_POLLING_INTERVAL_MS
: pollingIntervalIfExistsMs;
@@ -62,8 +60,8 @@ export class EventWatcher {
throw new Error(OrderWatcherError.SubscriptionAlreadyPresent);
}
this._blockAndLogStreamerIfExists = new BlockAndLogStreamer(
this._getBlockOrNullAsync.bind(this),
this._web3Wrapper.getLogsAsync.bind(this._web3Wrapper),
this._blockstreamGetBlockOrNullAsync.bind(this),
this._blockstreamGetLogsAsync.bind(this),
this._onBlockAndLogStreamerError.bind(this),
);
const catchAllLogFilter = {};
@@ -83,12 +81,30 @@ export class EventWatcher {
);
}
// This method only exists in order to comply with the expected interface of Blockstream's constructor
private async _getBlockOrNullAsync(): Promise<BlockWithoutTransactionData | null> {
const blockIfExists = await this._web3Wrapper.getBlockIfExistsAsync.bind(this._web3Wrapper);
if (_.isUndefined(blockIfExists)) {
return null;
}
return blockIfExists;
private async _blockstreamGetBlockOrNullAsync(hash: string): Promise<Block | null> {
const shouldIncludeTransactionData = false;
const blockOrNull = await this._web3Wrapper.sendRawPayloadAsync<Block | null>({
method: 'eth_getBlockByHash',
params: [hash, shouldIncludeTransactionData],
});
return blockOrNull;
}
// This method only exists in order to comply with the expected interface of Blockstream's constructor
private async _blockstreamGetLatestBlockOrNullAsync(): Promise<Block | null> {
const shouldIncludeTransactionData = false;
const blockOrNull = await this._web3Wrapper.sendRawPayloadAsync<Block | null>({
method: 'eth_getBlockByNumber',
params: [BlockParamLiteral.Latest, shouldIncludeTransactionData],
});
return blockOrNull;
}
// This method only exists in order to comply with the expected interface of Blockstream's constructor
private async _blockstreamGetLogsAsync(filterOptions: FilterObject): Promise<RawLogEntry[]> {
const logs = await this._web3Wrapper.sendRawPayloadAsync<RawLogEntry[]>({
method: 'eth_getLogs',
params: [filterOptions],
});
return logs as RawLogEntry[];
}
private _stopBlockAndLogStream(): void {
if (_.isUndefined(this._blockAndLogStreamerIfExists)) {
@@ -103,19 +119,20 @@ export class EventWatcher {
private async _onLogStateChangedAsync(
callback: EventWatcherCallback,
isRemoved: boolean,
log: LogEntry,
rawLog: RawLogEntry,
): Promise<void> {
const log: LogEntry = marshaller.unmarshalLog(rawLog);
await this._emitDifferencesAsync(log, isRemoved ? LogEventState.Removed : LogEventState.Added, callback);
}
private async _reconcileBlockAsync(): Promise<void> {
const latestBlockIfExists = await this._web3Wrapper.getBlockIfExistsAsync(this._stateLayer);
if (_.isUndefined(latestBlockIfExists)) {
const latestBlockOrNull = await this._blockstreamGetLatestBlockOrNullAsync();
if (_.isNull(latestBlockOrNull)) {
return; // noop
}
// We need to coerce to Block type cause Web3.Block includes types for mempool blocks
if (!_.isUndefined(this._blockAndLogStreamerIfExists)) {
// If we clear the interval while fetching the block - this._blockAndLogStreamer will be undefined
await this._blockAndLogStreamerIfExists.reconcileNewBlock((latestBlockIfExists as any) as Block);
await this._blockAndLogStreamerIfExists.reconcileNewBlock(latestBlockOrNull);
}
}
private async _emitDifferencesAsync(

View File

@@ -6,6 +6,11 @@
"note":
"Rename `getBlockAsync` to `getBlockIfExistsAsync` and rather then throw if the requested block wasn't found, return undefined.",
"pr": 1082
},
{
"note":
"Expose `sendRawPayloadAsync` so one can easily extend `Web3Wrapper` with their own custom JSON RPC calls",
"pr": 1080
}
],
"timestamp": 1537875740

View File

@@ -193,7 +193,7 @@ export class Web3Wrapper {
* @returns Ethereum node's version string
*/
public async getNodeVersionAsync(): Promise<string> {
const nodeVersion = await this._sendRawPayloadAsync<string>({ method: 'web3_clientVersion' });
const nodeVersion = await this.sendRawPayloadAsync<string>({ method: 'web3_clientVersion' });
return nodeVersion;
}
/**
@@ -201,7 +201,7 @@ export class Web3Wrapper {
* @returns The network id
*/
public async getNetworkIdAsync(): Promise<number> {
const networkIdStr = await this._sendRawPayloadAsync<string>({ method: 'net_version' });
const networkIdStr = await this.sendRawPayloadAsync<string>({ method: 'net_version' });
const networkId = _.parseInt(networkIdStr);
return networkId;
}
@@ -212,7 +212,7 @@ export class Web3Wrapper {
*/
public async getTransactionReceiptAsync(txHash: string): Promise<TransactionReceipt> {
assert.isHexString('txHash', txHash);
const transactionReceipt = await this._sendRawPayloadAsync<TransactionReceipt>({
const transactionReceipt = await this.sendRawPayloadAsync<TransactionReceipt>({
method: 'eth_getTransactionReceipt',
params: [txHash],
});
@@ -228,7 +228,7 @@ export class Web3Wrapper {
*/
public async getTransactionByHashAsync(txHash: string): Promise<Transaction> {
assert.isHexString('txHash', txHash);
const transaction = await this._sendRawPayloadAsync<Transaction>({
const transaction = await this.sendRawPayloadAsync<Transaction>({
method: 'eth_getTransactionByHash',
params: [txHash],
});
@@ -247,7 +247,7 @@ export class Web3Wrapper {
}
const marshalledDefaultBlock = marshaller.marshalBlockParam(defaultBlock);
const encodedOwner = marshaller.marshalAddress(owner);
const balanceInWei = await this._sendRawPayloadAsync<string>({
const balanceInWei = await this.sendRawPayloadAsync<string>({
method: 'eth_getBalance',
params: [encodedOwner, marshalledDefaultBlock],
});
@@ -279,7 +279,7 @@ export class Web3Wrapper {
}
const marshalledDefaultBlock = marshaller.marshalBlockParam(defaultBlock);
const encodedAddress = marshaller.marshalAddress(address);
const code = await this._sendRawPayloadAsync<string>({
const code = await this.sendRawPayloadAsync<string>({
method: 'eth_getCode',
params: [encodedAddress, marshalledDefaultBlock],
});
@@ -293,7 +293,7 @@ export class Web3Wrapper {
*/
public async getTransactionTraceAsync(txHash: string, traceParams: TraceParams): Promise<TransactionTrace> {
assert.isHexString('txHash', txHash);
const trace = await this._sendRawPayloadAsync<TransactionTrace>({
const trace = await this.sendRawPayloadAsync<TransactionTrace>({
method: 'debug_traceTransaction',
params: [txHash, traceParams],
});
@@ -308,7 +308,7 @@ export class Web3Wrapper {
public async signMessageAsync(address: string, message: string): Promise<string> {
assert.isETHAddressHex('address', address);
assert.isString('message', message); // TODO: Should this be stricter? Hex string?
const signData = await this._sendRawPayloadAsync<string>({
const signData = await this.sendRawPayloadAsync<string>({
method: 'eth_sign',
params: [address, message],
});
@@ -319,7 +319,7 @@ export class Web3Wrapper {
* @returns Block number
*/
public async getBlockNumberAsync(): Promise<number> {
const blockNumberHex = await this._sendRawPayloadAsync<string>({
const blockNumberHex = await this.sendRawPayloadAsync<string>({
method: 'eth_blockNumber',
params: [],
});
@@ -339,7 +339,7 @@ export class Web3Wrapper {
const encodedBlockParam = marshaller.marshalBlockParam(blockParam);
const method = utils.isHexStrict(blockParam) ? 'eth_getBlockByHash' : 'eth_getBlockByNumber';
const shouldIncludeTransactionData = false;
const blockWithoutTransactionDataWithHexValuesOrNull = await this._sendRawPayloadAsync<
const blockWithoutTransactionDataWithHexValuesOrNull = await this.sendRawPayloadAsync<
BlockWithoutTransactionDataRPC
>({
method,
@@ -366,7 +366,7 @@ export class Web3Wrapper {
}
const method = utils.isHexStrict(blockParam) ? 'eth_getBlockByHash' : 'eth_getBlockByNumber';
const shouldIncludeTransactionData = true;
const blockWithTransactionDataWithHexValues = await this._sendRawPayloadAsync<BlockWithTransactionDataRPC>({
const blockWithTransactionDataWithHexValues = await this.sendRawPayloadAsync<BlockWithTransactionDataRPC>({
method,
params: [encodedBlockParam, shouldIncludeTransactionData],
});
@@ -393,7 +393,7 @@ export class Web3Wrapper {
* @returns Available user addresses
*/
public async getAvailableAddressesAsync(): Promise<string[]> {
const addresses = await this._sendRawPayloadAsync<string>({
const addresses = await this.sendRawPayloadAsync<string>({
method: 'eth_accounts',
params: [],
});
@@ -405,7 +405,7 @@ export class Web3Wrapper {
* @returns The snapshot id. This can be used to revert to this snapshot
*/
public async takeSnapshotAsync(): Promise<number> {
const snapshotId = Number(await this._sendRawPayloadAsync<string>({ method: 'evm_snapshot', params: [] }));
const snapshotId = Number(await this.sendRawPayloadAsync<string>({ method: 'evm_snapshot', params: [] }));
return snapshotId;
}
/**
@@ -415,14 +415,14 @@ export class Web3Wrapper {
*/
public async revertSnapshotAsync(snapshotId: number): Promise<boolean> {
assert.isNumber('snapshotId', snapshotId);
const didRevert = await this._sendRawPayloadAsync<boolean>({ method: 'evm_revert', params: [snapshotId] });
const didRevert = await this.sendRawPayloadAsync<boolean>({ method: 'evm_revert', params: [snapshotId] });
return didRevert;
}
/**
* Mine a block on a TestRPC/Ganache local node
*/
public async mineBlockAsync(): Promise<void> {
await this._sendRawPayloadAsync<string>({ method: 'evm_mine', params: [] });
await this.sendRawPayloadAsync<string>({ method: 'evm_mine', params: [] });
}
/**
* Increase the next blocks timestamp on TestRPC/Ganache or Geth local node.
@@ -434,9 +434,9 @@ export class Web3Wrapper {
// Detect Geth vs. Ganache and use appropriate endpoint.
const version = await this.getNodeVersionAsync();
if (_.includes(version, uniqueVersionIds.geth)) {
return this._sendRawPayloadAsync<number>({ method: 'debug_increaseTime', params: [timeDelta] });
return this.sendRawPayloadAsync<number>({ method: 'debug_increaseTime', params: [timeDelta] });
} else if (_.includes(version, uniqueVersionIds.ganache)) {
return this._sendRawPayloadAsync<number>({ method: 'evm_increaseTime', params: [timeDelta] });
return this.sendRawPayloadAsync<number>({ method: 'evm_increaseTime', params: [timeDelta] });
} else {
throw new Error(`Unknown client version: ${version}`);
}
@@ -447,6 +447,12 @@ export class Web3Wrapper {
* @returns The corresponding log entries
*/
public async getLogsAsync(filter: FilterObject): Promise<LogEntry[]> {
if (!_.isUndefined(filter.blockHash) && (!_.isUndefined(filter.fromBlock) || !_.isUndefined(filter.toBlock))) {
throw new Error(
`Cannot specify 'blockHash' as well as 'fromBlock'/'toBlock' in the filter supplied to 'getLogsAsync'`,
);
}
let fromBlock = filter.fromBlock;
if (_.isNumber(fromBlock)) {
fromBlock = utils.numberToHex(fromBlock);
@@ -464,7 +470,7 @@ export class Web3Wrapper {
method: 'eth_getLogs',
params: [serializedFilter],
};
const rawLogs = await this._sendRawPayloadAsync<RawLogEntry[]>(payload);
const rawLogs = await this.sendRawPayloadAsync<RawLogEntry[]>(payload);
const formattedLogs = _.map(rawLogs, marshaller.unmarshalLog.bind(marshaller));
return formattedLogs;
}
@@ -480,7 +486,7 @@ export class Web3Wrapper {
schemas.jsNumber,
]);
const txDataHex = marshaller.marshalTxData(txData);
const gasHex = await this._sendRawPayloadAsync<string>({ method: 'eth_estimateGas', params: [txDataHex] });
const gasHex = await this.sendRawPayloadAsync<string>({ method: 'eth_estimateGas', params: [txDataHex] });
const gas = utils.convertHexToNumber(gasHex);
return gas;
}
@@ -501,7 +507,7 @@ export class Web3Wrapper {
}
const marshalledDefaultBlock = marshaller.marshalBlockParam(defaultBlock);
const callDataHex = marshaller.marshalCallData(callData);
const rawCallResult = await this._sendRawPayloadAsync<string>({
const rawCallResult = await this.sendRawPayloadAsync<string>({
method: 'eth_call',
params: [callDataHex, marshalledDefaultBlock],
});
@@ -522,7 +528,7 @@ export class Web3Wrapper {
schemas.jsNumber,
]);
const txDataHex = marshaller.marshalTxData(txData);
const txHash = await this._sendRawPayloadAsync<string>({ method: 'eth_sendTransaction', params: [txDataHex] });
const txHash = await this.sendRawPayloadAsync<string>({ method: 'eth_sendTransaction', params: [txDataHex] });
return txHash;
}
/**
@@ -632,7 +638,24 @@ export class Web3Wrapper {
*/
public async setHeadAsync(blockNumber: number): Promise<void> {
assert.isNumber('blockNumber', blockNumber);
await this._sendRawPayloadAsync<void>({ method: 'debug_setHead', params: [utils.numberToHex(blockNumber)] });
await this.sendRawPayloadAsync<void>({ method: 'debug_setHead', params: [utils.numberToHex(blockNumber)] });
}
/**
* Sends a raw Ethereum JSON RPC payload and returns the response's `result` key
* @param payload A partial JSON RPC payload. No need to include version, id, params (if none needed)
* @return The contents nested under the result key of the response body
*/
public async sendRawPayloadAsync<A>(payload: Partial<JSONRPCRequestPayload>): Promise<A> {
const sendAsync = this._provider.sendAsync.bind(this._provider);
const payloadWithDefaults = {
id: this._jsonRpcRequestId++,
params: [],
jsonrpc: '2.0',
...payload,
};
const response = await promisify<JSONRPCResponsePayload>(sendAsync)(payloadWithDefaults);
const result = response.result;
return result;
}
/**
* Returns either NodeType.Geth or NodeType.Ganache depending on the type of
@@ -648,16 +671,4 @@ export class Web3Wrapper {
throw new Error(`Unknown client version: ${version}`);
}
}
private async _sendRawPayloadAsync<A>(payload: Partial<JSONRPCRequestPayload>): Promise<A> {
const sendAsync = this._provider.sendAsync.bind(this._provider);
const payloadWithDefaults = {
id: this._jsonRpcRequestId++,
params: [],
jsonrpc: '2.0',
...payload,
};
const response = await promisify<JSONRPCResponsePayload>(sendAsync)(payloadWithDefaults);
const result = response.result;
return result;
}
} // tslint:disable-line:max-file-line-count

View File

@@ -5203,6 +5203,14 @@ ethereumjs-blockstream@5.0.0:
source-map-support "0.5.6"
uuid "3.2.1"
ethereumjs-blockstream@6.0.0:
version "6.0.0"
resolved "https://registry.yarnpkg.com/ethereumjs-blockstream/-/ethereumjs-blockstream-6.0.0.tgz#79d726d1f358935eb65195e91d40344c31e87eff"
dependencies:
immutable "3.8.2"
source-map-support "0.5.6"
uuid "3.2.1"
ethereumjs-tx@0xProject/ethereumjs-tx#fake-tx-include-signature-by-default:
version "1.3.4"
resolved "https://codeload.github.com/0xProject/ethereumjs-tx/tar.gz/29d1153889c389591f74b2401da8a0c6ad40f9a7"
@@ -6053,7 +6061,7 @@ ganache-core@0xProject/ganache-core#monorepo-dep:
ethereumjs-tx "0xProject/ethereumjs-tx#fake-tx-include-signature-by-default"
ethereumjs-util "^5.2.0"
ethereumjs-vm "2.3.5"
ethereumjs-wallet "~0.6.0"
ethereumjs-wallet "0.6.0"
fake-merkle-patricia-tree "~1.0.1"
heap "~0.2.6"
js-scrypt "^0.2.0"