分散ストレージプール
分散ストレージは、トランザクションに入りきらない大きなファイルを保持します。チェーンが記録するのは各ファイルのマニフェストとデポジットだけです。ピースは登録済みノード上に、それぞれ 5 つのコピーとして置かれ、その配置はすべてのノードが再現できる抽選で決まります。ファイルのレントは、まだそのバイト列を持っていることをエポックごとに証明する保持者に支払われます。
ピース、ルート、マニフェスト
保存されるファイルは 1 MiB(1,048,576 バイト)のピースに分割されます。各ピースはその SHA3-256 ハッシュで名前が付けられ、ファイルの身元であるルートは、ピースのサイズ、全体のサイズ、ピース ID の一覧に対するハッシュです。
piece id = SHA3-256(piece)
root = SHA3-256(piece size (u32) ‖ total size (u64) ‖ piece ids)
ピースは丸ごとコピーされます。ZooBC はイレイジャーコーディングを使いません。StoreFile トランザクション(タイプ 40)がマニフェストをチェーンに固定します。ルート、全体のサイズ、ピースのサイズ、少なくとも 0.01 ZBC のデポジット、1 から 100,000 のピース ID、そして任意のアクセスブロックです。ピースそのものが決してチェーンに載ることはありません。すでに有効なルートに対する StoreFile は有料の空操作です。プルーニングされたルートは再び保存できます。
アップロードするには、ウォレットがファイルをノードの POST /api/v1/storage/upload に送り、ノードは StoreFile のためのルート、サイズ、ピース ID を返します。ノードは、ストレージがオフの場合(503)、本体が空の場合(400)、空き容量が 2 GiB 未満か、まだ固定されていないピースが 256 MiB を超えている場合(507)、そしてクライアントが 600 秒あたり 10 回または 1 時間あたり 32 MiB のアップロードを超えた場合(429)に拒否します。
誰がピースを保持するか
配置にはランデブーハッシュを使います。mainnet では、時間は 240 ブロック、約 1 時間のストレージエポックに区切られます。あるエポックのあるピースについて、すべてのアクティブな登録済みノードが重みを得ます。
weight = SHA3-256(piece id ‖ node public key ‖ epoch seed)
エポックシードは、そのエポックを開くブロック、つまり高さ epoch × 240 のブロックのハッシュです。重みの最も高い 5 つのノードが想定される保持者で、5 はチェーンのパラメータ storage_replication です。ノードが 5 つ未満なら、そのすべてがピースを保持します。式の中のものはすべてチェーンのデータなので、どのノードも告知なしに同じ 5 つを計算します。保持者に支払う集計は直前のブロック時点のレジストリを読むため、2 つのノードがその集合について食い違うことはありません。
保持者はエポックごとに変わり、引き継ぎは重なり合って行われます。各エポックで、ノードは自分に割り当てられたピースを、まず現在の保持者から、次に以前の保持者から、それからどのノードからでも取得します。送られてきたピースを受け入れるのは、ハッシュが一致し、かつ自分がこのエポックか前のエポックの想定される保持者である場合だけです。割り当てられていないピースを削除するのは、前のエポックでも保持者でなく、かつファイルが少なくとも 1 時間経っている場合だけです。
ストレージは既定で有効です。ピースを保持することは、アーカイブのチャンクを保持することと同じく、登録済みノードの席の義務です。運用者は node.conf で無効にでき([storage] enabled = false)、その場合に失うのはストレージ報酬だけです。この切り替えはチェーンからは見えないローカルな設定なので、抽選はそれを参照しません。無効にしたノードも他のノードと同じように抽選されますが、一度も証明せず、そのピースについて何も得られず、スコアも失いません。そしてそのピースの稼働中のコピーが 1 つ減ります。複製数とエポックの長さは、node.conf の内容にかかわらず、常にチェーンのパラメータから取られます。
ストレージ証明
チェーンはピースを見ることがないため、自分だけでは保持者を確認できません。代わりにエポックごとに、各保持者が、バイト列を持っている者にしか計算できないチャレンジに答えます。
challenge = SHA3-256(epoch seed ‖ piece id ‖ epoch (u64 LE))
proof = SHA3-256(piece bytes ‖ challenge)
エポックが始まる前にチャレンジを知ることは誰にもできません。保持者は SubmitStorageProof トランザクション(タイプ 41)で答えを送ります。これは登録済みノードの鍵で署名された手数料のかからない義務で、1 つのトランザクションがそのエポックのノードのバッチ全体を運びます。
各エポックの境界で、チェーンは答えを比較します。最も多くの異なるノードが提出した値が真実とみなされますが、それと同数の値が他にない場合に限られ、少なくとも 3 つの保持者が一致した場合にだけ数えられます(oracle_quorum)。その後、一致した各保持者のオーナーにストレージプールから支払いが行われます。
reward = the active files' rent for one epoch ÷ the expected proofs (pieces × 5)
paid = min(reward, storage pool balance)
投票で否決された保持者はそのエポックについて何も稼げませんが、参加スコア は失いません。チェーンはバイト列を持たないため、判断できるのは保持者の間の一致だけだからです。何も送らない保持者は監査テーブルに記録されます。まだ成熟していないノードは何も稼げず、そのストレージ報酬はプールに残ります(成熟と収入)。
レント
期間(mainnet では 172,800 ブロック、約 30 日)ごとに 1 回、チェーンは 1 期間分のレントをファイルのデポジットからストレージプール(ZBCSTORE)に移します。
rent = max(1, ceil(size ÷ 1,048,576)) × price per MiB
価格はデータセットと同じで、開始時は 1 MiB あたり 0.05 ZBC です。レントは整数のメビバイト単位で、少なくとも 1 として課金されます。42 バイトのファイルは 1 期間あたり 0.05 ZBC を支払い、1 MiB を 1 バイト超える 1,048,577 バイトのファイルは 0.1 ZBC を支払います。ノードの GET /api/v1/storage/quote?bytes=&blocks= は、同じ式でデポジットの価格を求めます。
どのアカウントも、FundStoredFile(タイプ 296)で有効なファイルのデポジットにお金を加えることができます。1 回あたり少なくとも 0.01 ZBC です。デポジットで 1 期間分を丸ごとまかなえなくなると、ファイルはプルーニングされます。そのレコードは保存されていないものとして記録され、残りはロンジェビティプールに入ります。
非公開のファイル
StoreFile のアクセスブロックで、ファイルを非公開にし(フラグのビット 0)、1 から 16 のアドレスの許可リストを加える(フラグのビット 1)ことができます。その場合、ノードの API は、内容に対する署名のない要求を拒否し(401)、オーナーまたはリストに載っている者以外からの署名付きの要求も拒否します(403)。非公開のファイルは公開の一覧から除かれ、有効な非公開ファイルとピースを共有する公開ファイルも同じように守られます。
「非公開」はアクセスのモードであって、暗号化ではありません。ピースは暗号化されないまま保持者にコピーされ、ノードは互いにピースを提供し合うため、保持するノードの運用者は自分のディスク上のバイト列を読むことができます。内容が重要なら、アップロードする前に暗号化してください。そうすれば内容はあなたの鍵で守られ、非公開フラグは 2 つ目の、より軽い層を加えます。