From e6f1450f0e09ea4117097eb32e0a43418218f69b Mon Sep 17 00:00:00 2001 From: KillariDev Date: Fri, 20 Dec 2024 11:46:14 +0200 Subject: [PATCH 1/2] add timeout (60 secs), reduce get simulation stack spamming to interceptor, add more information to user --- app/ts/components/Settings.tsx | 2 +- app/ts/components/Submit.tsx | 1 + app/ts/components/Transactions.tsx | 8 ++-- app/ts/library/bundleUtils.ts | 8 ++-- app/ts/library/flashbots.ts | 59 ++++++++++++++++-------------- 5 files changed, 42 insertions(+), 36 deletions(-) diff --git a/app/ts/components/Settings.tsx b/app/ts/components/Settings.tsx index 038b8a4..7cea80f 100644 --- a/app/ts/components/Settings.tsx +++ b/app/ts/components/Settings.tsx @@ -157,7 +157,7 @@ export const SettingsModal = ({ display, bouquetNetwork, bouquetSettings }: { di { relayMode.value.value === 'mempool' ? <>
- Mempool Simulation RPC URL (a RPC with eth_simulateV1 support) + Mempool Simulation RPC URL (an RPC with eth_simulateV1 support) ) => validateMempoolSimulationRpcEndpointInput(e.currentTarget.value)} value={mempoolSimulationRpcEndpoint.value.value} type='text' className='bg-transparent outline-none placeholder:text-gray-600' placeholder='https://' />
diff --git a/app/ts/components/Submit.tsx b/app/ts/components/Submit.tsx index c9ef0e1..2fe5514 100644 --- a/app/ts/components/Submit.tsx +++ b/app/ts/components/Submit.tsx @@ -279,6 +279,7 @@ export const Submit = ({

Gas: {formatUnits(getMaxBaseFeeInFutureBlock(blockInfo.value.baseFee, bouquetNetwork.value.blocksInFuture), 'gwei')} gwei + {formatUnits(bouquetNetwork.value.priorityFee.toString(), 'gwei')} gwei priority

Transaction Submit RPC: { bouquetNetwork.value.mempoolSubmitRpcEndpoint }

+

Transaction Simulation RPC: { bouquetNetwork.value.mempoolSimulationRpcEndpoint }

: <>

Gas: {formatUnits(getMaxBaseFeeInFutureBlock(blockInfo.value.baseFee, bouquetNetwork.value.blocksInFuture), 'gwei')} gwei + {formatUnits(bouquetNetwork.value.priorityFee.toString(), 'gwei')} gwei priority

Relays: simulation:{bouquetNetwork.value.simulationRelayEndpoint}, submit:{bouquetNetwork.value.submissionRelayEndpoint} (Block {blockInfo.value.blockNumber.toString()})

diff --git a/app/ts/components/Transactions.tsx b/app/ts/components/Transactions.tsx index 74ef829..5632cb7 100644 --- a/app/ts/components/Transactions.tsx +++ b/app/ts/components/Transactions.tsx @@ -56,7 +56,7 @@ export const Transactions = ({ try { const uniqueAddresses = [...new Set(bundle.value.transactions.map((tx) => tx.to ? addressString(tx.to) : null ).filter(addr => addr))] as string[] const abis: (string | undefined)[] = [] - + const network = getNetwork(bouquetSettings.value, provider.value?.chainId || 1n) const requests = await Promise.all( uniqueAddresses.map((address) => @@ -93,7 +93,6 @@ export const Transactions = ({ useSignalEffect(() => { if (interfaces.value && bundle.value) { parseTransactions() - compareWithInterceptor() } if (provider.value && provider.value.isInterceptor && !interceptorComparison.value.intervalId) createCompareInterval() }) @@ -141,9 +140,10 @@ export const Transactions = ({ } async function createCompareInterval() { - if (!provider.value || !provider.value.isInterceptor) return; + if (!provider.value || !provider.value.isInterceptor) return const different = await compare() - interceptorComparison.value = { different, intervalId: setInterval(compareWithInterceptor, 20000)} + clearInterval(interceptorComparison.value.intervalId) + interceptorComparison.value = { different, intervalId: setInterval(compareWithInterceptor, 10000)} } return ( diff --git a/app/ts/library/bundleUtils.ts b/app/ts/library/bundleUtils.ts index 4e0e6d7..d068a26 100644 --- a/app/ts/library/bundleUtils.ts +++ b/app/ts/library/bundleUtils.ts @@ -55,14 +55,14 @@ export const getRawTransactionsAndCalculateFeesAndNonces = async (bundle: Flashb return transactions } -export const createBundleTransactions = async ( +export const createBundleTransactions = ( bundle: Bundle, signers: Signers, blockInfo: BlockInfo, blocksInFuture: bigint, fundingAmountMin: bigint, -): Promise => { - return await Promise.all(bundle.transactions.map(async ({ from, to, gasLimit, value, input, chainId }) => { +): FlashbotsBundleTransaction[] => { + return bundle.transactions.map(({ from, to, gasLimit, value, input, chainId }) => { const gasOpts = { maxPriorityFeePerGas: blockInfo.priorityFee, type: 2, @@ -99,5 +99,5 @@ export const createBundleTransactions = async ( ...gasOpts, }, } - })) + }) } diff --git a/app/ts/library/flashbots.ts b/app/ts/library/flashbots.ts index 20fb7a1..e1ce532 100644 --- a/app/ts/library/flashbots.ts +++ b/app/ts/library/flashbots.ts @@ -68,7 +68,7 @@ export async function simulateBundle( ): Promise { if (network.blocksInFuture <= 0n) throw new Error('Blocks in future is negative or zero') const maxBaseFee = getMaxBaseFeeInFutureBlock(blockInfo.baseFee, network.blocksInFuture) - const bundleTransactions = await createBundleTransactions(bundle, signers, blockInfo, network.blocksInFuture, fundingAmountMin) + const bundleTransactions = createBundleTransactions(bundle, signers, blockInfo, network.blocksInFuture, fundingAmountMin) const txs = await getRawTransactionsAndCalculateFeesAndNonces(bundleTransactions, provider.provider, blockInfo, maxBaseFee) const bigIntify = (ethersValue: ethers.BigNumberish | null | undefined | AddressLike) => ethersValue ? BigInt(ethersValue.toString()) : undefined @@ -94,32 +94,37 @@ export async function simulateBundle( })) } ], traceTransfers: false, validation: true }, 'latest' ] } as const const serialized = serialize(EthSimulateV1Params, data) - const request = await fetch(network.mempoolSimulationRpcEndpoint, { method: 'POST', body: JSON.stringify({ jsonrpc: '2.0', id: 0, ...serialized }), headers: { 'Content-Type': 'application/json' } }) - const response = JsonRpcResponse.parse(await request.json()) - if ('error' in response) { - console.log(response) - throw new Error(response.error.message) - } - const parsed = EthSimulateV1Result.parse(response.result) - const calls = parsed[0].calls + try { + const request = await fetch(network.mempoolSimulationRpcEndpoint, { method: 'POST', body: JSON.stringify({ jsonrpc: '2.0', id: 0, ...serialized }), headers: { 'Content-Type': 'application/json' }, signal: AbortSignal.timeout(60000) }) + const response = JsonRpcResponse.parse(await request.json()) + if ('error' in response) { + console.log(response) + throw new Error(response.error.message) + } + const parsed = EthSimulateV1Result.parse(response.result) + const calls = parsed[0].calls - return { - totalGasUsed: calls.reduce((a, b) => a + b.gasUsed, 0n), - firstRevert: calls.map((call, index) => { - const to = bigIntify(txs[index].transaction.to) - if (to === undefined) throw new Error('to is undefined') - const from = bigIntify(txs[index].transaction.from) - return { - ...call, - toAddress: addressString(to), - fromAddress: from !== undefined ? addressString(from) : undefined, - } - }).find((txSim) => txSim.status === 'failure'), - results: calls, - gasFees: txs.reduce((totalFee, tx, currentIndex) => { - if (tx.transaction.gasPrice) return totalFee + BigInt(tx.transaction.gasPrice) * calls[currentIndex].gasUsed - return totalFee + min(parsed[0].baseFeePerGas + BigInt(tx.transaction.maxPriorityFeePerGas || 0n), BigInt(tx.transaction.maxFeePerGas || 0n)) - }, 0n), + return { + totalGasUsed: calls.reduce((a, b) => a + b.gasUsed, 0n), + firstRevert: calls.map((call, index) => { + const to = bigIntify(txs[index].transaction.to) + if (to === undefined) throw new Error('to is undefined') + const from = bigIntify(txs[index].transaction.from) + return { + ...call, + toAddress: addressString(to), + fromAddress: from !== undefined ? addressString(from) : undefined, + } + }).find((txSim) => txSim.status === 'failure'), + results: calls, + gasFees: txs.reduce((totalFee, tx, currentIndex) => { + if (tx.transaction.gasPrice) return totalFee + BigInt(tx.transaction.gasPrice) * calls[currentIndex].gasUsed + return totalFee + min(parsed[0].baseFeePerGas + BigInt(tx.transaction.maxPriorityFeePerGas || 0n), BigInt(tx.transaction.maxFeePerGas || 0n)) + }, 0n), + } + } catch (error) { + if (error instanceof DOMException && error.name === 'TimeoutError') throw new Error(`Simulation timed out to RPC: ${network.mempoolSimulationRpcEndpoint}`) + throw error } } case 'relay': { @@ -161,7 +166,7 @@ export async function sendBundle(bundle: Bundle, targetBlock: bigint, fundingAmo if (network.blocksInFuture <= 0n) throw new Error('Blocks in future is negative or zero') const maxBaseFee = getMaxBaseFeeInFutureBlock(blockInfo.baseFee, network.blocksInFuture) const transactions = (await getRawTransactionsAndCalculateFeesAndNonces( - await createBundleTransactions(bundle, signers, blockInfo, network.blocksInFuture, fundingAmountMin), + createBundleTransactions(bundle, signers, blockInfo, network.blocksInFuture, fundingAmountMin), provider.provider, blockInfo, maxBaseFee, From 63281f732df52d72058606bb030bd3552afe30fb Mon Sep 17 00:00:00 2001 From: KillariDev <13102010+KillariDev@users.noreply.github.com> Date: Mon, 23 Dec 2024 10:12:12 +0200 Subject: [PATCH 2/2] Update app/ts/components/Transactions.tsx Co-authored-by: Micah Zoltu --- app/ts/components/Transactions.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/ts/components/Transactions.tsx b/app/ts/components/Transactions.tsx index 5632cb7..46d71c8 100644 --- a/app/ts/components/Transactions.tsx +++ b/app/ts/components/Transactions.tsx @@ -143,7 +143,7 @@ export const Transactions = ({ if (!provider.value || !provider.value.isInterceptor) return const different = await compare() clearInterval(interceptorComparison.value.intervalId) - interceptorComparison.value = { different, intervalId: setInterval(compareWithInterceptor, 10000)} + interceptorComparison.value = { different, intervalId: setInterval(compareWithInterceptor, 10000) } } return (