スマートコントラクトHyperledger Fabricの開発とテスト

Hyperledger Fabric(HLF)は、分散型台帳技術(DLT)を使用するオープンソースプラットフォームであり、許可ルールを使用して組織のコンソーシアムによって作成および制御されるビジネスネットワーク環境で実行されるアプリケーションを開発するように設計されています。







プラットフォームは、HLFの観点からスマートコントラクトをサポートします。たとえば、契約指向の限定的な機能のSolidity言語を使用するイーサリアムとは異なり、Golang、JavaScript、Javaなどの一般言語で作成されたチェーンコード(LLL、Viperなど)。













ブロックチェーンネットワークの多数のコンポーネントを展開する必要があるため、チェーンコードの開発とテストは、変更のテストに時間がかかるかなり長いプロセスになる可能性があります。 この記事では、 CCKitライブラリを使用したGolangでのHLFスマートコントラクトの迅速な開発とテストへのアプローチについて説明します。







HLFベースのアプリケーション



開発者の観点から見ると、ブロックチェーンアプリケーションは2つの主要部分で構成されています。









通常、データは「ホーム」ブロックチェーンネットワークノードを介して読み取られます。 データを記録するために、アプリケーションは特定のスマートコントラクトの「承認ポリシー」に参加している組織のノードにリクエストを送信します。







オフチェーンコード(APIなど)を開発するには、ブロックチェーンノードとの相互作用をカプセル化し、応答を収集するなど、専用のSDKを使用します。 HLFには、Go( 1、2 )、 Node.Js 、およびJavaの SDK実装があります







ハイパーレジャーファブリックコンポーネント



チャンネル



チャネルは、孤立したブロックチェーン(元帳)をサポートするノードの個別のサブネットであり、スマートコントラクトを操作するために使用されるブロックチェーンの現在の状態(キー値)( world state )です。 ホストは、任意の数のチャネルにアクセスできます。







取引



Hyperledger Fabricのトランザクションは、チェーンチェーンメソッドの実行結果であるブロックチェーンの状態のアトミック更新です。 トランザクションは、呼び出し元ノードによって署名されたいくつかの引数(トランザクション提案)と、トランザクションが「確認」されたノードからの一連の応答(トランザクション提案応答)でチェーンコードメソッドを呼び出す要求で構成されます。 応答には、 Read-Write Setブロックチェーンの状態とサービス情報(トランザクションを確認するノードの署名と証明書)の変化するキーと値のペアに関する情報が含まれます。 なぜなら 個々のチャネルのブロックチェーンは物理的に分離されており、トランザクションは1つのチャネルのコンテキストでのみ実行できます。







BitcoinEthereumなどの「クラシック」ブロックチェーンプラットフォームは、すべてのノードで実行されるOrdering-Executionトランザクションサイクルを使用します。これにより、ブロックチェーンネットワークのスケーラビリティが制限されます。













Hyperledger Fabricは、3つの主な操作があるトランザクション実行および配布アーキテクチャを使用します。















このアプローチにより、ブロックチェーンネットワークに入る前にトランザクション実行フェーズを実行でき、ネットワークノードの動作を水平方向にスケーリングできます。







チェーンコード



スマートコントラクトとも呼ばれるチェーンコードは、Golang、JavaScript(HLF 1.1+)またはJava(HLF 1.3+)で記述されたプログラムで、ブロックチェーンの状態を変更するトランザクションを作成するためのルールを定義します。 プログラムは、ブロックチェーンノードの分散ネットワークの複数の独立したノードで同時に実行され、トランザクションの「確認」に必要なすべてのノードでのプログラム実行の結果を調整することにより、スマートコントラクトの実行に中立な環境を作成します。







コードは、メソッドで構成されるインターフェイスを実装する必要があります。







type Chaincode interface { // Init is called during Instantiate transaction Init(stub ChaincodeStubInterface) pb.Response // Invoke is called to update or query the ledger Invoke(stub ChaincodeStubInterface) pb.Response }
      
      







チェーンコードは、ブロックチェーンネットワークのピアにインストールされます。 システムレベルでは、コードの各インスタンスは、特定のネットワークノードに接続された個別のdocker-containerに対応し、コードの実行への呼び出しをディスパッチします。

Ethereumスマートコントラクトとは異なり、連鎖ロジックは更新できますが、これにはコードコードをホストするすべてのノードに更新バージョンがインストールされている必要があります。







SDKを介した外部からのチェーンコード関数の呼び出しに応じて、チェーンコードは、ブロックチェーンの状態( Read-Write Set )およびイベントの変更を作成します。 チェーンコードは特定のチャネルを参照し、1つのチャネルのデータのみを変更できます。 同時に、コードがインストールされているホストが他のチャネルにもアクセスできる場合、コードのロジックでは、これらのチャネルからデータを読み取ることができます。







ブロックチェーンネットワークのさまざまな側面を管理するための特別なチェーンコードは、システムチェーンコードと呼ばれます。







推奨ポリシー



承認ポリシーは、特定のチェーンコードによって生成されたトランザクションのレベルでコンセンサスルールを定義します。 このポリシーは、どのチャネルノードがトランザクションを作成するかを決定するルールを設定します。 これを行うには、承認ポリシーで指定された各ノードがチェーンコードメソッド(「実行」ステップ)を実行し、「シミュレーション」を実行します。その後、署名された結果が収集され、トランザクションを開始したSDKによって検証されます(すべてのシミュレーション結果は同一である必要があります。ポリシーに必要なすべてのノードの署名が存在する必要があります)。 次に、SDKはトランザクションをordererに送信します。その後、チャネルにアクセスできるすべてのノードは、 ordererを介してトランザクションを受信し、「検証」ステップを実行します。 すべてのチャネルノードが「実行」ステップに参加する必要があるわけではないことを強調することが重要です。







承認ポリシーは、コードのインスタンス化またはアップグレード時に決定されます。 バージョン1.3では、チェーンコードのレベルだけでなく、個々の状態ベースの承認キーのレベルでもポリシーを設定できるようになりました。 承認ポリシーの例:









イベント



イベントは、ブロックチェーンチェーンの状態の「更新フィード」を公開できる名前付きデータセットです。 イベント属性のセットは、チェーンコードを定義します。







ネットワークインフラ



ホスト(ピア)



ホストは、アクセス権を持つ任意の数のチャネルに接続されます。 ホストは、ブロックチェーンのバージョンとブロックチェーンの状態を維持し、チェーンコードを実行するための環境も提供します。 ホストが承認ポリシーに含まれていない場合、チェーンコードを設定する必要はありません。







ホストソフトウェアレベルでは、ブロックチェーンの現在の状態(ワールド状態)をLevelDBまたはCouchDBに保存できます。 CouchDBの利点は、MongoDB構文を使用した豊富なクエリのサポートです。







注文者



トランザクション管理サービスは、署名付きトランザクションを入力として受け入れ、トランザクションがネットワークノード全体に正しい順序で分散されるようにします。







注文者はスマートコントラクトを実行せず、ブロックチェーンとブロックチェーン状態を含みません。 現在(1.3)、 ordererには2つの実装があります-開発ソロと、クラッシュフォールトトレランスを提供するKafkaベースのバージョンです。 特定の割合の参加者の不正な動作(ビザンチンフォールトトレランス)に対する耐性をサポートする注文者の実装は、2018年末に予定されています。







アイデンティティサービス



Hyperledger Fabricネットワークでは、すべてのメンバーが他のメンバーに知られているID(​​ID)を持っています。 識別には、公開鍵インフラストラクチャ(PKI)が使用されます。これにより、組織、インフラストラクチャ要素(ノード、発注者)、アプリケーション、およびエンドユーザー用のX.509証明書が作成されます。 その結果、データの読み取りおよび変更へのアクセスは、ネットワークレベル、単一チャネル、またはスマートコントラクトのロジックでアクセスルールを介して制御できます。 同じブロックチェーンネットワークでは、さまざまなタイプの複数の識別サービスが同時に機能します。







チェーンコードの実装



チェーンコードは、特定のビジネスロジックを実装するメソッドを持つオブジェクトと見なすことができます。 従来のOOPとは異なり、チェーンコードに属性フィールドを含めることはできません。 ストレージがHLFブロックチェーンプラットフォームによって提供される状態を操作するには、 InitおよびInvokeメソッドが呼び出されたときに渡されるChaincodeStubInterfaceレイヤーが使用されます。 関数呼び出しの引数を受け取り、ブロックチェーンの状態を変更する機能を提供します。







 type ChaincodeStubInterface interface { // GetArgs returns the arguments intended for the chaincode Init and Invoke GetArgs() [][]byte // InvokeChaincode locally calls the specified chaincode InvokeChaincode(chaincodeName string, args [][]byte, channel string) pb.Response // GetState returns the value of the specified `key` from the ledger. GetState(key string) ([]byte, error) // PutState puts the specified `key` and `value` into the transaction's writeset as a data-write proposal. PutState(key string, value []byte) error // DelState records the specified `key` to be deleted in the writeset of the transaction proposal. DelState(key string) error // GetStateByRange returns a range iterator over a set of keys in the ledger. GetStateByRange(startKey, endKey string) (StateQueryIteratorInterface, error) // CreateCompositeKey combines the given `attributes` to form a composite key. CreateCompositeKey(objectType string, attributes []string) (string, error) // GetCreator returns `SignatureHeader.Creator` (eg an identity of the agent (or user) submitting the transaction. GetCreator() ([]byte, error) // and many more methods }
      
      





Solidityで開発されたEthereumスマートコントラクトでは、各メソッドはパブリック関数に対応しています。 ChaincodeStubInterface関数を使用して、 InitおよびInvokeメソッドのHyperledger Fabricチェーンコードで。 GetArgs()、バイト配列の配列の形式で関数呼び出しの引数を取得できますが、 Invokeを呼び出すときの配列の最初の要素にはチェーンコード関数の名前が含まれます。 なぜなら チェーンコードメソッドの呼び出しは、Invokeメソッドを通過します;これは、フロントコントローラーパターンの実装であると言えます。







たとえば、 ERC-20トークンの標準Ethereumインターフェイスの実装を検討する場合、スマートコントラクトはメソッドを実装する必要があります。









HLFを実装する場合、 Invoke関数コードは、 Invokeの最初の引数が期待されるメソッドの名前を呼び出す場合(たとえば、「totalSupply」または「balanceOf」)を処理できる必要があります。 ERC-20標準の実装の例はここで見られます







チェーンコードの例



Hyperledger Fabricのドキュメントに加えて、チェーンコードの例がいくつかあります。









これらの例のチェーンコードの実装はかなり冗長であり、呼び出されたルーティング関数を選択するための多くの繰り返しロジックを含みます)、引数の数をチェックし、jsonマーシャリング/アンマーシャリングします:







 func (t *SimpleChaincode) Invoke(stub shim.ChaincodeStubInterface) pb.Response { function, args := stub.GetFunctionAndParameters() fmt.Println("invoke is running " + function) // Handle different functions if function == "initMarble" { //create a new marble return t.initMarble(stub, args) } else if function == "transferMarble" { //change owner of a specific marble return t.transferMarble(stub, args) } else if function == "readMarble" { //read a marble return t.readMarble(stub, args) } else ...
      
      





このようなコードの編成は、コードの可読性の低下と、入力データの非整列化を忘れた場合に発生する可能性のあるエラーなどにつながります。 HLF開発計画に関するプレゼンテーションでは、チェーンコードの開発へのアプローチの改訂、特にJavaチェーンコードへの注釈の導入などについて言及していますが、計画は2019年にのみ予想されるバージョンに関連しています。 スマートコントラクトの開発経験から、別のライブラリで基本機能を選択すると、チェーンコードの開発とテストが容易になるという結論に至りました。







CCKit-チェーンコードを開発およびテストするためのライブラリ



CCKitライブラリは、チェーンコードの開発とテストの実践を要約しています。 チェーンコード拡張機能の開発の一環として、Ethereumスマートコントラクトの拡張機能 OpenZeppelinライブラリが例として使用されました。 CCKitは次のアーキテクチャソリューションを使用します。







スマートコントラクト機能への通話のルーティング



ルーティングとは、アプリケーションがクライアント要求に応答するアルゴリズムを指します。 このアプローチは、たとえば、ほぼすべてのhttpフレームワークで使用されます。 ルーターは特定のルールを使用して、要求と要求ハンドラーをバインドします。 チェーンコードの場合、これはチェーンコード関数の名前をハンドラー関数に関連付けるためです。







Insurance Appなどのスマートコントラクトの最新の例では、チェーンコード関数の名前と、フォームのGolangコードの関数の間のマッピングを使用します。







 var bcFunctions = map[string]func(shim.ChaincodeStubInterface, []string) pb.Response{ // Insurance Peer "contract_type_ls": listContractTypes, "contract_type_create": createContractType, ... "theft_claim_process": processTheftClaim, }
      
      





CCKitルーターは、httpルーターに似たアプローチと、チェーンコード機能とミドルウェア機能にリクエストコンテキストを使用する機能を使用します。







コードの関数の呼び出しのコンテキスト



通常http要求のパラメーターにアクセスするhttp要求コンテキストと同様に、CCKitルーターは、 スマートコントラクト関数の呼び出しのコンテキストを使用します。これは、 shim.ChaincodeStubInterfaceの抽象化です 。 コンテキストは、チェーン関数のハンドラーへの唯一の引数にすることができます。ハンドラーは、それを介して、関数呼び出しの引数を取得できるだけでなく、スマートコントラクト(状態)の状態を操作したり、回答(応答)を作成したりするための補助機能にアクセスできます







 Context interface { Stub() shim.ChaincodeStubInterface Client() (cid.ClientIdentity, error) Response() Response Logger() *shim.ChaincodeLogger Path() string State() State Time() (time.Time, error) Args() InterfaceMap Arg(string) interface{} ArgString(string) string ArgBytes(string) []byte SetArg(string, interface{}) Get(string) interface{} Set(string, interface{}) SetEvent(string, interface{}) error }
      
      





なぜなら コンテキストはインターフェイスであり、特定のチェーンコードでは展開できます。







ミドルウェア機能



中間処理の機能(ミドルウェア)は、コードのメソッドのハンドラーの呼び出しの前に呼び出され、コードのメソッドと次の中間関数の呼び出しのコンテキストにアクセスするか、次のメソッドのハンドラー(直接)に直接アクセスします。 ミドルウェアは次の用途に使用できます。









データ構造変換



チェーンコードインターフェイスは、バイト配列の配列が入力に提供され、各要素がチェーンコード関数の属性であることを前提としています。 チェーン関数の各ハンドラーの関数呼び出し引数からバイト配列からgolangデータ型(int、string、structure、array)への手動データマーシャリングを防ぐため、ルーティングルールが作成され、型が自動的に変換されるときに、CCKitルーターで予期されるデータ型が設定されます。 の例でcarGet関数は文字列型の引数を想定し、 carRegister関数はCarPayload構造体を想定しています。 引数にも名前が付けられます。これにより、ハンドラーは名前でコンテキストから値を取得できます。 ハンドラーの例を以下に示します。 Protobufを使用して、連鎖データスキームを記述することもできます。







 r.Group(`car`). Query(`List`, cars). // chain code method name is carList Query(`Get`, car, p.String(`id`)). // chain code method name is carGet, method has 1 string argument "id" Invoke(`Register`, carRegister, p.Struct(`car`, &CarPayload{}), // 1 struct argument owner.Only) // allow access to method only for chaincode owner (authority)
      
      





また、スマートコントラクトの状態にデータを書き込むとき、およびイベントを作成するときに自動変換(マーシャリング)が使用されます(golang型はバイトの配列にシリアル化されます)







チェーンコードのデバッグとログ記録のためのツール



コードをデバッグするには、 デバッグ拡張機能を使用できます。 デバッグ拡張機能は、スマートコントラクトの状態でキーの存在を検査し、キーごとの値を直接読み取り/変更/削除できるスマートコントラクトメソッドを実装します。







チェーンコード関数の呼び出しのコンテキストでログを記録するには、Log()メソッドを使用できます。これは、HLFで使用されるロガーのインスタンスを返します。







スマートコントラクトメソッドアクセス制御メソッド



所有者拡張の一部として、インスタンス化されたチェーンコードの所有者に関する情報を格納するための基本的なプリミティブと、スマートコントラクトのメソッドへのアクセス修飾子(ミドルウェア)が実装されます。







スマートコントラクトテストツール



ブロックチェーンネットワークの展開、チェーンコードのインストールと初期化は、かなり複雑なセットアップと長い手順です。 スマートコントラクトのDEVモードを使用すると、スマートコントラクトコードを再インストール/アップグレードする時間を短縮できますが、コードの更新プロセスは依然として遅くなります。







shimパッケージには、チェーンコードのコードへの呼び出しをラップするMockStubの実装が含まれ、HLFブロックチェーン環境での動作をシミュレートします。 MockStubを使用すると、テスト結果をほぼ瞬時に取得でき、開発時間を短縮できます。 HLFでのコード操作の一般的なスキームを考慮すると、MockStubは本質的にSDKを置き換え、コードの機能を呼び出すことができ、ホスト上でコードを開始するための環境を模倣します。













HLF配信のMockStubには、 shim.ChaincodeStubInterfaceインターフェイスのほぼすべてのメソッドの実装が含まれていますが、現在のバージョン(1.3)では、GetCreatorなどの重要なメソッドの実装が欠けています。 なぜなら チェーンコードはこのメソッドを使用して、アクセス制御用のトランザクション作成者の証明書を取得できます。テストで最大限のカバレッジを得るには、このメソッドのスタブを持つ能力が重要です。







CCKitライブラリには、MockStubの拡張バージョンが含まれています。これには、欠落しているメソッドの実装や、イベントチャネルなどを操作するためのメソッドが含まれています。







チェーンコードの例



たとえば、登録済みの車に関する情報を保存するための簡単なチェーンコードを作成します







データモデル



コードコードの状態はキーと値のストレージです。キーは文字列で、値はバイトの配列です。 基本的な方法は、データ構造のgonalized golangインスタンスを値として保存することです。 したがって、チェーンコード内のデータを操作するには、状態から読み取った後、バイト配列を非整列化する必要があります。







車について記録するために、次の属性セットを使用します。









 // Car struct for chaincode state type Car struct { Id string Title string Owner string UpdatedAt time.Time // set by chaincode method }
      
      





データをチェーンコードに転送するには、チェーンコードの外部からのフィールドのみを含む別の構造を作成します。







 // CarPayload chaincode method argument type CarPayload struct { Id string Title string Owner string }
      
      





キーを操作する



スマートコントラクト状態のレコードキーは文字列です。 また、キーの一部がゼロバイト( U + 0000 )で区切られている複合キーを作成する機能もサポートしています







 func CreateCompositeKey(objectType string, attributes []string) (string, error)
      
      





CCKitでは 、転送された構造がキーヤーインターフェイスをサポートしている場合スマートコントラクトステータス関数がレコードのキーを自動的に作成できます







 // Keyer interface for entity containing logic of its key creation type Keyer interface { Key() ([]string, error) }
      
      





車を記録するためのキー生成機能は次のとおりです。







 const CarEntity = `CAR` // Key for car entry in chaincode state func (c Car) Key() ([]string, error) { return []string{CarEntity, c.Id}, nil }
      
      





スマートコントラクト機能の宣言(ルーティング)



チェーンコードのコンストラクターメソッドでは、チェーンコードの関数とその引数を定義できます。 車の登録コードには3つの機能があります









 func New() *router.Chaincode { r := router.New(`cars`) // also initialized logger with "cars" prefix r.Init(invokeInit) r.Group(`car`). Query(`List`, queryCars). // chain code method name is carList Query(`Get`, queryCar, p.String(`id`)). // chain code method name is carGet, method has 1 string argument "id" Invoke(`Register`, invokeCarRegister, p.Struct(`car`, &CarPayload{}), // 1 struct argument owner.Only) // allow access to method only for chaincode owner (authority) return router.NewChaincode(r) }
      
      





上記の例では、 InitメソッドとInvokeメソッドの処理がルーターに委任されるチェーンコード構造を使用しています。







 package router import ( "github.com/hyperledger/fabric/core/chaincode/shim" "github.com/hyperledger/fabric/protos/peer" ) // Chaincode default chaincode implementation with router type Chaincode struct { router *Group } // NewChaincode new default chaincode implementation func NewChaincode(r *Group) *Chaincode { return &Chaincode{r} } //======== Base methods ==================================== // // Init initializes chain code - sets chaincode "owner" func (cc *Chaincode) Init(stub shim.ChaincodeStubInterface) peer.Response { // delegate handling to router return cc.router.HandleInit(stub) } // Invoke - entry point for chain code invocations func (cc *Chaincode) Invoke(stub shim.ChaincodeStubInterface) peer.Response { // delegate handling to router return cc.router.Handle(stub) }
      
      





ルーターと基本的なチェーンコード構造を使用すると、ハンドラー関数を再利用できます。 たとえば、 carRegister



関数へのアクセスをチェックせずにチェーンコードを実装するには、新しいコンストラクターメソッドを作成するだけで十分です。







スマートコントラクトの機能の実装



Golang関数-CCKitルーターのスマートコントラクト関数ハンドラーには、 の3つのタイプがあります。









 // StubHandlerFunc acts as raw chaincode invoke method, accepts stub and returns peer.Response StubHandlerFunc func(shim.ChaincodeStubInterface) peer.Response // ContextHandlerFunc use stub context as input parameter ContextHandlerFunc func(Context) peer.Response // HandlerFunc returns result as interface and error, this is converted to peer.Response via response.Create HandlerFunc func(Context) (interface{}, error)
      
      





, , ( CarPayload)

State , ( )







 // car get info chaincode method handler func car(c router.Context) (interface{}, error) { return c.State().Get( // get state entry Key(c.ArgString(`id`)), // by composite key using CarKeyPrefix and car.Id &Car{}) // and unmarshal from []byte to Car struct } // cars car list chaincode method handler func cars(c router.Context) (interface{}, error) { return c.State().List( CarKeyPrefix, // get list of state entries of type CarKeyPrefix &Car{}) // unmarshal from []byte and append to []Car slice } // carRegister car register chaincode method handler func carRegister(c router.Context) (interface{}, error) { // arg name defined in router method definition p := c.Arg(`car`).(CarPayload) t, _ := c.Time() // tx time car := &Car{ // data for chaincode state Id: p.Id, Title: p.Title, Owner: p.Owner, UpdatedAt: t, } return car, // peer.Response payload will be json serialized car data c.State().Insert( //put json serialized data to state Key(car.Id), // create composite key using CarKeyPrefix and car.Id car) }
      
      





-



- — , . BDD – Behavior Driven Development, .







, , - Ethereum ganache-cli truffle . golang - Mockstub.









, . .







Ginkgo , Go, go test



. gomega expect , , .







  import ( "testing" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" examplecert "github.com/s7techlab/cckit/examples/cert" "github.com/s7techlab/cckit/extensions/owner" "github.com/s7techlab/cckit/identity" "github.com/s7techlab/cckit/state" testcc "github.com/s7techlab/cckit/testing" expectcc "github.com/s7techlab/cckit/testing/expect" ) func TestCars(t *testing.T) { RegisterFailHandler(Fail) RunSpecs(t, "Cars Suite") }
      
      





, CarPayload :







 var Payloads = []*Car{{ Id: `A777MP77`, Title: `VAZ`, Owner: `victor`, }, { Id: `O888OO77`, Title: `YOMOBIL`, Owner: `alexander`, }, { Id: `O222OO177`, Title: `Lambo`, Owner: `hodl`, }}
      
      





MockStub Cars.







 //Create chaincode mock cc := testcc.NewMockStub(`cars`, New())
      
      





なぜなら cars , .







 // load actor certificates actors, err := identity.ActorsFromPemFile(`SOME_MSP`, map[string]string{ `authority`: `s7techlab.pem`, `someone`: `victor-nosov.pem`}, examplecert.Content)
      
      





BeforeSuite Car authority Init . , Cars Init Init , .







 BeforeSuite(func() { // init chaincode expectcc.ResponseOk(cc.From(actors[`authority`]).Init()) // init chaincode from authority })
      
      





. , CarRegister , .







 It("Allow authority to add information about car", func() { //invoke chaincode method from authority actor expectcc.ResponseOk(cc.From(actors[`authority`]).Invoke(`carRegister`, Payloads[0])) }) It("Disallow non authority to add information about car", func() { //invoke chaincode method from non authority actor expectcc.ResponseError( cc.From(actors[`someone`]).Invoke(`carRegister`, Payloads[0]), owner.ErrOwnerOnly) // expect "only owner" error })
      
      





:







 It("Disallow authority to add duplicate information about car", func() { expectcc.ResponseError( cc.From(actors[`authority`]).Invoke(`carRegister`, Payloads[0]), state.ErrKeyAlreadyExists) //expect car id already exists })
      
      





おわりに



- HLF Go, Java, JavaScript, , , - (Solidity) / -. / .







HLFのチェーンコードのアーキテクチャは積極的に完成されており、以前は明らかに十分ではなかった機能があります(レコードのリストのページごとのクエリなど)。Hypeledger Fabricの貢献者は、興味のある開発者がプロ​​ジェクトの開発に参加するよう積極的に奨励しています。開発の分野は十分に広い。








All Articles