diff --git a/modules/contracts/src.ts/services/ethService.spec.ts b/modules/contracts/src.ts/services/ethService.spec.ts index 7bcc3ca44..98fb16d6b 100644 --- a/modules/contracts/src.ts/services/ethService.spec.ts +++ b/modules/contracts/src.ts/services/ethService.spec.ts @@ -37,7 +37,7 @@ let sendTxWithRetriesMock: SinonStub; let approveMock: SinonStub; let getCodeMock: SinonStub; let getOnchainBalanceMock: SinonStub; -let waitForConfirmation: SinonStub<[chainId: number, responses: TransactionResponse[]], Promise>; +let waitForConfirmation: SinonStub<[chainId: number, responses: TransactionResponse[], timeoutMultiplier?: number | undefined], Promise>; let getGasPrice: SinonStub<[chainId: number], Promise>>; let channelState: FullChannelState; diff --git a/modules/contracts/src.ts/services/ethService.ts b/modules/contracts/src.ts/services/ethService.ts index 6b7f3c4c9..fa45ef07b 100644 --- a/modules/contracts/src.ts/services/ethService.ts +++ b/modules/contracts/src.ts/services/ethService.ts @@ -115,6 +115,16 @@ export class EthereumChainService extends EthereumChainReader implements IVector reason: TransactionReason, response: TransactionResponse, ): Promise { + await this.store.saveTransactionAttempt(onchainTransactionId, channelAddress, reason, response); + this.evts[ChainServiceEvents.TRANSACTION_SUBMITTED].post({ + response: Object.fromEntries( + Object.entries(response).map(([key, value]) => { + return [key, BigNumber.isBigNumber(value) ? value.toString() : value]; + }), + ) as StringifiedTransactionResponse, + channelAddress, + reason, + }); this.log.info( { method, @@ -125,18 +135,8 @@ export class EthereumChainService extends EthereumChainReader implements IVector gasPrice: response.gasPrice.toString(), nonce: response.nonce, }, - "Tx submitted.", + "Tx submitted. Attempt saved.", ); - await this.store.saveTransactionAttempt(onchainTransactionId, channelAddress, reason, response); - this.evts[ChainServiceEvents.TRANSACTION_SUBMITTED].post({ - response: Object.fromEntries( - Object.entries(response).map(([key, value]) => { - return [key, BigNumber.isBigNumber(value) ? value.toString() : value]; - }), - ) as StringifiedTransactionResponse, - channelAddress, - reason, - }); } /// Save the tx receipt in the store and fire tx mined event. @@ -148,7 +148,6 @@ export class EthereumChainService extends EthereumChainReader implements IVector reason: TransactionReason, receipt: TransactionReceipt, ) { - this.log.info({ method, methodId, onchainTransactionId, reason, receipt }, "Tx mined."); await this.store.saveTransactionReceipt(onchainTransactionId, receipt); this.evts[ChainServiceEvents.TRANSACTION_MINED].post({ receipt: Object.fromEntries( @@ -159,6 +158,7 @@ export class EthereumChainService extends EthereumChainReader implements IVector channelAddress, reason, }); + this.log.info({ method, methodId, onchainTransactionId, reason, receipt }, "Tx mined. Saved receipt."); } /// Save tx failure in store and fire tx failed event. @@ -172,7 +172,6 @@ export class EthereumChainService extends EthereumChainReader implements IVector error?: Error, message: string = "Tx reverted", ) { - this.log.error({ method, methodId, error: error ? jsonifyError(error) : message }, message); await this.store.saveTransactionFailure(onchainTransactionId, error?.message ?? message, receipt); this.evts[ChainServiceEvents.TRANSACTION_FAILED].post({ error, @@ -188,6 +187,7 @@ export class EthereumChainService extends EthereumChainReader implements IVector } : {}), }); + this.log.error({ method, methodId, error: error ? jsonifyError(error) : message }, message); } /// Helper method to wrap queuing up a transaction and waiting for response. @@ -402,25 +402,10 @@ export class EthereumChainService extends EthereumChainReader implements IVector // Only if the callback txFn() *disobeys* us in not passing in the nonce. // throw new ChainError(ChainError.reasons.) } - - this.log.info( - { - hash: response.hash, - gas: response.gasPrice.toString(), - channelAddress, - method, - methodId, - nonce: response.nonce, - }, - "Tx submitted", - ); - // Add this response to our local response history. - responses.push(response); - // NOTE: Response MUST be defined here because if it was NEVER defined (i.e. undefined on first iteration), - // we would have returned in prev block, and if it was undefined on this iteration we would not overwrite - // that value. // Tx was submitted: handle saving to store. await this.handleTxSubmit(onchainTransactionId, method, methodId, channelAddress, reason, response); + // Add this response to our local response history. + responses.push(response); } else { // If response returns undefined, we assume the tx was not sent. This will happen if some logic was // passed into txFn to bail out at the time of sending. @@ -473,7 +458,7 @@ export class EthereumChainService extends EthereumChainReader implements IVector /// CONFIRM // Now we wait for confirmation and get tx receipt. - receipt = await this.waitForConfirmation(chainId, responses); + receipt = await this.waitForConfirmation(chainId, responses, nonceExpired ? 3 : 1); // Check status in event of tx reversion. if (receipt && receipt.status === 0) { throw new ChainError(ChainError.reasons.TxReverted, { receipt }); @@ -579,7 +564,22 @@ export class EthereumChainService extends EthereumChainReader implements IVector * a timeout. If within the timeout there are *not* 10 confirmations, * the tx will be resubmitted at the same nonce. */ - public async waitForConfirmation(chainId: number, responses: TransactionResponse[]): Promise { + public async waitForConfirmation( + chainId: number, + responses: TransactionResponse[], + timeoutMultiplier: number = 1 + ): Promise { + const method = "waitForConfirmation"; + const methodId = getRandomBytes32(); + this.log.info( + { + method, + methodId, + attemptHashes: responses.map(tx => tx.hash), + }, + "Checking for confirmations on tx attempt(s).", + ); + const provider: JsonRpcProvider = this.chainProviders[chainId]; if (!provider) { throw new ChainError(ChainError.reasons.ProviderNotFound); @@ -628,7 +628,8 @@ export class EthereumChainService extends EthereumChainReader implements IVector // NOTE: This loop won't execute if receipt is valid (not undefined). let timeElapsed: number = 0; const startMark = new Date().getTime(); - while (!receipt && timeElapsed < CONFIRMATION_TIMEOUT) { + const confirmationTimeout = CONFIRMATION_TIMEOUT * timeoutMultiplier; + while (!receipt && timeElapsed < confirmationTimeout) { receipt = await pollForReceipt(); // Update elapsed time. timeElapsed = new Date().getTime() - startMark;