I'm writing a bot that monitors a DeFi protocol for liquidation opportunities. The bot behaves like in the pseudo-code below:
let pending_txs: [];
for every_new_block in Ethereum {
// Check the state of pending txs.
for pending_tx in pending_txs {
let receipt = get_receipt(pending_tx);
if receipt != null {
// If we got a receipt from Ethereum, clear the tx from the local state.
delete_tx_from_array(pending_tx);
} else {
// Otherwise, replace the tx and update it in the array.
let new_gas_price = bump_gas_price(pending_tx.gas_price);
let tx = send_tx(new_gas_price);
update_tx_in_array(tx);
}
}
...
// Search for new liquidation opportunities.
if liquidation_opportunity and if !pending_tx_for_liquidation_opportunity {
let tx = send_tx(initial_gas_price);
add_tx_in_array(tx);
}
}
The above works well most of the times, but there it an exception:
- Submit tx
0x123
- Wait 15 seconds, check if we got a receipt from Ethereum.
- If we didn't, re-calculate the gas price and send a replacement tx
0xabc
. - By the time the replacement tx
0xabc
is broadcast to the Ethereum network, the0x123
gets mined. - The bot will attempt to replace the
0xabc
tx forever, because the receipt is alwaysnull
for0xabc
.
When I update the tx in the array, I also override the previous tx hash (this makes the implementation simple). In other words, the script ceases to be aware of past tx hashes once it broadcasts a replacement tx, which causes an infinite regress when a racing condition occurs.
The question is whether there is any way to avoid this situation? I've only run my bot on Rinkeby, against an Infura node. Maybe it's something that happens frequently on testnets, but rarely on mainnet?
I know that one solution is to modify the code to track multiple tx hashes per liquidation opportunity, but that would greatly increase the complexity of the implementation (I'm using Rust).