Merge pull request #1082 from 0xProject/fixGetBlocks
Fix block fetch error if block not found
This commit is contained in:
@@ -2,6 +2,7 @@ import { AbiDecoder, intervalUtils, logUtils } from '@0xproject/utils';
|
|||||||
import { Web3Wrapper } from '@0xproject/web3-wrapper';
|
import { Web3Wrapper } from '@0xproject/web3-wrapper';
|
||||||
import {
|
import {
|
||||||
BlockParamLiteral,
|
BlockParamLiteral,
|
||||||
|
BlockWithoutTransactionData,
|
||||||
ContractAbi,
|
ContractAbi,
|
||||||
ContractArtifact,
|
ContractArtifact,
|
||||||
FilterObject,
|
FilterObject,
|
||||||
@@ -174,7 +175,7 @@ export abstract class ContractWrapper {
|
|||||||
throw new Error(ContractWrappersError.SubscriptionAlreadyPresent);
|
throw new Error(ContractWrappersError.SubscriptionAlreadyPresent);
|
||||||
}
|
}
|
||||||
this._blockAndLogStreamerIfExists = new BlockAndLogStreamer(
|
this._blockAndLogStreamerIfExists = new BlockAndLogStreamer(
|
||||||
this._web3Wrapper.getBlockAsync.bind(this._web3Wrapper),
|
this._getBlockOrNullAsync.bind(this),
|
||||||
this._web3Wrapper.getLogsAsync.bind(this._web3Wrapper),
|
this._web3Wrapper.getLogsAsync.bind(this._web3Wrapper),
|
||||||
ContractWrapper._onBlockAndLogStreamerError.bind(this, isVerbose),
|
ContractWrapper._onBlockAndLogStreamerError.bind(this, isVerbose),
|
||||||
);
|
);
|
||||||
@@ -194,6 +195,14 @@ export abstract class ContractWrapper {
|
|||||||
this._onLogStateChanged.bind(this, isRemoved),
|
this._onLogStateChanged.bind(this, isRemoved),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
// 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
|
||||||
// who inherit from ContractWrapper, and it is only used by the internal implementation
|
// who inherit from ContractWrapper, and it is only used by the internal implementation
|
||||||
@@ -212,11 +221,14 @@ export abstract class ContractWrapper {
|
|||||||
delete this._blockAndLogStreamerIfExists;
|
delete this._blockAndLogStreamerIfExists;
|
||||||
}
|
}
|
||||||
private async _reconcileBlockAsync(): Promise<void> {
|
private async _reconcileBlockAsync(): Promise<void> {
|
||||||
const latestBlock = await this._web3Wrapper.getBlockAsync(BlockParamLiteral.Latest);
|
const latestBlockIfExists = await this._web3Wrapper.getBlockIfExistsAsync(BlockParamLiteral.Latest);
|
||||||
|
if (_.isUndefined(latestBlockIfExists)) {
|
||||||
|
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((latestBlock as any) as Block);
|
await this._blockAndLogStreamerIfExists.reconcileNewBlock((latestBlockIfExists as any) as Block);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -61,7 +61,7 @@ describe('SubscriptionTest', () => {
|
|||||||
callback,
|
callback,
|
||||||
);
|
);
|
||||||
stubs = [
|
stubs = [
|
||||||
Sinon.stub((contractWrappers as any)._web3Wrapper, 'getBlockAsync').throws(
|
Sinon.stub((contractWrappers as any)._web3Wrapper, 'getBlockIfExistsAsync').throws(
|
||||||
new Error('JSON RPC error'),
|
new Error('JSON RPC error'),
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
|
@@ -269,7 +269,10 @@ describe('MultiSigWalletWithTimeLock', () => {
|
|||||||
expect(confirmRes.logs).to.have.length(2);
|
expect(confirmRes.logs).to.have.length(2);
|
||||||
|
|
||||||
const blockNum = await web3Wrapper.getBlockNumberAsync();
|
const blockNum = await web3Wrapper.getBlockNumberAsync();
|
||||||
const blockInfo = await web3Wrapper.getBlockAsync(blockNum);
|
const blockInfo = await web3Wrapper.getBlockIfExistsAsync(blockNum);
|
||||||
|
if (_.isUndefined(blockInfo)) {
|
||||||
|
throw new Error(`Unexpectedly failed to fetch block at #${blockNum}`);
|
||||||
|
}
|
||||||
const timestamp = new BigNumber(blockInfo.timestamp);
|
const timestamp = new BigNumber(blockInfo.timestamp);
|
||||||
const confirmationTimeBigNum = new BigNumber(await multiSig.confirmationTimes.callAsync(txId));
|
const confirmationTimeBigNum = new BigNumber(await multiSig.confirmationTimes.callAsync(txId));
|
||||||
|
|
||||||
|
@@ -35,6 +35,9 @@ export async function increaseTimeAndMineBlockAsync(seconds: number): Promise<nu
|
|||||||
* @returns a new Promise which will resolve with the timestamp in seconds.
|
* @returns a new Promise which will resolve with the timestamp in seconds.
|
||||||
*/
|
*/
|
||||||
export async function getLatestBlockTimestampAsync(): Promise<number> {
|
export async function getLatestBlockTimestampAsync(): Promise<number> {
|
||||||
const currentBlock = await web3Wrapper.getBlockAsync('latest');
|
const currentBlockIfExists = await web3Wrapper.getBlockIfExistsAsync('latest');
|
||||||
return currentBlock.timestamp;
|
if (_.isUndefined(currentBlockIfExists)) {
|
||||||
|
throw new Error(`Unable to fetch latest block.`);
|
||||||
|
}
|
||||||
|
return currentBlockIfExists.timestamp;
|
||||||
}
|
}
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
import { intervalUtils, logUtils } from '@0xproject/utils';
|
import { intervalUtils, logUtils } from '@0xproject/utils';
|
||||||
import { Web3Wrapper } from '@0xproject/web3-wrapper';
|
import { Web3Wrapper } from '@0xproject/web3-wrapper';
|
||||||
import { BlockParamLiteral, LogEntry, Provider } from 'ethereum-types';
|
import { BlockParamLiteral, BlockWithoutTransactionData, LogEntry, Provider } 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';
|
||||||
|
|
||||||
@@ -62,7 +62,7 @@ export class EventWatcher {
|
|||||||
throw new Error(OrderWatcherError.SubscriptionAlreadyPresent);
|
throw new Error(OrderWatcherError.SubscriptionAlreadyPresent);
|
||||||
}
|
}
|
||||||
this._blockAndLogStreamerIfExists = new BlockAndLogStreamer(
|
this._blockAndLogStreamerIfExists = new BlockAndLogStreamer(
|
||||||
this._web3Wrapper.getBlockAsync.bind(this._web3Wrapper),
|
this._getBlockOrNullAsync.bind(this),
|
||||||
this._web3Wrapper.getLogsAsync.bind(this._web3Wrapper),
|
this._web3Wrapper.getLogsAsync.bind(this._web3Wrapper),
|
||||||
this._onBlockAndLogStreamerError.bind(this),
|
this._onBlockAndLogStreamerError.bind(this),
|
||||||
);
|
);
|
||||||
@@ -82,6 +82,14 @@ export class EventWatcher {
|
|||||||
this._onLogStateChangedAsync.bind(this, callback, isRemoved),
|
this._onLogStateChangedAsync.bind(this, callback, isRemoved),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
// 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 _stopBlockAndLogStream(): void {
|
private _stopBlockAndLogStream(): void {
|
||||||
if (_.isUndefined(this._blockAndLogStreamerIfExists)) {
|
if (_.isUndefined(this._blockAndLogStreamerIfExists)) {
|
||||||
throw new Error(OrderWatcherError.SubscriptionNotFound);
|
throw new Error(OrderWatcherError.SubscriptionNotFound);
|
||||||
@@ -100,11 +108,14 @@ export class EventWatcher {
|
|||||||
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 latestBlock = await this._web3Wrapper.getBlockAsync(this._stateLayer);
|
const latestBlockIfExists = await this._web3Wrapper.getBlockIfExistsAsync(this._stateLayer);
|
||||||
|
if (_.isUndefined(latestBlockIfExists)) {
|
||||||
|
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((latestBlock as any) as Block);
|
await this._blockAndLogStreamerIfExists.reconcileNewBlock((latestBlockIfExists as any) as Block);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
private async _emitDifferencesAsync(
|
private async _emitDifferencesAsync(
|
||||||
|
@@ -1,4 +1,14 @@
|
|||||||
[
|
[
|
||||||
|
{
|
||||||
|
"version": "3.0.0",
|
||||||
|
"changes": [
|
||||||
|
{
|
||||||
|
"note":
|
||||||
|
"Rename `getBlockAsync` to `getBlockIfExistsAsync` and rather then throw if the requested block wasn't found, return undefined.",
|
||||||
|
"pr": 1082
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"version": "2.0.3",
|
"version": "2.0.3",
|
||||||
"changes": [
|
"changes": [
|
||||||
|
@@ -329,23 +329,29 @@ export class Web3Wrapper {
|
|||||||
/**
|
/**
|
||||||
* Fetch a specific Ethereum block without transaction data
|
* Fetch a specific Ethereum block without transaction data
|
||||||
* @param blockParam The block you wish to fetch (blockHash, blockNumber or blockLiteral)
|
* @param blockParam The block you wish to fetch (blockHash, blockNumber or blockLiteral)
|
||||||
* @returns The requested block without transaction data
|
* @returns The requested block without transaction data, or undefined if block was not found
|
||||||
|
* (e.g the node isn't fully synced, there was a block re-org and the requested block was uncles, etc...)
|
||||||
*/
|
*/
|
||||||
public async getBlockAsync(blockParam: string | BlockParam): Promise<BlockWithoutTransactionData> {
|
public async getBlockIfExistsAsync(
|
||||||
|
blockParam: string | BlockParam,
|
||||||
|
): Promise<BlockWithoutTransactionData | undefined> {
|
||||||
Web3Wrapper._assertBlockParamOrString(blockParam);
|
Web3Wrapper._assertBlockParamOrString(blockParam);
|
||||||
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 blockWithoutTransactionDataWithHexValues = await this._sendRawPayloadAsync<
|
const blockWithoutTransactionDataWithHexValuesOrNull = await this._sendRawPayloadAsync<
|
||||||
BlockWithoutTransactionDataRPC
|
BlockWithoutTransactionDataRPC
|
||||||
>({
|
>({
|
||||||
method,
|
method,
|
||||||
params: [encodedBlockParam, shouldIncludeTransactionData],
|
params: [encodedBlockParam, shouldIncludeTransactionData],
|
||||||
});
|
});
|
||||||
const blockWithoutTransactionData = marshaller.unmarshalIntoBlockWithoutTransactionData(
|
let blockWithoutTransactionDataIfExists;
|
||||||
blockWithoutTransactionDataWithHexValues,
|
if (!_.isNull(blockWithoutTransactionDataWithHexValuesOrNull)) {
|
||||||
);
|
blockWithoutTransactionDataIfExists = marshaller.unmarshalIntoBlockWithoutTransactionData(
|
||||||
return blockWithoutTransactionData;
|
blockWithoutTransactionDataWithHexValuesOrNull,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return blockWithoutTransactionDataIfExists;
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Fetch a specific Ethereum block with transaction data
|
* Fetch a specific Ethereum block with transaction data
|
||||||
@@ -376,8 +382,11 @@ export class Web3Wrapper {
|
|||||||
*/
|
*/
|
||||||
public async getBlockTimestampAsync(blockParam: string | BlockParam): Promise<number> {
|
public async getBlockTimestampAsync(blockParam: string | BlockParam): Promise<number> {
|
||||||
Web3Wrapper._assertBlockParamOrString(blockParam);
|
Web3Wrapper._assertBlockParamOrString(blockParam);
|
||||||
const { timestamp } = await this.getBlockAsync(blockParam);
|
const blockIfExists = await this.getBlockIfExistsAsync(blockParam);
|
||||||
return timestamp;
|
if (_.isUndefined(blockIfExists)) {
|
||||||
|
throw new Error(`Failed to fetch block with blockParam: ${JSON.stringify(blockParam)}`);
|
||||||
|
}
|
||||||
|
return blockIfExists.timestamp;
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Retrieve the user addresses available through the backing provider
|
* Retrieve the user addresses available through the backing provider
|
||||||
|
@@ -85,28 +85,40 @@ describe('Web3Wrapper tests', () => {
|
|||||||
expect(typeof blockNumber).to.be.equal('number');
|
expect(typeof blockNumber).to.be.equal('number');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
describe('#getBlockAsync', () => {
|
describe('#getBlockIfExistsAsync', () => {
|
||||||
it('gets block when supplied a valid BlockParamLiteral value', async () => {
|
it('gets block when supplied a valid BlockParamLiteral value', async () => {
|
||||||
const blockParamLiteral = BlockParamLiteral.Earliest;
|
const blockParamLiteral = BlockParamLiteral.Earliest;
|
||||||
const block = await web3Wrapper.getBlockAsync(blockParamLiteral);
|
const blockIfExists = await web3Wrapper.getBlockIfExistsAsync(blockParamLiteral);
|
||||||
expect(block.number).to.be.equal(0);
|
if (_.isUndefined(blockIfExists)) {
|
||||||
expect(utils.isBigNumber(block.difficulty)).to.equal(true);
|
throw new Error('Expected block to exist');
|
||||||
expect(_.isNumber(block.gasLimit)).to.equal(true);
|
}
|
||||||
|
expect(blockIfExists.number).to.be.equal(0);
|
||||||
|
expect(utils.isBigNumber(blockIfExists.difficulty)).to.equal(true);
|
||||||
|
expect(_.isNumber(blockIfExists.gasLimit)).to.equal(true);
|
||||||
});
|
});
|
||||||
it('gets block when supplied a block number', async () => {
|
it('gets block when supplied a block number', async () => {
|
||||||
const blockParamLiteral = 0;
|
const blockParamLiteral = 0;
|
||||||
const block = await web3Wrapper.getBlockAsync(blockParamLiteral);
|
const blockIfExists = await web3Wrapper.getBlockIfExistsAsync(blockParamLiteral);
|
||||||
expect(block.number).to.be.equal(0);
|
if (_.isUndefined(blockIfExists)) {
|
||||||
|
throw new Error('Expected block to exist');
|
||||||
|
}
|
||||||
|
expect(blockIfExists.number).to.be.equal(0);
|
||||||
});
|
});
|
||||||
it('gets block when supplied a block hash', async () => {
|
it('gets block when supplied a block hash', async () => {
|
||||||
const blockParamLiteral = 0;
|
const blockParamLiteral = 0;
|
||||||
const block = await web3Wrapper.getBlockAsync(blockParamLiteral);
|
const blockIfExists = await web3Wrapper.getBlockIfExistsAsync(blockParamLiteral);
|
||||||
const sameBlock = await web3Wrapper.getBlockAsync(block.hash as string);
|
if (_.isUndefined(blockIfExists)) {
|
||||||
expect(sameBlock.number).to.be.equal(0);
|
throw new Error('Expected block to exist');
|
||||||
|
}
|
||||||
|
const sameBlockIfExists = await web3Wrapper.getBlockIfExistsAsync(blockIfExists.hash as string);
|
||||||
|
if (_.isUndefined(sameBlockIfExists)) {
|
||||||
|
throw new Error('Expected block to exist');
|
||||||
|
}
|
||||||
|
expect(sameBlockIfExists.number).to.be.equal(0);
|
||||||
});
|
});
|
||||||
it('should throw if supplied invalid blockParam value', async () => {
|
it('should throw if supplied invalid blockParam value', async () => {
|
||||||
const invalidBlockParam = 'deadbeef';
|
const invalidBlockParam = 'deadbeef';
|
||||||
expect(web3Wrapper.getBlockAsync(invalidBlockParam)).to.eventually.to.be.rejected();
|
expect(web3Wrapper.getBlockIfExistsAsync(invalidBlockParam)).to.eventually.to.be.rejected();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
describe('#getBlockWithTransactionDataAsync', () => {
|
describe('#getBlockWithTransactionDataAsync', () => {
|
||||||
|
Reference in New Issue
Block a user