Read-Write set semantics

このドキュメントでは、読み書きセットのセマンティクスに関する現在の実装の詳細について説明します。

Transaction simulation and read-write set

endorser によるトランザクションのシミュレーション中に、トランザクションの読み書きセットが準備されます。 read set には、ユニークなキーのリストと、トランザクションがシミュレーション中に読み取るコミットされたバージョン番号(値ではない)が含まれます。 write set には、ユニークなキー(読み込みセットに存在するキーと重複する場合もあります)のリストと、トランザクションが書き込む新しい値が含まれます。 トランザクションによって実行される更新がキーを削除する場合、キーの削除マーカーが(新しい値の場所に)設定されます。

さらに、トランザクションが1つのキーに対して値を複数回書き込む場合、最後に書き込まれた値のみが保持されます。 また、トランザクションがキーの値を更新した後に、そのトランザクションの中で同じキーの値を読み込むと、ワールドステートにコミットされている値が返されます。 つまり、Read-your-writes(自分が書いた新しい値を自分で読める)セマンティクスはサポートされていません。

前述したように、キーのバージョンは読み込みセットにのみ記録されます。 書き込みセットには、ユニークなキーのリストと、トランザクションによって設定された最新の値だけが含まれます。

さまざまなスキームで、バージョンを実装することができます。 バージョニングスキームの最小要件は、特定のキーに対して繰り返しのない識別子を生成することです。 例えば、バージョンに対して単調増加する数を使用することは、そのようなスキームの1つです。 現在の実装では、ブロックチェーンの高さによるバージョニングスキームを使用しています。 つまり、コミットするトランザクションの高さが、そのトランザクションによって変更されたすべてのキーの最新バージョンとして使用されます。 このスキームでは、トランザクションの高さはタプルで表されます(txNumberはブロック内のトランザクションの高さです)。 このスキームには、単調増加スキームよりも多くの利点があります。 主に、statedbのような他のコンポーネントが、トランザクションのシミュレーションや検証を効率的に行う設計を選択することを可能にします。

以下は、仮想的なトランザクションのシミュレーションによって作成された読み書きセットの例を示す図です。 説明を簡単にするために、単調増加番号でバージョンを表示しています。

<TxReadWriteSet>
  <NsReadWriteSet name="chaincode1">
    <read-set>
      <read key="K1", version="1">
      <read key="K2", version="1">
    </read-set>
    <write-set>
      <write key="K1", value="V1">
      <write key="K3", value="V2">
      <write key="K4", isDelete="true">
    </write-set>
  </NsReadWriteSet>
<TxReadWriteSet>

さらに、トランザクションがシミュレーション中に範囲クエリを実行した場合、 範囲クエリとその結果が読み書きセットに query-info として追加されます。

Transaction validation and updating world state using read-write set

committer は、トランザクションの有効性をチェックするために読み書きセットの読み込みセット部分を使用し、 影響を受けたキーのバージョンおよび値を更新するために読み書きセットの書き込みセット部分を使用します。

検証フェーズでは、(シミュレーション時から)トランザクションの読み込みセットに存在する各キーのバージョンが同じキーの現在のバージョンと一致する場合、 トランザクションは valid と見なされます。 この時、トランザクションのシミュレート後に新しいブロックによりステートにコミットされた valid なトランザクションと、 同じブロック内で先に有効と判断されたトランザクションを考慮します。 読み書きセットに1つ以上のquery-infoが含まれている場合、追加の検証が実行されます。

この追加の検証では、query-infoで取得された結果の範囲(つまり、範囲の結合)でキーが挿入/削除/更新されていないことを確認する必要があります。 つまり、検証フェーズで(シミュレーション時にトランザクションが実行した)範囲クエリを再実行したら、 シミュレーション時にトランザクションで取得されたものと同じ結果を生成する必要があります。 このチェックにより、コミット中にトランザクションがファントムアイテムを見つけた場合、トランザクションは無効とマークされます。 このファントムプロテクションは、範囲クエリ(つまり、チェーンコードの GetStateByRange 関数)に限定され、 他のクエリ(つまり、チェーンコードの GetQueryResult 関数)にはまだ実装されていないことに注意してください。 その他のクエリはファントムのリスクがあるため、アプリケーションがシミュレーションと検証/コミットの間の結果セットの安定性を保証できない限り、 オーダリングノードに送信されない読み込み専用のトランザクションでのみ使用する必要があります。

トランザクションが有効性チェックに合格すると、コミッタは書き込みセットを使用してワールドステートを更新します。 更新フェーズでは、書き込みセットに存在する各キーについて、同じキーのワールドステートの値が書き込みセットで指定された値に設定されます。 さらに、ワールドステートのキーのバージョンは、最新のバージョンを反映するように変更されます。

Example simulation and validation

このセクションでは、サンプルシナリオを通してセマンティクスの理解を促します。 この例では、ワールドステートに存在するキー k は、タプル (k,ver,val) で表されます。 ver は、キー k の最新バージョンを表し、 val を値として持っています。

次に、 T1, T2, T3, T4, T5 の5つのトランザクションのセットを考えます。 これらはすべて、ワールドステートの同じスナップショット上でシミュレートされます。 次のスニペットは、トランザクションがシミュレートされるワールドステートのスナップショットと、 これらの各トランザクションによって実行される読み書きのアクティビティのシーケンスを示します。

World state: (k1,1,v1), (k2,1,v2), (k3,1,v3), (k4,1,v4), (k5,1,v5)
T1 -> Write(k1, v1'), Write(k2, v2')
T2 -> Read(k1), Write(k3, v3')
T3 -> Write(k2, v2'')
T4 -> Write(k2, v2'''), read(k2)
T5 -> Write(k6, v6'), read(k5)

ここで、これらのトランザクションが、T1,..,T5の順(単一のブロックまたは異なるブロックに含めることができる)に順序付けられていると仮定します。

  1. T1 は読み込みを行わないので検証に合格します。 さらに、ワールドステート上のキー k1k2 のタプルは (k1,2,v1'), (k2,2,v2') に更新されます。
  2. T2 は、前のトランザクション T1 によって変更されたキー k1 を読み込むので、検証に失敗します。
  3. T3 は読み込みを行わないので検証に合格します。 さらに、ワールドステート上で、キー k2 のタプルが (k2,3,v2'') に更新されます。
  4. T4 は、前のトランザクション T1 によって変更されたキー k2 を読み込むので、検証に失敗します。
  5. T5 は、キー k5 が前のトランザクションによって変更されないので、検証に合格します。

Note: 複数の読み込みセットを持つトランザクションは、まだサポートされていません。