fix bugs improve stability

master
cyhhao 2 years ago
parent c2956f0c41
commit e3bb76e7bd

@ -22,7 +22,7 @@ export class TxManager {
constructor(
contract: ethers.Contract,
chainId: number,
constOptions?: {
constOptions: {
blockTimeSec?: number
gasLimitRatio?: number
rbfTimes?: number
@ -35,12 +35,12 @@ export class TxManager {
this.contract = contract
this.price = null
this.cancel = false
this.blockTimeSec = constOptions?.blockTimeSec || 3
this.gasLimitRatio = constOptions?.gasLimitRatio || 1.2
this.rbfTimes = constOptions?.rbfTimes || 3
this.boardcastTimes = constOptions?.boardcastTimes || 3
this.waitDistance = constOptions?.waitDistance || 10
this.minRBFRatio = constOptions?.minRBFRatio || 1.3
this.blockTimeSec = constOptions.blockTimeSec || 3
this.gasLimitRatio = constOptions.gasLimitRatio || 1.2
this.rbfTimes = constOptions.rbfTimes || 3
this.boardcastTimes = constOptions.boardcastTimes || 3
this.waitDistance = constOptions.waitDistance || 10
this.minRBFRatio = constOptions.minRBFRatio || 1.3
}
async FreshBaseGas(): Promise<ethers.providers.FeeData | null> {
@ -57,7 +57,7 @@ export class TxManager {
const signer = this.contract.signer
let nonce = await this.getNonce()
this._deltaCount++
console.log("clearPendingNonce", nonce, num)
console.error("clearPendingNonce", nonce, num)
let price = await this.FreshBaseGas()
let txs = []
for (let i = 0; i < num; i++) {
@ -67,22 +67,19 @@ export class TxManager {
gasLimit: 21000,
type: 2,
chainId: this.chainId,
maxFeePerGas: price!
.maxFeePerGas!.mul((rbfRatio * 100) | 0)
.div(100),
maxFeePerGas: price!.maxFeePerGas!.mul((rbfRatio * 100) | 0).div(100),
maxPriorityFeePerGas: price!
.maxPriorityFeePerGas!.mul((rbfRatio * 100) | 0)
.div(100),
})
txs.push(res)
txs.push(res.then((tx) => tx.wait()))
}
await Promise.all(txs)
}
async getNonce(): Promise<number> {
if (!this._initialPromise) {
this._initialPromise =
this.contract.signer.getTransactionCount("pending")
this._initialPromise = this.contract.signer.getTransactionCount("pending")
}
const deltaCount = this._deltaCount
this._deltaCount++
@ -91,20 +88,19 @@ export class TxManager {
async SendCall(_method: string, _args: any[]): Promise<any> {
const nonce = await this.getNonce()
if (this.queueCurrNonce < 0) this.queueCurrNonce = nonce
let unsignedTx = await this.contract.populateTransaction[_method](
..._args
)
let unsignedTx = await this.contract.populateTransaction[_method](..._args)
unsignedTx.nonce = nonce
unsignedTx.chainId = this.chainId
// estimateGas check
let gasLimit = await this.contract.provider.estimateGas(unsignedTx)
unsignedTx.gasLimit = gasLimit
.mul((this.gasLimitRatio * 100) | 0)
.div(100)
unsignedTx.gasLimit = gasLimit.mul((this.gasLimitRatio * 100) | 0).div(100)
let retryRBF = this.rbfTimes
let rbfCount = 0
let lastPrice = null
let waitTxs: Record<string, any> = {}
while (retryRBF > 0 && !this.cancel) {
// set gas price
let price
@ -113,15 +109,13 @@ export class TxManager {
} catch (e) {
price = this.price
} finally {
if (
!price ||
!price.maxFeePerGas ||
!price.maxPriorityFeePerGas
) {
if (!price || !price.maxFeePerGas || !price.maxPriorityFeePerGas) {
throw new Error("get fee data failed")
}
}
if (lastPrice) {
// RBF
console.error("[tx-manager] RBF", "nonce:", nonce, "cnt:", rbfCount)
let maxFeePerGasMin = lastPrice
.maxFeePerGas!.mul((this.minRBFRatio * 100) | 0)
.div(100)
@ -145,24 +139,18 @@ export class TxManager {
}
// sign
let signedTx = await this.contract.signer.signTransaction(
unsignedTx
)
let signedTx = await this.contract.signer.signTransaction(unsignedTx)
let retryBoardcast = this.boardcastTimes
let txRes = null
let txRes: ethers.providers.TransactionResponse | null = null
while (retryBoardcast > 0 && !this.cancel) {
if (
this.queueCurrNonce < 0 ||
this.queueCurrNonce + 1 == nonce
) {
if (nonce <= this.queueCurrNonce + 1) {
// Arrive in line
retryBoardcast--
} else if (nonce - this.queueCurrNonce > this.waitDistance) {
// Too far away don't boardcast, waitTime = int(distance / groupSize) * blockTime + 1s
const waitTime =
(((nonce - this.queueCurrNonce) / this.waitDistance) |
0) *
(((nonce - this.queueCurrNonce) / this.waitDistance) | 0) *
this.blockTimeSec *
1000 +
1000
@ -174,62 +162,93 @@ export class TxManager {
try {
// send
txRes = await this.contract.provider.sendTransaction(
signedTx
)
await new Promise((r) =>
setTimeout(r, (this.blockTimeSec / 2) * 1000)
)
txRes = await this.contract.provider.sendTransaction(signedTx)
await new Promise((r) => setTimeout(r, (this.blockTimeSec / 2) * 1000))
} catch (e: Error | any) {
if (e.code == ethers.errors.NONCE_EXPIRED) {
// ignore if tx already in mempool
} else if (e.code == ethers.errors.SERVER_ERROR) {
// ignore if tx already in mempool
} else if (e.code == ethers.errors.REPLACEMENT_UNDERPRICED) {
// gas price too low, rbf++ but total rbf times < rbfTimes*2
if (rbfCount < this.rbfTimes * 2) {
retryRBF++
}
} else if (e.code == ethers.errors.INSUFFICIENT_FUNDS) {
console.error("insufficient funds!")
throw Error("insufficient funds!")
} else {
console.error(
"[tx-manager] sendTransaction",
nonce,
e.code,
e.message
)
console.error("[tx-manager] sendTransaction", nonce, e.code, e.message)
}
// console.error(
// "[tx-manager] sendTransaction",
// nonce,
// this.queueCurrNonce,
// e.code,
// e.message
// )
}
if (txRes) {
// wait
try {
let receipt =
await this.contract.provider.waitForTransaction(
txRes.hash,
1,
this.blockTimeSec * 1000 + 1000
)
if (receipt) {
this.queueCurrNonce =
txRes.nonce > this.queueCurrNonce
? txRes.nonce
: this.queueCurrNonce
waitTxs[txRes.hash] = txRes
let done = new Promise((resolve, reject) => {
let errCnt = 0
let reportError = (e: Error) => {
errCnt++
if (errCnt >= Object.keys(waitTxs).length) {
reject(new Error("all timeout"))
}
}
for (let hash of Object.keys(waitTxs)) {
// console.error("wait start", nonce, hash)
this.contract.provider
.waitForTransaction(hash, 1, this.blockTimeSec * 1000 + 1000)
.then((receipt) => {
resolve(receipt)
return receipt
}
} catch (e: Error | any) {
})
.catch((e) => {
if (e.code == ethers.errors.TIMEOUT) {
// ignore timeout
} else {
console.error(
"[tx-manager] waitForTransaction",
nonce,
txRes.hash,
hash,
e.code,
e.reason
)
}
// console.error(
// "[tx-manager] waitForTransaction",
// nonce,
// hash,
// e.code,
// e.reason
// )
// console.error("wait", nonce, hash, e.code)
reportError(e)
})
}
})
try {
let receipt = await done
this.queueCurrNonce =
nonce > this.queueCurrNonce ? nonce : this.queueCurrNonce
// console.error("wait done", nonce)
return receipt
} catch (e) {
// ignore
}
} else {
// send first time failed, wait 1s then try again
await new Promise((r) => setTimeout(r, 1000))
}
}
retryRBF--
rbfCount++
}
throw new Error("send tx failed")
throw new Error(`send tx failed: ${nonce}`)
}
}

@ -25,6 +25,26 @@ const evmNetworks: Record<number, any> = {
},
contracts: { git3: "" },
},
5: {
name: "Goerli",
rpc: ["https://eth-goerli.g.alchemy.com/v2/asrXwNuiK9my-cZJYZ_ooo4q-lDw8HLm"],
nativeCurrency: {
name: "Goerli Ether",
symbol: "ETH",
decimals: 18,
},
explorers: [
{
name: "etherscan-goerli",
url: "https://goerli.etherscan.io",
standard: "EIP3091",
},
],
txConst: {
blockTimeSec: 15,
},
contracts: { git3: "0x80F4b977F9C1d21FF6fDDd56C3CA59eeD5745B58" },
},
3334: {
name: "Web3Q Galileo",
nativeCurrency: {
@ -41,7 +61,7 @@ const evmNetworks: Record<number, any> = {
},
],
txConst: {
blockTimeSec: 6,
blockTimeSec: 7,
},
contracts: { git3: "0x59ef6b2dbfE86CcAaD84E2d8e78177f528521Da9" },
},
@ -65,6 +85,7 @@ const evmNetworks: Record<number, any> = {
],
txConst: {
blockTimeSec: 30,
boardcastTimes: 5,
},
contracts: { git3: "0xF56A1dd941667911896B9B872AC79E56cfc6a3dB" },
},

@ -11,6 +11,10 @@ const ns: Record<string, any> = {
chainId: 3141,
resolver: "",
},
goerli: {
chainId: 5,
resolver: "0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e",
},
}
export default ns

@ -224,6 +224,8 @@ program
let nonce = await protocol.wallet.getTransactionCount()
console.log(`current nonce: ${nonce}`)
await txManager.clearPendingNonce(num)
nonce = await protocol.wallet.getTransactionCount()
console.log(`current nonce: ${nonce}`)
})
// Todo: set-wallet temporarily useless

Loading…
Cancel
Save