fix bugs improve stability

master
cyhhao 2 years ago
parent c2956f0c41
commit e3bb76e7bd

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

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

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

Loading…
Cancel
Save