solana documentation-Design Proposals

Implemented

Solana ABI management process(ソラナABIマネジメントプロセス)

This document proposes the Solana ABI management process. The ABI management process is an engineering practice and a supporting technical framework to avoid introducing unintended incompatible ABI changes.
このドキュメントでは、ソラナABIマネジメントプロセスを提案します。ABIマネジメントプロセスは、意図しない互換性のないABI変更の導入を避けるための光学的実践とそれを支える技術的枠組みです。

Problem(問題点)

The Solana ABI (binary interface to the cluster) is currently only defined implicitly by the implementation and requires a very careful eye to notice breaking changes. This makes it extremely difficult to upgrade the software on an existing cluster without rebooting the ledger.
ソラナABI(クラスタへのバイナリインターフェース)は、現在のところ実装によって暗黙的にしか定義されておらず、破壊的変更に気づくためには非常に慎重な目が必要となります。このため、台帳を再起動せずに既存のクラスタ上でソフトウェアをアップグレードすることは非常に困難です。

Requirements and objectives(要求と目的)

・Unintended ABI changes can be detected as CI failures mechanically.
・意図しないABIの変更を機械的にCIの失敗として検出できること。

・Newer implementation must be able to process the oldest data (since genesis) once we go mainnet.
・新しい実装では、メインネット化した後の古いデータを処理できるようにしなければなりません。

・The objective of this proposal is to protect the ABI while sustaining rather rapid development by opting for a mechanical process rather than a very long human-driven auditing process.
・本提案の目的は、非常に長い人間手動の監査プロセスではなく、機械的なプロセスを選択することで、ABIを保護しつつ、むしろ急速な開発をすることにあります。

・Once signed cryptographically, data blob must be identical, so no in-place data format update is possible regardless of inbound and outbound of the online system. Also, considering the sheer volume of transactions we're aiming to handle, retrospective in-place update is undesirable at best.
・暗号署名されたデータブロブは同一でなければならないため、オンラインシステムのインバウンド、アウトバウンドに関わらず、インプレースでのデータ形式の更新は出来ません。また、扱うことを目的としたトランザクションの量を考えると、遡及的なインプレース更新はあまり望ましくありません。

Solution(解決策)

Instead of natural human's eye due-diligence, which should be assumed to fail regularly, we need a systematic assurance of not breaking the cluster when changing the source code.
定期的に失敗することを前提とした人間の自然的な目視によるデューデリジェンスではなく、ソースコードを変更する際にクラスタを壊さないことを体系的に保証する必要があります。

For that purpose, we introduce a mechanism of marking every ABI-related things in source code (structs, enums) with the new #[frozen_abi] attribute. This takes hard-coded digest value derived from types of its fields via ser::Serialize. And the attribute automatically generates a unit test to try to detect any unsanctioned changes to the marked ABI-related things.
そのために、ソースコード中のABI関連のもの(構造体や列挙型)に"#[frozen_abi]"属性を付与する仕組みを導入しました。これは、"ser::Serialize"を介して、フィールドの型から派生したハードコードされたダイジェスト値を受け取ります。そして、この属性は自動的にユニットテストを生成して、マークされたABI関連のものに対して認可されていない変更を検出しようとします。

However, the detection cannot be complete; no matter how hard we statically analyze the source code, it's still possible to break ABI. For example, this includes not-derived hand-written ser::Serialize, underlying library's implementation changes (for example bincode), CPU architecture differences. The detection of these possible ABI incompatibilities is out-of-scope for this ABI management.
しかし、検出を完全に行うことは出来ません。いくらソースコードを統計的に分析しても、ABIを壊す可能性があります。例えば、派生していない手書きの"ser::Serialize"、基礎となるライブラリの変更(例えばbincode)、CPUアーキテクチャの違いなどがこれに該当します。これらの可能性のあるABIの非互換性の検出は、このABI管理の範囲外です。

Definitions(定義)

ABI item/type: various types to be used for serialization, which collectively comprises the whole ABI for any system components. For example, those types include structs and enums.
ABI item/type:シリアライズに使用される様々な型で、システムコンポーネントのABI全体をまとめて構成します。例えば、これらの型には構造体や列挙型が含まれます。

ABI item digest: Some fixed hash derived from type information of ABI item's fields.
ABI item digest:ABI項目のフィールドの型情報から派生した固定ハッシュ。

Example(例)


+#[frozen_abi(digest="eXSMM7b89VY72V...")]
#[derive(Serialize, Default, Deserialize, Debug, PartialEq, Eq, Clone)]
pub struct Vote {
    /// A stack of votes starting with the oldest vote
    pub slots: Vec<Slot>,
    /// signature of the bank's state at the last slot
    pub hash: Hash,
}

Developer's workflow(開発者のワークフロー)

To know the digest for new ABI items, developers can add frozen_abi with a random digest value and run the unit tests and replace it with the correct digest from the assertion test error message.
新しいABI項目のダイジェストを知るために、開発者は"frozen_abi"にランダムなダイジェスト値を追加してユニットテストを実行し、アサーションテストのエラーメッセージから正しいダイジェストに置き換えることが出来ます。

In general, once we add frozen_abi and its change is published in the stable release channel, its digest should never change. If such a change is needed, we should opt for defining a new struct like FooV1. And special release flow like hard forks should be approached.
一般的に一度"frozen_abi"を追加して、その変更が安定版リリースチャンネルで公開されると、そのダイジェストは決して変更されるべきではありません。そのような変更が必要な場合は、"FooV1"のような新しい構造体を定義することを選ぶべきです。また、ハードフォークのような特殊なリリースフローにもアプローチすることが望ましいです。

Implementation remarks(実装上の注意事項)

We use some degree of macro machinery to automatically generate unit tests and calculate a digest from ABI items. This is doable by clever use of serde::Serialize ([1]) and any::type_name ([2]). For a precedent for similar implementation, ink from the Parity Technologies [3] could be informational.
ある程度のマクロを使ってユニットテストを自動的に生成し、ABI項目からダイジェストを計算します。これは、"serde::Serialize([1])"と"any::type_name([2])"の巧みな使用によって可能になります。同様な実装の全例としては、"Parity Technologies[3]"の"ink"が参考になります。

Implementation details(実施内容)

The implementation's goal is to detect unintended ABI changes automatically as much as possible. To that end, the digest of structural ABI information is calculated with best-effort accuracy and stability.
この実装の目標は、意図しないABIの変更を、可能な限り自動的に検出することです。そのために、構造的なABI情報のダイジェストは、ベストエフォートな精度と安定性で計算されます。

When the ABI digest check is run, it dynamically computes an ABI digest by recursively digesting the ABI of fields of the ABI item, by re-using the serde's serialization functionality, proc macro and generic specialization. And then, the check assert!s that its finalized digest value is identical as what is specified in the frozen_abi attribute.
ABIダイジェストチェックが実行されると、"serde"のシリアライズ機能、"proc"マクロ、一般的な特殊化を再利用して、ABI項目におけるフィールドのABIを再帰的にダイジェストすることで、ABIダイジェストを動的に計算します。そして、その最終的なダイジェスト値が"frozen_abi"属性で指定されたものと同一であることを確認します。

To realize that, it creates an example instance of the type and a custom Serializer instance for serde to recursively traverse its fields as if serializing the example for real. This traversing must be done via serde to really capture what kinds of data actually would be serialized by serde, even considering custom non-derived Serialize trait implementations.
これを実現するために、型のサンプルインスタンスと、"serde"用のカスタムシリアライズ化インスタンスを作成して、あたかもサンプルを実施にシリアラウズするかのように、そのフィールドを再帰的にトラバースします。このトラバースは、"serde"が実際にどのようなデータをシリアライズするのかを実際に把握するために、"serde"を介して行われなければなりません。

The ABI digesting process(ABIのダイジェストプロセス)

This part is a bit complex. There is three inter-depending parts: AbiExample, AbiDigester and AbiEnumVisitor.
この部分はちょっと複雑です。相互に依存する部分が3つあります。"AbiExample"、"AbiDigester"および、"AbiEnumVisitor"です。

First, the generated test creates an example instance of the digested type with a trait called AbiExample, which should be implemented for all of digested types like the Serialize and return Self like the Default trait. Usually, it's provided via generic trait specialization for most of common types. Also it is possible to derive for struct and enum and can be hand-written if needed.
最初に生成されたテストは、シリアライズ化のように全てのダイジェストされた型に実装され、デフォルト特性のように"Self"を返す必要がある、"AbiExample"と呼ばれる特性を持った例を生成します。通常、ほとんどの一般的な軽視の特殊化を介して提供されます。また、"struct"や"enum"に対しても導出が可能で、必要に応じて手書きで書くことも可能です。

The custom Serializer is called AbiDigester. And when it's called by serde to serialize some data, it recursively collects ABI information as much as possible. AbiDigester's internal state for the ABI digest is updated differentially depending on the type of data. This logic is specifically redirected via with a trait called AbiEnumVisitor for each enum type. As the name suggests, there is no need to implement AbiEnumVisitor for other types.
カスタムシリアライザは"AbiDigester"と呼ばれています。このカスタムシリアライザは、"AbiDigester"と呼ばれ、"serde"によって呼びだれて、データをシリアライズする際に、可能な限りABIの情報を再帰的に収集します。ABIダイジェストのための"AbiDigester"の内部状態は、データの種類に応じて異なるように更新されます。このロジックは、各列挙型のために、"AbiEnumVisitor"と呼ばれる形質を介して具体的にリダイレクトされています。その名が示すように、他の型のために"AbiEnumVisitor"を実装する必要はありません。

To summarize this interplay, serde handles the recursive serialization control flow in tandem with AbiDigester. The initial entry point in tests and child AbiDigesters use AbiExample recursively to create an example object hierarchal graph. And AbiDigester uses AbiEnumVisitor to inquiry the actual ABI information using the constructed sample.
この相互作用を要約すると、"serde"は"AbiDigester"と連動して、再帰的なシリアライズ制御フローを処理します。テストの最初のエントリポイントと子の"AbiDigesters"は"AbiExample"を再帰的に使用して、サンプルオブジェクトの階層グラフを作成します。そして、"AbiDigester"は、構築されたサンプルを使って実際のABIインフォメーションを照会するために、"AbiEnumVisitor"を使用します。

Default isn't enough for AbiExample. Various collection's ::default() is empty, yet, we want to digest them with actual items. And, ABI digesting can't be realized only with AbiEnumVisitor. AbiExample is required because an actual instance of type is needed to actually traverse the data via serde.
"Abi Example"はデフォルトだけでは不十分です。様々なコレクションの"::default()"は空ですが、実際の項目でダイジェストをしたいのです。また、"AbiEnumVisitor"だけでは、ABIをダイジェストすることは出来ません。実際に、"serde"を経由して、データをたどるには、実際の型のインスタンスが必要になるため、"AbiExample"が必要になります。

On the other hand, ABI digesting can't be done only with AbiExample, either. AbiEnumVisitor is required because all variants of an enum cannot be traversed just with a single variant of it as a ABI example.
一方、ABIダイジェストも"AbiExample"だけではダイジェスト出来ません。"AbiEnumVisitor"が必要なのは"ABI example"としての単一の変種だけでは、列挙型の全ての編集をトラバースすることができないからです。

Digestable information:(ダイジェスト情報)

・rust's type name
・serde's data type name
・all fields in struct
・all variants in enum
・struct: normal(struct {...}) and tuple-style (struct(...))
・enum: normal variants and struct- and tuple- styles.
・attributes: serde(serialize_with=...) and serde(skip)

Not digestable information:(ダイジェスト出来ない情報)

・Any custom serialize code path not touched by the sample provided by AbiExample. (technically not possible)
・generics (must be a concrete type; use frozen_abi on concrete type aliases)

References

1.(De)Serialization with type info · Issue #1095 · serde-rs/serde
2.std::any::type_name - Rust
3.Parity's ink to write smart contracts


この記事が気に入ったらサポートをしてみませんか?