flashbotsについて(メモ)
本記事の内容から何か損失などが出たとしても責任は取れませんのでご了承願います。
動機
自身のメモ的意味合いが強いです。
本当はpythonコードを動かして検証作業もしたかったのですが、なにせETH mainnetのガス代が高いので、勉強代として割り切れず、メモレベルとなっています。
内容は、下記sugerさんの記事に登場するflashbotsについてです。
sugerさんの記事にも引用がありますが、細かい話は以下にまとめられています。
概要
ブロックチェーンのトランザクションは、通常mempoolというトランザクション一時プールに送られて、そこからマイナー(バリデータ)つまりトランザクションを検証承認するnodeがピックアップして承認作業が行われ、ブロックチェーンに書き込まれる、という流れになります。
そして、mempoolに送信されたトランザクションは監視できる(これは別記事で確認しようと思います)。つまり、ネットワークに承認される前に誰でも見ることができる。
が、flashbotsを使えば、このmempoolをすっ飛ばしてマイナー(バリデータ)にトランザクションを渡すことができる、らしいです。
つまり、トランザクションが公になるのは承認後、ということ。
そして、flashbotsにトランザクションを渡すときに、複数のトランザクションをbundle(バンドル)することで、その並び順を固定して渡すことができ、また、そのトランザクション間に別の人のトランザクションを割り込まれないらしいです。
これを検証したかったのですが、pythonにてflashbotsライブラリを使えるネットワークがどうもETHメインネットのみっぽかったので、ガス代の高さが勉強代に見合わないと判断し、flashbotsの利用手前までで足を止めました。
(ETHメインネットでflashbotsが正しく動作することを確認。BASEネットワークで試みると正しく動作しなかったので、ETHのみと判断し、この先を断念しました。←安いネットワークでも動くよ、という情報があれば教えてください。
やったこと
ひとまず普通に連続してtxを送ってみる(python)。
Base mainnetにおいて、0.0001ETHをWETHにWrapするトランザクションを立て続けに4回送り、1分待ってから0.0004WETHをETHにUnwrapするトランザクションを送り、さらに1分待ってからそれぞれのトランザクションが何番目のブロックに格納されたのか、そしてブロック内の何番目に位置しているのかを確認してみます。
#base_wrap.py
import json
from web3 import Web3
from datetime import datetime
import time
baseRpc = "https://mainnet.base.org"
walletAddress = 'my_wallet_address'
walletKey = 'my_private_key'
WETH = '0x4200000000000000000000000000000000000006'
gas = 200000
web3 = Web3(Web3.HTTPProvider(baseRpc))
with open('WETH_ABI.json') as f:
wethABI = json.load(f)
wethContract = web3.eth.contract(address=WETH, abi=wethABI)
#Wrap transaction
nonce1 = web3.eth.get_transaction_count(walletAddress)
tx1 = wethContract.functions.deposit().build_transaction({
'value': web3.to_wei(0.0001, 'ether'),
'gas': gas,
'gasPrice': web3.eth.gas_price,
'nonce': nonce1
})
signedTx1 = web3.eth.account.sign_transaction(tx1, walletKey)
tx2 = wethContract.functions.deposit().build_transaction({
'value': web3.to_wei(0.0001, 'ether'),
'gas': gas,
'gasPrice': web3.eth.gas_price,
'nonce': nonce1 + 1
})
signedTx2 = web3.eth.account.sign_transaction(tx2, walletKey)
tx3 = wethContract.functions.deposit().build_transaction({
'value': web3.to_wei(0.0001, 'ether'),
'gas': gas,
'gasPrice': web3.eth.gas_price,
'nonce': nonce1 + 2
})
signedTx3 = web3.eth.account.sign_transaction(tx3, walletKey)
tx4 = wethContract.functions.deposit().build_transaction({
'value': web3.to_wei(0.0001, 'ether'),
'gas': gas,
'gasPrice': web3.eth.gas_price,
'nonce': nonce1 + 3
})
signedTx4 = web3.eth.account.sign_transaction(tx4, walletKey)
tx1Hash = web3.eth.send_raw_transaction(signedTx1.raw_transaction)
tx2Hash = web3.eth.send_raw_transaction(signedTx2.raw_transaction)
tx3Hash = web3.eth.send_raw_transaction(signedTx3.raw_transaction)
tx4Hash = web3.eth.send_raw_transaction(signedTx4.raw_transaction)
print('Send Wrap transaction1')
print(web3.to_hex(tx1Hash))
result = web3.eth.wait_for_transaction_receipt(tx1Hash)
status = result['status']
if status == 1:
print('Wrap Succeeded')
else:
print('Wrap Failed')
print('Send Wrap transaction2')
print(web3.to_hex(tx2Hash))
result = web3.eth.wait_for_transaction_receipt(tx2Hash)
status = result['status']
if status == 1:
print('Wrap Succeeded')
else:
print('Wrap Failed')
print('Send Wrap transaction3')
print(web3.to_hex(tx3Hash))
result = web3.eth.wait_for_transaction_receipt(tx3Hash)
status = result['status']
if status == 1:
print('Wrap Succeeded')
else:
print('Wrap Failed')
print('Send Wrap transaction4')
print(web3.to_hex(tx4Hash))
result = web3.eth.wait_for_transaction_receipt(tx4Hash)
status = result['status']
if status == 1:
print('Wrap Succeeded')
else:
print('Wrap Failed')
time.sleep(60)
#Unwrap function
funcUnwrap = wethContract.functions.withdraw(
web3.to_wei(0.0004, 'ether')
)
tx5 = funcUnwrap.build_transaction({
'value': 0,
'gas': gas,
'gasPrice': web3.eth.gas_price,
'nonce': nonce1 + 4
})
signedTx5 = web3.eth.account.sign_transaction(tx5, walletKey)
tx5Hash = web3.eth.send_raw_transaction(signedTx5.raw_transaction)
print('Send Unwarap transaction')
print(web3.to_hex(tx5Hash))
result = web3.eth.wait_for_transaction_receipt(tx5Hash)
status = result['status']
if status == 1:
print('Unwrap Succeeded')
else:
print('Unwrap Failed')
time.sleep(60)
#ブロック番号とブロック内のインデックス確認
receipt1 = web3.eth.get_transaction_receipt(tx1Hash)
block_number1 = receipt1['blockNumber']
index1 = receipt1['transactionIndex']
print("tx1:")
print("blockNumber:"+ str(block_number1) + ", index:" + str(index1))
receipt2 = web3.eth.get_transaction_receipt(tx2Hash)
block_number2 = receipt2['blockNumber']
index2 = receipt2['transactionIndex']
print("tx2:")
print("blockNumber:"+ str(block_number2) + ", index:" + str(index2))
receipt3 = web3.eth.get_transaction_receipt(tx3Hash)
block_number3 = receipt3['blockNumber']
index3 = receipt3['transactionIndex']
print("tx3:")
print("blockNumber:"+ str(block_number3) + ", index:" + str(index3))
receipt4 = web3.eth.get_transaction_receipt(tx4Hash)
block_number4 = receipt4['blockNumber']
index4 = receipt4['transactionIndex']
print("tx4:")
print("blockNumber:"+ str(block_number4) + ", index:" + str(index4))
receipt5 = web3.eth.get_transaction_receipt(tx5Hash)
block_number5 = receipt5['blockNumber']
index5 = receipt5['transactionIndex']
print("tx5:")
print("blockNumber:"+ str(block_number5) + ", index:" + str(index5))
2回実行してみました。
1回目
tx1~tx4は同じブロック番号に格納され、しかも順番も連番になっています。当然1分遅れて実行したtx5は異なるブロック番号に格納されています。
2回目
今度はtx1~tx3は同じブロックに格納されていますが、tx2とtx3の間に別のトランザクションが割り込んでいることが分かります。
また、tx4はtx1~tx3とは別のブロックに取り込まれています。
そして、当然tx5はさらに別のブロックとなります。
このように、通常のトランザクション送信だとtx1~tx4について、同ブロック連番になることもあれば、別ブロックだったり間に別トランザクションが割り込んだり、ということが発生します。
flashbotsを使うと、tx1~tx4が同ブロック連番、という形を崩さずに実行できると解釈しています。
flashbotsライブラリが動作するかを確認してみる(ETHメインネット)。
flashbotsのインストール
pip3 install flashbots --break-system-packages
flashbotsライブラリを最新バージョンにアップデート
pip3 install flashbots --upgrade --break-system-packages
upgradeを実行すると、私が利用しているweb3.py(バージョン7.3.0)には対応していない旨のエラーおよびバージョン6.20.3までは対応している旨のエラーが出力されました。
web3.pyのバージョンを6.20.3に落とす。
pip install web3==6.20.3 --break-system-packages
flashbotsが正しく動いているかを確認するpythonコード
(ChatGPTに、flashbotsライブラリが正しく動くことを確認するpythonコードを書いてくれるようにお願いしました。)
#test_flashbots.py
from web3 import Web3
from eth_account import Account
from flashbots import flashbot
# RPC URL を指定 (適切な RPC URL を設定してください)
RPC_URL = "eth_mainnet_rcp_url" # 必要に応じて変更
# サンプル秘密鍵 (適切な形式で設定してください)
PRIVATE_KEY = "my_private_key"
# Flashbots の動作確認関数
def check_flashbots():
try:
# Web3 インスタンスを作成
web3 = Web3(Web3.HTTPProvider(RPC_URL))
if not web3.provider.is_connected():
print("Web3 is not connected. Check your RPC URL.")
return
print(f"Connected to Ethereum network: {web3.client_version}")
from pkg_resources import get_distribution
web3_version = get_distribution("web3").version
print(f"web3.py version: {web3_version}")
# サインアカウントを作成
account = Account.from_key(PRIVATE_KEY)
print(f"Account address: {account.address}")
# Flashbots を初期化
flashbot(web3, account)
# Flashbots シミュレーションをテスト (ブロック番号を指定)
block_number = web3.eth.block_number
print(f"Current block number: {block_number}")
base_fee = web3.eth.get_block("latest")["baseFeePerGas"]
print(f"Current base fee: {base_fee}")
max_fee = base_fee + web3.to_wei(2, "gwei") # ベース手数料 + 2 Gwei
# ダミーのトランザクションデータ
tx = {
"to": account.address,
"value": 0,
"gas": 21000,
"maxPriorityFeePerGas": web3.to_wei(1, "gwei"),
"maxFeePerGas": max_fee,
"nonce": web3.eth.get_transaction_count(account.address),
"chainId": 1, # Ethereum Mainnet
}
signed_tx = account.sign_transaction(tx)
bundle = [{"signed_transaction": signed_tx.rawTransaction}]
# シミュレーションを試行
try:
web3.flashbots.simulate(bundle, block_number + 1)
print("Flashbots simulation successful.")
except Exception as e:
print(f"Flashbots simulation failed: {e}")
except ImportError as e:
print(f"Required library is missing: {e}")
except Exception as e:
print(f"An error occurred: {e}")
# 実行
if __name__ == "__main__":
check_flashbots()
実行結果
successfulと出たので、たぶん動くんだと思います・・・(笑)
安いネットワークで・・・
例えば、Sepolia ETH(テストネット)。
rpc_urlをSepoliaにして、ダミートランザクションデータのchainIdを11155111に。
これで実行すると下記。
Baseメインネットで・・・chainIdは8453
同じでした。
その他のネットワークを試してはいないですが、諦めました。