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", "version": "1.0.5",
"changes": [ "changes": [
{ {
"note": "Dependencies updated" "note": "Dependencies updated"
} }
] ],
"timestamp": 1537875740
}, },
{ {
"timestamp": 1537541580,
"version": "1.0.4", "version": "1.0.4",
"changes": [ "changes": [
{ {
"note": "Dependencies updated" "note": "Dependencies updated"
} }
] ],
"timestamp": 1537541580
}, },
{ {
"version": "1.0.3", "version": "1.0.3",

View File

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

View File

@@ -1,14 +1,14 @@
import { AbiDecoder, intervalUtils, logUtils } from '@0xproject/utils'; import { AbiDecoder, intervalUtils, logUtils } from '@0xproject/utils';
import { Web3Wrapper } from '@0xproject/web3-wrapper'; import { marshaller, Web3Wrapper } from '@0xproject/web3-wrapper';
import { import {
BlockParamLiteral, BlockParamLiteral,
BlockWithoutTransactionData,
ContractAbi, ContractAbi,
ContractArtifact, ContractArtifact,
FilterObject, FilterObject,
LogEntry, LogEntry,
LogWithDecodedArgs, LogWithDecodedArgs,
RawLog, RawLog,
RawLogEntry,
} from 'ethereum-types'; } from 'ethereum-types';
import { Block, BlockAndLogStreamer, Log } from 'ethereumjs-blockstream'; import { Block, BlockAndLogStreamer, Log } from 'ethereumjs-blockstream';
import * as _ from 'lodash'; import * as _ from 'lodash';
@@ -158,7 +158,8 @@ export abstract class ContractWrapper {
return addressIfExists; 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) => { _.forEach(this._filters, (filter: FilterObject, filterToken: string) => {
if (filterUtils.matchesFilter(log, filter)) { if (filterUtils.matchesFilter(log, filter)) {
const decodedLog = this._tryToDecodeLogOrNoop(log) as LogWithDecodedArgs<ArgsType>; const decodedLog = this._tryToDecodeLogOrNoop(log) as LogWithDecodedArgs<ArgsType>;
@@ -175,8 +176,8 @@ export abstract class ContractWrapper {
throw new Error(ContractWrappersError.SubscriptionAlreadyPresent); throw new Error(ContractWrappersError.SubscriptionAlreadyPresent);
} }
this._blockAndLogStreamerIfExists = new BlockAndLogStreamer( this._blockAndLogStreamerIfExists = new BlockAndLogStreamer(
this._getBlockOrNullAsync.bind(this), this._blockstreamGetBlockOrNullAsync.bind(this),
this._web3Wrapper.getLogsAsync.bind(this._web3Wrapper), this._blockstreamGetLogsAsync.bind(this),
ContractWrapper._onBlockAndLogStreamerError.bind(this, isVerbose), ContractWrapper._onBlockAndLogStreamerError.bind(this, isVerbose),
); );
const catchAllLogFilter = {}; 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 // This method only exists in order to comply with the expected interface of Blockstream's constructor
private async _getBlockOrNullAsync(): Promise<BlockWithoutTransactionData | null> { private async _blockstreamGetBlockOrNullAsync(hash: string): Promise<Block | null> {
const blockIfExists = await this._web3Wrapper.getBlockIfExistsAsync.bind(this._web3Wrapper); const shouldIncludeTransactionData = false;
if (_.isUndefined(blockIfExists)) { const blockOrNull = await this._web3Wrapper.sendRawPayloadAsync<Block | null>({
return null; method: 'eth_getBlockByHash',
} params: [hash, shouldIncludeTransactionData],
return blockIfExists; });
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) // 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 // 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; delete this._blockAndLogStreamerIfExists;
} }
private async _reconcileBlockAsync(): Promise<void> { private async _reconcileBlockAsync(): Promise<void> {
const latestBlockIfExists = await this._web3Wrapper.getBlockIfExistsAsync(BlockParamLiteral.Latest); const latestBlockOrNull = await this._blockstreamGetLatestBlockOrNullAsync();
if (_.isUndefined(latestBlockIfExists)) { if (_.isNull(latestBlockOrNull)) {
return; // noop return; // noop
} }
// We need to coerce to Block type cause Web3.Block includes types for mempool blocks // We need to coerce to Block type cause Web3.Block includes types for mempool blocks
if (!_.isUndefined(this._blockAndLogStreamerIfExists)) { if (!_.isUndefined(this._blockAndLogStreamerIfExists)) {
// If we clear the interval while fetching the block - this._blockAndLogStreamer will be undefined // 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 { export interface FilterObject {
fromBlock?: number | string; fromBlock?: number | string;
toBlock?: number | string; toBlock?: number | string;
blockHash?: string;
address?: string; address?: string;
topics?: LogTopic[]; 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", "version": "1.0.5",
"changes": [ "changes": [
{ {
"note": "Dependencies updated" "note": "Dependencies updated"
} }
] ],
"timestamp": 1537875740
}, },
{ {
"timestamp": 1537541580,
"version": "1.0.4", "version": "1.0.4",
"changes": [ "changes": [
{ {
"note": "Dependencies updated" "note": "Dependencies updated"
} }
] ],
"timestamp": 1537541580
}, },
{ {
"version": "1.0.3", "version": "1.0.3",

View File

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

View File

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

View File

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

View File

@@ -193,7 +193,7 @@ export class Web3Wrapper {
* @returns Ethereum node's version string * @returns Ethereum node's version string
*/ */
public async getNodeVersionAsync(): Promise<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; return nodeVersion;
} }
/** /**
@@ -201,7 +201,7 @@ export class Web3Wrapper {
* @returns The network id * @returns The network id
*/ */
public async getNetworkIdAsync(): Promise<number> { 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); const networkId = _.parseInt(networkIdStr);
return networkId; return networkId;
} }
@@ -212,7 +212,7 @@ export class Web3Wrapper {
*/ */
public async getTransactionReceiptAsync(txHash: string): Promise<TransactionReceipt> { public async getTransactionReceiptAsync(txHash: string): Promise<TransactionReceipt> {
assert.isHexString('txHash', txHash); assert.isHexString('txHash', txHash);
const transactionReceipt = await this._sendRawPayloadAsync<TransactionReceipt>({ const transactionReceipt = await this.sendRawPayloadAsync<TransactionReceipt>({
method: 'eth_getTransactionReceipt', method: 'eth_getTransactionReceipt',
params: [txHash], params: [txHash],
}); });
@@ -228,7 +228,7 @@ export class Web3Wrapper {
*/ */
public async getTransactionByHashAsync(txHash: string): Promise<Transaction> { public async getTransactionByHashAsync(txHash: string): Promise<Transaction> {
assert.isHexString('txHash', txHash); assert.isHexString('txHash', txHash);
const transaction = await this._sendRawPayloadAsync<Transaction>({ const transaction = await this.sendRawPayloadAsync<Transaction>({
method: 'eth_getTransactionByHash', method: 'eth_getTransactionByHash',
params: [txHash], params: [txHash],
}); });
@@ -247,7 +247,7 @@ export class Web3Wrapper {
} }
const marshalledDefaultBlock = marshaller.marshalBlockParam(defaultBlock); const marshalledDefaultBlock = marshaller.marshalBlockParam(defaultBlock);
const encodedOwner = marshaller.marshalAddress(owner); const encodedOwner = marshaller.marshalAddress(owner);
const balanceInWei = await this._sendRawPayloadAsync<string>({ const balanceInWei = await this.sendRawPayloadAsync<string>({
method: 'eth_getBalance', method: 'eth_getBalance',
params: [encodedOwner, marshalledDefaultBlock], params: [encodedOwner, marshalledDefaultBlock],
}); });
@@ -279,7 +279,7 @@ export class Web3Wrapper {
} }
const marshalledDefaultBlock = marshaller.marshalBlockParam(defaultBlock); const marshalledDefaultBlock = marshaller.marshalBlockParam(defaultBlock);
const encodedAddress = marshaller.marshalAddress(address); const encodedAddress = marshaller.marshalAddress(address);
const code = await this._sendRawPayloadAsync<string>({ const code = await this.sendRawPayloadAsync<string>({
method: 'eth_getCode', method: 'eth_getCode',
params: [encodedAddress, marshalledDefaultBlock], params: [encodedAddress, marshalledDefaultBlock],
}); });
@@ -293,7 +293,7 @@ export class Web3Wrapper {
*/ */
public async getTransactionTraceAsync(txHash: string, traceParams: TraceParams): Promise<TransactionTrace> { public async getTransactionTraceAsync(txHash: string, traceParams: TraceParams): Promise<TransactionTrace> {
assert.isHexString('txHash', txHash); assert.isHexString('txHash', txHash);
const trace = await this._sendRawPayloadAsync<TransactionTrace>({ const trace = await this.sendRawPayloadAsync<TransactionTrace>({
method: 'debug_traceTransaction', method: 'debug_traceTransaction',
params: [txHash, traceParams], params: [txHash, traceParams],
}); });
@@ -308,7 +308,7 @@ export class Web3Wrapper {
public async signMessageAsync(address: string, message: string): Promise<string> { public async signMessageAsync(address: string, message: string): Promise<string> {
assert.isETHAddressHex('address', address); assert.isETHAddressHex('address', address);
assert.isString('message', message); // TODO: Should this be stricter? Hex string? 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', method: 'eth_sign',
params: [address, message], params: [address, message],
}); });
@@ -319,7 +319,7 @@ export class Web3Wrapper {
* @returns Block number * @returns Block number
*/ */
public async getBlockNumberAsync(): Promise<number> { public async getBlockNumberAsync(): Promise<number> {
const blockNumberHex = await this._sendRawPayloadAsync<string>({ const blockNumberHex = await this.sendRawPayloadAsync<string>({
method: 'eth_blockNumber', method: 'eth_blockNumber',
params: [], params: [],
}); });
@@ -339,7 +339,7 @@ export class Web3Wrapper {
const encodedBlockParam = marshaller.marshalBlockParam(blockParam); const encodedBlockParam = marshaller.marshalBlockParam(blockParam);
const method = utils.isHexStrict(blockParam) ? 'eth_getBlockByHash' : 'eth_getBlockByNumber'; const method = utils.isHexStrict(blockParam) ? 'eth_getBlockByHash' : 'eth_getBlockByNumber';
const shouldIncludeTransactionData = false; const shouldIncludeTransactionData = false;
const blockWithoutTransactionDataWithHexValuesOrNull = await this._sendRawPayloadAsync< const blockWithoutTransactionDataWithHexValuesOrNull = await this.sendRawPayloadAsync<
BlockWithoutTransactionDataRPC BlockWithoutTransactionDataRPC
>({ >({
method, method,
@@ -366,7 +366,7 @@ export class Web3Wrapper {
} }
const method = utils.isHexStrict(blockParam) ? 'eth_getBlockByHash' : 'eth_getBlockByNumber'; const method = utils.isHexStrict(blockParam) ? 'eth_getBlockByHash' : 'eth_getBlockByNumber';
const shouldIncludeTransactionData = true; const shouldIncludeTransactionData = true;
const blockWithTransactionDataWithHexValues = await this._sendRawPayloadAsync<BlockWithTransactionDataRPC>({ const blockWithTransactionDataWithHexValues = await this.sendRawPayloadAsync<BlockWithTransactionDataRPC>({
method, method,
params: [encodedBlockParam, shouldIncludeTransactionData], params: [encodedBlockParam, shouldIncludeTransactionData],
}); });
@@ -393,7 +393,7 @@ export class Web3Wrapper {
* @returns Available user addresses * @returns Available user addresses
*/ */
public async getAvailableAddressesAsync(): Promise<string[]> { public async getAvailableAddressesAsync(): Promise<string[]> {
const addresses = await this._sendRawPayloadAsync<string>({ const addresses = await this.sendRawPayloadAsync<string>({
method: 'eth_accounts', method: 'eth_accounts',
params: [], params: [],
}); });
@@ -405,7 +405,7 @@ export class Web3Wrapper {
* @returns The snapshot id. This can be used to revert to this snapshot * @returns The snapshot id. This can be used to revert to this snapshot
*/ */
public async takeSnapshotAsync(): Promise<number> { 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; return snapshotId;
} }
/** /**
@@ -415,14 +415,14 @@ export class Web3Wrapper {
*/ */
public async revertSnapshotAsync(snapshotId: number): Promise<boolean> { public async revertSnapshotAsync(snapshotId: number): Promise<boolean> {
assert.isNumber('snapshotId', snapshotId); 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; return didRevert;
} }
/** /**
* Mine a block on a TestRPC/Ganache local node * Mine a block on a TestRPC/Ganache local node
*/ */
public async mineBlockAsync(): Promise<void> { 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. * 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. // Detect Geth vs. Ganache and use appropriate endpoint.
const version = await this.getNodeVersionAsync(); const version = await this.getNodeVersionAsync();
if (_.includes(version, uniqueVersionIds.geth)) { 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)) { } 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 { } else {
throw new Error(`Unknown client version: ${version}`); throw new Error(`Unknown client version: ${version}`);
} }
@@ -447,6 +447,12 @@ export class Web3Wrapper {
* @returns The corresponding log entries * @returns The corresponding log entries
*/ */
public async getLogsAsync(filter: FilterObject): Promise<LogEntry[]> { 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; let fromBlock = filter.fromBlock;
if (_.isNumber(fromBlock)) { if (_.isNumber(fromBlock)) {
fromBlock = utils.numberToHex(fromBlock); fromBlock = utils.numberToHex(fromBlock);
@@ -464,7 +470,7 @@ export class Web3Wrapper {
method: 'eth_getLogs', method: 'eth_getLogs',
params: [serializedFilter], params: [serializedFilter],
}; };
const rawLogs = await this._sendRawPayloadAsync<RawLogEntry[]>(payload); const rawLogs = await this.sendRawPayloadAsync<RawLogEntry[]>(payload);
const formattedLogs = _.map(rawLogs, marshaller.unmarshalLog.bind(marshaller)); const formattedLogs = _.map(rawLogs, marshaller.unmarshalLog.bind(marshaller));
return formattedLogs; return formattedLogs;
} }
@@ -480,7 +486,7 @@ export class Web3Wrapper {
schemas.jsNumber, schemas.jsNumber,
]); ]);
const txDataHex = marshaller.marshalTxData(txData); 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); const gas = utils.convertHexToNumber(gasHex);
return gas; return gas;
} }
@@ -501,7 +507,7 @@ export class Web3Wrapper {
} }
const marshalledDefaultBlock = marshaller.marshalBlockParam(defaultBlock); const marshalledDefaultBlock = marshaller.marshalBlockParam(defaultBlock);
const callDataHex = marshaller.marshalCallData(callData); const callDataHex = marshaller.marshalCallData(callData);
const rawCallResult = await this._sendRawPayloadAsync<string>({ const rawCallResult = await this.sendRawPayloadAsync<string>({
method: 'eth_call', method: 'eth_call',
params: [callDataHex, marshalledDefaultBlock], params: [callDataHex, marshalledDefaultBlock],
}); });
@@ -522,7 +528,7 @@ export class Web3Wrapper {
schemas.jsNumber, schemas.jsNumber,
]); ]);
const txDataHex = marshaller.marshalTxData(txData); 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; return txHash;
} }
/** /**
@@ -632,7 +638,24 @@ export class Web3Wrapper {
*/ */
public async setHeadAsync(blockNumber: number): Promise<void> { public async setHeadAsync(blockNumber: number): Promise<void> {
assert.isNumber('blockNumber', blockNumber); 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 * 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}`); 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 } // tslint:disable-line:max-file-line-count

View File

@@ -5203,6 +5203,14 @@ ethereumjs-blockstream@5.0.0:
source-map-support "0.5.6" source-map-support "0.5.6"
uuid "3.2.1" 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: ethereumjs-tx@0xProject/ethereumjs-tx#fake-tx-include-signature-by-default:
version "1.3.4" version "1.3.4"
resolved "https://codeload.github.com/0xProject/ethereumjs-tx/tar.gz/29d1153889c389591f74b2401da8a0c6ad40f9a7" 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-tx "0xProject/ethereumjs-tx#fake-tx-include-signature-by-default"
ethereumjs-util "^5.2.0" ethereumjs-util "^5.2.0"
ethereumjs-vm "2.3.5" ethereumjs-vm "2.3.5"
ethereumjs-wallet "~0.6.0" ethereumjs-wallet "0.6.0"
fake-merkle-patricia-tree "~1.0.1" fake-merkle-patricia-tree "~1.0.1"
heap "~0.2.6" heap "~0.2.6"
js-scrypt "^0.2.0" js-scrypt "^0.2.0"