From bacc4403979fa423890e269e7a5c7d11c6891a9f Mon Sep 17 00:00:00 2001 From: Richard Moore Date: Mon, 3 Feb 2020 23:05:04 -0500 Subject: [PATCH] Added timeout to waitForTransaction (#477). --- packages/abstract-provider/src.ts/index.ts | 2 +- packages/logger/src.ts/index.ts | 3 ++- packages/providers/src.ts/base-provider.ts | 26 ++++++++++++++++--- .../providers/src.ts/fallback-provider.ts | 18 ++++++++++--- 4 files changed, 40 insertions(+), 9 deletions(-) diff --git a/packages/abstract-provider/src.ts/index.ts b/packages/abstract-provider/src.ts/index.ts index c590de978e..9271df662c 100644 --- a/packages/abstract-provider/src.ts/index.ts +++ b/packages/abstract-provider/src.ts/index.ts @@ -259,7 +259,7 @@ export abstract class Provider implements OnceBlockable { } // @TODO: This *could* be implemented here, but would pull in events... - abstract waitForTransaction(transactionHash: string, timeout?: number): Promise; + abstract waitForTransaction(transactionHash: string, confirmations?: number, timeout?: number): Promise; readonly _isProvider: boolean; diff --git a/packages/logger/src.ts/index.ts b/packages/logger/src.ts/index.ts index 3f8b4bc373..3b248347d5 100644 --- a/packages/logger/src.ts/index.ts +++ b/packages/logger/src.ts/index.ts @@ -193,7 +193,8 @@ export class Logger { messageDetails.push(key + "=" + JSON.stringify(params[key].toString())); } }); - messageDetails.push("version=" + this.version); + messageDetails.push(`code=${ code }`); + messageDetails.push(`version=${ this.version }`); const reason = message; if (messageDetails.length) { diff --git a/packages/providers/src.ts/base-provider.ts b/packages/providers/src.ts/base-provider.ts index d2a8594c7a..af84b8c03b 100644 --- a/packages/providers/src.ts/base-provider.ts +++ b/packages/providers/src.ts/base-provider.ts @@ -444,22 +444,42 @@ export class BaseProvider extends Provider { // @TODO: Add .poller which must be an event emitter with a 'start', 'stop' and 'block' event; // this will be used once we move to the WebSocket or other alternatives to polling - async waitForTransaction(transactionHash: string, confirmations?: number): Promise { + async waitForTransaction(transactionHash: string, confirmations?: number, timeout?: number): Promise { if (confirmations == null) { confirmations = 1; } const receipt = await this.getTransactionReceipt(transactionHash); // Receipt is already good - if (receipt.confirmations >= confirmations) { return receipt; } + if ((receipt ? receipt.confirmations: 0) >= confirmations) { return receipt; } // Poll until the receipt is good... - return new Promise((resolve) => { + return new Promise((resolve, reject) => { + let timer: NodeJS.Timer = null; + let done = false; + const handler = (receipt: TransactionReceipt) => { if (receipt.confirmations < confirmations) { return; } + + if (timer) { clearTimeout(timer); } + if (done) { return; } + done = true; + this.removeListener(transactionHash, handler); resolve(receipt); } this.on(transactionHash, handler); + + if (typeof(timeout) === "number" && timeout > 0) { + timer = setTimeout(() => { + if (done) { return; } + timer = null; + done = true; + + this.removeListener(transactionHash, handler); + reject(logger.makeError("timeout exceeded", Logger.errors.TIMEOUT, { timeout: timeout })); + }, timeout); + if (timer.unref) { timer.unref(); } + } }); } diff --git a/packages/providers/src.ts/fallback-provider.ts b/packages/providers/src.ts/fallback-provider.ts index 67f725ddaa..d2227eda13 100644 --- a/packages/providers/src.ts/fallback-provider.ts +++ b/packages/providers/src.ts/fallback-provider.ts @@ -1,7 +1,7 @@ "use strict"; import { Network } from "@ethersproject/networks"; -import { BlockWithTransactions, Provider } from "@ethersproject/abstract-provider"; +import { Block, BlockWithTransactions, Provider } from "@ethersproject/abstract-provider"; import { shuffled } from "@ethersproject/random"; import { deepCopy, defineReadOnly, shallowCopy } from "@ethersproject/properties"; import { BigNumber } from "@ethersproject/bignumber"; @@ -56,7 +56,7 @@ function median(values: Array): number { function serialize(value: any): string { if (value === null) { - return null; + return "null"; } else if (typeof(value) === "number" || typeof(value) === "boolean") { return JSON.stringify(value); } else if (typeof(value) === "string") { @@ -222,6 +222,8 @@ function getProcessFunc(provider: FallbackProvider, method: string, params: { [ case "getTransaction": case "getTransactionReceipt": normalize = function(tx: any): string { + if (tx == null) { return null; } + tx = shallowCopy(tx); tx.confirmations = -1; return serialize(tx); @@ -233,6 +235,8 @@ function getProcessFunc(provider: FallbackProvider, method: string, params: { [ // We drop the confirmations from transactions as it is approximate if (params.includeTransactions) { normalize = function(block: BlockWithTransactions): string { + if (block == null) { return null; } + block = shallowCopy(block); block.transactions = block.transactions.map((tx) => { tx = shallowCopy(tx); @@ -241,6 +245,11 @@ function getProcessFunc(provider: FallbackProvider, method: string, params: { [ }); return serialize(block); }; + } else { + normalize = function(block: Block): string { + if (block == null) { return null; } + return serialize(block); + } } break; @@ -460,7 +469,7 @@ export class FallbackProvider extends BaseProvider { const results = configs.filter((c) => (c.done && c.error == null)); if (results.length >= this.quorum) { const result = processFunc(results); - if (result != undefined) { return result; } + if (result !== undefined) { return result; } } // All configs have run to completion; we will never get more data @@ -470,8 +479,9 @@ export class FallbackProvider extends BaseProvider { return logger.throwError("failed to meet quorum", Logger.errors.SERVER_ERROR, { method: method, params: params, - results: configs.map((c) => exposeDebugConfig(c)), + //results: configs.map((c) => c.result), //errors: configs.map((c) => c.error), + results: configs.map((c) => exposeDebugConfig(c)), provider: this }); }