1 章の用語表で触れたとおり、チェーンに書き込むと手数料がかかります。この章では、その手数料が設計そのものを決めてしまう例を扱います。

材料は、2 本目の取引に載っていた 2,872 バイトの暗号化済みメタデータです。これがどこに置かれているのか、そしてなぜそこなのかを確かめます。

書き込み先は 2 種類ある

契約(チェーン上のプログラム)がデータを残す場所は、大きく 2 つあります。

保管領域(storage)は、契約が自分で持つ記憶領域です。あとから契約自身が読み返せます。残高やフラグのように、プログラムの動作に使う値はここに置きます。

イベントログ(log)は、「こういうことが起きた」という記録を外向きに残す仕組みです。前章までに見てきた TransferMetadataCreated はこちらです。

両者には決定的な違いが 2 つあります。値段と、読み返せるかどうかです。

単価を並べる

Ethereum の仕様書(Yellow Paper、および現行の実行仕様 execution-specs)に、それぞれの単価が定められています。

なお topic は、イベントに付く検索用の見出しです。1 つのイベントに最大 4 個まで付けられ、外部の索引はここを手がかりに「この種類のイベントだけ」「このアドレスが出したものだけ」と絞り込みます。

操作単価
保管領域に 32 バイト書く(ゼロから非ゼロへ)20,000 gas
↑ そのスロットに取引内で初めて触る場合の加算+2,100 gas
ログの基本料375 gas
ログの topic 1 個あたり375 gas
ログのデータ 1 バイトあたり8 gas

2 行目は 2021 年の EIP-2929 で入ったもので、同じ取引の中で初めて触る保管領域には割増がかかります。新しく書き込むときは必ずこれが乗るので、実効単価は 1 スロットあたり 22,100 gas です。

保管領域は 32 バイト単位で、しかも 1 単位が 20,000 gas です。ログはデータ 1 バイトが 8 gas。単位が違うので、そのままでは比べにくいのですが、実際の量を当てはめると差がはっきりします。

2,872 バイトで計算してみる

2 本目の取引が出した MetadataCreated には、topic が 2 個(イベントの種類を表すものと、createdBy の 1 個)付いていました。

ログに出した場合
  375(基本料)+ 375 × 2(topic)+ 8 × 2,872(データ)= 24,101 gas

保管領域に書いた場合
  保管領域は 32 バイトを 1 区画(スロット)として扱う
  2,872 ÷ 32 = 89.75 → 90 スロット
  (20,000 + 2,100) × 90 = 1,989,000 gas

同じ 2,872 バイトを保管領域に書く場合とイベントログに出す場合を比べた図。左に「暗号化されたメタデータ 2,872 バイト」の箱があり、そこから 2 本の矢印が出ている。上の破線の矢印は「契約の保管領域に書く/22,100 gas × 90 スロット/約 1,989,000 gas」へ向かい、その右の「取引の費用が 4 倍に/実際の消費は 516,615 gas」に繋がり、下にバツ印が置かれている。下の実線の矢印は「イベントログに出す/375 + 375×2 + 8×2,872/約 24,101 gas」へ向かい、その右の「契約からは読み返せない/外部のインデクサなら読める」に繋がっている。2 つの経路の中央に「およそ 80 倍」と大きく記されている。図の下部に、保管領域はチェーン上で最も高い書き込み先であること、ログは安い代わりにチェーン上のプログラムから参照できないこと、そのため暗号化した本体はログへ、目印だけ保管領域へという分担になることが記されている。

およそ 80 倍です(1,989,000 ÷ 24,101 = 82.5。付属ツールはこの小数のまま表示します)。

この差の意味は、実際の消費量と並べるとわかります。2 本目の取引が使ったガスは全部で 516,615 でした。もし保管領域に書いていたら、その部分だけで 1,989,000 ガスかかります。払えない額ではありませんが、1 件登録するたびに費用が 4 倍ほどに膨らむ計算です。

なお、この 2,872 バイトはどちらの方式でも取引データとして送る費用が別にかかります。両方に等しくかかるので、比較そのものには影響しません。

Chain Lens で確かめる

付属ツールは、この比較を自動で表示します。取引を読み込むと「もし保管領域に書いていたら」という項目が出ます。

書き方単価ガス
イベントログ(実際の方式)8 gas / byte24,101
保管領域22,100 gas / 32 bytes1,989,000

これは実際に起きたことではなく、「もしそうしていたら」という計算です。汎用のエクスプローラは実際の値しか出さないので、この比較はツール側で足しています。

他の取引を読み込むと、その取引で一番大きなデータについて同じ計算が出ます。データ量が変われば比も変わるので、いくつか試してみると単価の効き方が見えてきます。

安いほうには制約がある

では常にログを使えばよいかというと、そうはいきません。契約は、自分が出したログを読み返せません。

これは EVM(Ethereum Virtual Machine、チェーン上でプログラムを動かす仕組み)の制約です。ログは外向きの記録として設計されていて、チェーン上のプログラムから参照する手段が用意されていません。

つまり、ログに置いた値は次のような性質を持ちます。

  • チェーン上の他の契約からは参照できない
  • 外部のプログラム(インデクサ=チェーンのログを片端から読み、検索できる形に貯め直しておくサーバ)からは読める
  • 改竄はできない(ブロックの一部として確定している)

「プログラムが使う値」は保管領域に、「人や外部システムが読む記録」はログに、という使い分けになります。

Ocean がどう分けたか

getMetaData() で、契約の保管領域に何が入っているかを読めます。実行するとこうでした。

項目
復号を頼むサーバの URLhttp://…:8001
復号者のアドレス(空)
状態0
メタデータを持っているかtrue

本体である 2,872 バイトは、ここには入っていません。保管領域にあるのは、どこに問い合わせればよいかという目印だけです。

この設計は Ocean 自身のリポジトリにも明記されています。メタデータ索引を担う Aquarius の README に「チェーン上のメタデータは保管領域には保持されず、MetadataCreated という名前のイベントログに載る」と書かれています。費用の観点から見れば、これ以外の選択肢は取りにくかったはずです。

そして「契約はログを読み返せない」という制約が、この設計の帰結を決めています。チェーン上のプログラムは、この資産の題名もライセンスも参照できません。読めるのは外部のインデクサだけです。登録した直後はポータルの一覧に出てこないのに、しばらく待つと出てくる——データスペースのポータルでよく遭遇する時間差ですが、これはインデクサが追いつくまでの間です。

改竄されていないことは確かめられる

ログに置いたものを外部が読む、という構成には気になる点があります。復号して見せられた内容が、本当に登録されたものと同じだと、どうやって確かめるのでしょうか。

MetadataCreated には metaDataHash という項目が一緒に載っていました。

metaDataHash = 0xc717a9eb053d7b23db3da5b30a26ba4c57a19939e697fc52c10691b055d5d78b

復号した結果からハッシュを計算し、この値と突き合わせれば、途中で書き換えられていないことを確認できます。読めない状態のまま、正しさだけは検証できる、という構成です。

ただしこれが保証するのは「登録された内容と同じであること」までです。登録された内容そのものが正しいかどうかは、別の話になります。

この章で見たこと

  • 保管領域とログでは、単価が桁で違う(この例で約 80 倍)
  • 安いログには「契約から読み返せない」という制約がある
  • Ocean は本体をログに、目印を保管領域に置いている。費用から見て自然な選択
  • その結果、チェーン上のプログラムは資産の中身を参照できない
  • ハッシュを併記することで、読めないまま改竄の有無だけ検証できる

次章では、この設計の上に載っている NFT が、規格として何を決めていて何を決めていないのかを見ます。