DeFiで落ちてるお金を拾いたいブログ

DeFiについてのメモ書きやポエムを投下する予定です

Ethereumの1ブロック先を予測せよ

はじめに

こんにちは。住民税の通知書が今日届いたビビドット🫡 (@vividot) / Twitterです。出国していいですか?

今回は知っている人は知っているし知らない人は知らない、Ethereumのブロックの時間について少し不思議な話を書きたいと思います。 地味な小ネタですが、塵も積もれば山となることは僕の住民税が保証します。

直近ではmegamiのmintにも関連する話で、狙っていたmegamiを人に取られた…という人は是非最後まで読んでみてください。

ブロックの時刻 とは

Ethereumのブロックにはそれぞれ時刻(timestamp)が記録されています。 例えば、15121640番目のブロックをEtherscanで見ると「2022年7月11日 午後1時42分5秒」がこのブロックの時刻であることが分かります。

さて、この「ブロックの時刻」は、一体いつなのでしょうか?直感的には「ブロックがマイニングされた時刻」ですが、実は「ブロックのマイニングを始めた時刻」なのです。 そして、「ブロックのマイニングを始めた時刻」というのは「マイニングされた前ブロックを受け取った時刻」でもあります。

これを示したのが次の図です。上が現実世界での時間軸であり、t10はBlock#10がマイニングされた時間です。 そしてBlock#11のtimestampはt10+α(αはBlock#10がマイニングされてからネットワークを伝搬するまでにかかる時間)です。

つまり、Ethereumの世界というのは現実世界より少し遅れていて、まだマイニングされていない次のブロックの時刻は予測可能なのです(タイトル回収)。

確かめてみよう

弊ノードでブロックを受け取った時刻を出力しました。

EtherscanでBlock #12297273 を見てみます。

Block #12297273 のtimestampが1つ前のBlock #12297272 を弊ノードが受け取った時間、2021/04/23 15:31:36(UTC) と同じであることが確認できました。 他のブロックについても同様なので是非Etherscanで確認してみてください。

それがなんだってんだい

TXが格納されるブロックのtimestampをスマートコントラクトで利用したいとき、Solidityに存在する特別な変数block.timestampを使うことができます。 この変数はスマートコントラクトでいくつかの目的を実現するときに利用されており、前述の話を用いることでEthereumの世界で有利に立ち回ることができます。 今回はそのうち2つを紹介します。

①乱数を操作する

乱数(ランダムな数)は、プログラムで"確率"を実現する時によく用いられます(e.g. レアリティのあるアイテム)。 一方、ブロックチェーンでは乱数を公平に生成するのが非常に難しいです。 そのため、ハッシュ関数と呼ばれる「あるデータを規則性のないデータに変換する関数」を応用し、疑似乱数とすることが多いです。

ここで、ハッシュ関数に突っ込む値というのはすべてのユーザにとって未知である必要があります。 ハッシュ関数自体が既知であるため、入力さえわかればスマートコントラクトを実行せずとも疑似乱数の値がわかってしまうためです。

しかし、スマートコントラクトの開発者は乱数を用いたいときに、次のようなコードを書いてしまう場合があります。

uint256 random = uint256(keccak256(abi.encodePacked(msg.sender, block.timestamp))) % 100;

このコードは、TXの送信者・ブロックの時刻を連結してハッシュ関数に突っ込み、0~99の乱数を得ようとしています。

前述の通り、ブロックの時刻は未知でないため、この乱数は脆弱です。 新しいブロックがマイニングされるたびに手元で乱数がいくつになるかを計算し、任意の結果が得られたときにTXを出すことができます。

有識者の方は「いやいや、そんなコード書かないでしょ」と思われるかもしれませんが、CryptoZombiesのLesson4のコードが次の通りです ハハッ

②セールでTXを誰よりも早く叩きつける

セールと一括りにしましたが、NFTのPrivate Saleだったり、First Come First Serve方式のIDOだったり、先着ボーナスのあるIDO(e.g. BitDAO)だったりと様々です。

これらのセールは「10:00から販売開始」のように販売時刻が決まっていることが多いですが、この販売方式を実現する方法は2種類あります。

  1. 運営が10時と同時に販売開始をトリガーするTXを出すこと
  2. スマートコントラクトでTXの時間をチェックすること

今回は後者の話ですが、このチェックする部分だけ書き出すと次のようになります。 要はセール開始の時間を示すstartTimeとブロックの時刻を見比べて、ブロックの時刻の方が小さい(つまりまだセールが開始していない)場合にTXをFailedにしています。

require(block.timestamp >= startTime, "Sale has not started!");        

初心者はセールで必ず1ブロック遅れてしまう

ここで、あなたは直コンやガスの設定ができない初心者でNFTのPublic Saleでサイトからmintしたいとします。 販売時間になってサイトでmintボタンが押せるようになりましたが、TXを出そうとすると次みたいな画面が出ます。1回Metamaskを閉じて再度mintしようとしても同じ。なぜでしょうか?

これは、MetaMaskくんがTXを出す前にそのTXが最新のブロックの状態で実行可能かどうかをチェックしてくれているためです。 ここでEthereumの世界は現実世界より少し遅れている…という話を思い出してください。

Block#Yがマイニングされていない段階では、TXを実行可能かチェックするときにBlock#Xのtimestampを参照します。 つまり、現実世界で10時を過ぎていても、Ethereumの世界はまだ10時を過ぎていないのでエラーになります。

そしてこの場合、Block#Yがマイニングされた後にTXを出せるようになるため、最速でTXがブロックに入ったとしてもセール開始から1ブロック遅れてしまいます。

ガスの設定を覚えても、早漏TXを出して失敗しがち

次に、あなたがMetamaskのエラーを無視してガスの設定をし、TXを出すことができるようになったとします。 セールのファーストブロックに滑り込むぞ、と販売時間の10時ぴったりにTXを出しましたが…

なぜかTXが格納されるのが早すぎて失敗してしまいました。たぶん、多くの方がこの現象を経験したと思います。 これも図の通りで現実世界の時間とEthereumの時間がずれているために発生する現象です。

ほなどうしたらええねん

以上をまとめると「Block#Xがマイニングされた時間から、次のBlock#Yがセール開始条件である10時以降を満たすことを予測し、Block#YにTXが含まれるようにTXを出す」とセールに最速でTXを叩きつけることができます。

あとがき

これは生きてるエッジです。まあ、最近は知ってる人も増えてきましたし、そもそも利用できる機会が減ってきましたが…。 儲かったら投げ銭くだしあ。

ところで、Ethereumくん年初来-64%なんですが、どうやって住民税払ったらいいですか?