fix: cancel/replace

This commit is contained in:
Danil Kovtonyuk 2022-07-12 23:00:55 +10:00
parent d36a8fec96
commit e39cf2825d
No known key found for this signature in database
GPG Key ID: E72A919BF08C3746
2 changed files with 36 additions and 14 deletions

View File

@ -37,6 +37,7 @@ class Transaction {
this._promise = PromiEvent() this._promise = PromiEvent()
this._emitter = this._promise.eventEmitter this._emitter = this._promise.eventEmitter
this.executed = false this.executed = false
this.replaced = false
this.retries = 0 this.retries = 0
this.currentTxHash = null this.currentTxHash = null
// store all submitted hashes to catch cases when an old tx is mined // store all submitted hashes to catch cases when an old tx is mined
@ -63,6 +64,10 @@ class Transaction {
*/ */
async replace(tx) { async replace(tx) {
// todo throw error if the current transaction is mined already // todo throw error if the current transaction is mined already
// if (this.currentTxHash) {
// throw new Error('Previous transaction was mined')
// }
console.log('Replacing current transaction') console.log('Replacing current transaction')
if (!this.executed) { if (!this.executed) {
// Tx was not executed yet, just replace it // Tx was not executed yet, just replace it
@ -80,7 +85,7 @@ class Transaction {
? min(gasLimit, this.manager.config.BLOCK_GAS_LIMIT) ? min(gasLimit, this.manager.config.BLOCK_GAS_LIMIT)
: gasLimit : gasLimit
} }
// TODO: check if the new tx params is valid
tx.chainId = this.tx.chainId tx.chainId = this.tx.chainId
tx.nonce = this.tx.nonce // can be different from `this.manager._nonce` tx.nonce = this.tx.nonce // can be different from `this.manager._nonce`
@ -90,13 +95,16 @@ class Transaction {
} else if (this.tx.maxFeePerGas) { } else if (this.tx.maxFeePerGas) {
tx.maxFeePerGas = max(this.tx.maxFeePerGas, tx.maxFeePerGas || 0) tx.maxFeePerGas = max(this.tx.maxFeePerGas, tx.maxFeePerGas || 0)
tx.maxPriorityFeePerGas = max(this.tx.maxPriorityFeePerGas, tx.maxPriorityFeePerGas || 0) tx.maxPriorityFeePerGas = max(this.tx.maxPriorityFeePerGas, tx.maxPriorityFeePerGas || 0)
} else {
const gasParams = await this._getGasParams()
tx = { ...tx, ...gasParams }
} }
this.tx = { ...tx } this.tx = { ...tx }
this._increaseGasPrice() await this._prepare()
if (tx.gasPrice || tx.maxFeePerGas) {
this._increaseGasPrice()
}
this.replaced = true
await this._send() await this._send()
} }
@ -148,7 +156,10 @@ class Transaction {
const net = await this.manager._provider.getNetwork() const net = await this.manager._provider.getNetwork()
this.manager._chainId = net.chainId this.manager._chainId = net.chainId
} }
this.tx.chainId = this.manager._chainId
if (!this.tx.chainId) {
this.tx.chainId = this.manager._chainId
}
if (!this.tx.gasLimit || this.manager.config.ESTIMATE_GAS) { if (!this.tx.gasLimit || this.manager.config.ESTIMATE_GAS) {
const gas = await this._estimateGas(this.tx) const gas = await this._estimateGas(this.tx)
@ -161,15 +172,15 @@ class Transaction {
if (!this.manager._nonce) { if (!this.manager._nonce) {
this.manager._nonce = await this._getLastNonce() this.manager._nonce = await this._getLastNonce()
} }
this.tx.nonce = this.manager._nonce
if (this.tx.gasPrice || (this.tx.maxFeePerGas && this.tx.maxPriorityFeePerGas)) { if (!this.tx.nonce) {
return this.tx.nonce = this.manager._nonce
} }
const gasParams = await this._getGasParams() if (!this.tx.gasPrice && !this.tx.maxFeePerGas && !this.tx.maxPriorityFeePerGas) {
const gasParams = await this._getGasParams()
this.tx = Object.assign(this.tx, gasParams) this.tx = Object.assign(this.tx, gasParams)
}
} }
/** /**
@ -317,6 +328,10 @@ class Transaction {
// nonce is too low, trying to increase and resubmit // nonce is too low, trying to increase and resubmit
if (this._hasError(message, nonceErrors)) { if (this._hasError(message, nonceErrors)) {
if (this.replaced) {
console.log('Transaction with the same nonce was mined')
return // do nothing
}
console.log(`Nonce ${this.tx.nonce} is too low, increasing and retrying`) console.log(`Nonce ${this.tx.nonce} is too low, increasing and retrying`)
if (this.retries <= this.manager.config.MAX_RETRIES) { if (this.retries <= this.manager.config.MAX_RETRIES) {
this.tx.nonce++ this.tx.nonce++

View File

@ -91,16 +91,23 @@ const transactionTests = () => {
}) })
it('should cancel', async () => { it('should cancel', async () => {
const currentNonce = await this.manager._wallet.getTransactionCount('latest')
const tx = this.manager.createTx(tx3) const tx = this.manager.createTx(tx3)
setTimeout(() => tx.cancel(), 1000) setTimeout(() => tx.cancel(), 1000)
await sendTx(tx) const receipt = await sendTx(tx)
const transaction = await this.manager._provider.getTransaction(receipt.transactionHash)
transaction.value.toNumber().should.equal(0)
transaction.nonce.should.equal(currentNonce)
}) })
it('should replace', async () => { it('should replace', async () => {
const currentNonce = await this.manager._wallet.getTransactionCount('latest')
const tx = this.manager.createTx(tx3) const tx = this.manager.createTx(tx3)
setTimeout(() => tx.replace(tx4), 1000) setTimeout(() => tx.replace(tx4), 1000)
const receipt = await sendTx(tx) const receipt = await sendTx(tx)
const transaction = await this.manager._provider.getTransaction(receipt.transactionHash)
receipt.to.should.equal(tx4.to) receipt.to.should.equal(tx4.to)
transaction.nonce.should.equal(currentNonce)
}) })
it('should increase nonce', async () => { it('should increase nonce', async () => {
@ -118,7 +125,7 @@ const transactionTests = () => {
this.manager.config.ENABLE_EIP1559 = true this.manager.config.ENABLE_EIP1559 = true
}) })
it.skip('should send multiple txs', async () => { it('should send multiple txs', async () => {
const genTx = value => ({ const genTx = value => ({
value, value,
to: '0x0039F22efB07A647557C7C5d17854CFD6D489eF3', to: '0x0039F22efB07A647557C7C5d17854CFD6D489eF3',