Logo
    Logo

    Search

    R3-Solana連携

    Blockchainトレンド

    Corda活用事例

    Corda技術

    おすすめ記事

    記事を探す

    その他

    お客様サポート

    SBI R3 Japan HP

    お問い合わせ

    CordaにおけるReference Stateの活用方法(コード解説編)

    公開日
    Jun 21, 2022
    カテゴリ
    Corda技術を知る
    タグ
    🧑‍💻CorDapp開発👁️‍🗨️Notary
    筆者
    井本
    image
    icon
    この記事で学べること

    公式ドキュメントに記載されていないReference Stateの使い方や制約について学ぶことができます。

    icon
    目次
    • まえがき
    • 自己紹介
    • Ref.Stateについて
    • 調査内容
    • 調査結果について
    • サンプルプログラムについて
    • ファイルの説明
    • 新規作成ファイル
    • 既存のファイルの変更
    • CDL
    • AddressStateに関するCDL
    • IOUIssueに関するCDL
    • コード解説
    • AddressState.java
    • AddressContract.java(Publishに関する制約)
    • AddressContract.java(Moveに関する制約)
    • PublishFlow.java
    • MoveFlow.java
    • IOUContract.java
    • IOUIssueFlow.java(要望1,2)
    • IOUIssueFlow.java(要望3)
    • サンプルプログラムのURL
    • 終わりに

    まえがき

    今回、Reference State(以下Ref.State)に関する細かな動作確認を行うため調査を行いました。

    調査にあたりましてサンプルプログラムを作成しました。コード解説編と デモンストレーション編の2部編成でお届けしたいと思います。本記事はRef.Stateについて詳しく知りたいCordaに精通していらっしゃるエンジニアの方やセールスの方を対象としております。 Cordaについて詳しく知りたい方は R3公式ドキュメント や 株式会社digglueさんの記事 をご覧ください。また、Cordaの概念であるUTXOモデルについて知りたい方は、こちらの解説記事 をご覧ください。

    自己紹介

    SBI R3 Japan株式会社にインターンでお邪魔している井本翔太と申します。現在、都内の大学に通いながらエンジニアとして働かせていただいています。昨年からブロックチェーンに興味を持ち始め、その仕組みや活用方法を学ぶ過程でCordaと出会いました。私にとってCordaは社会にブロックチェーンという存在を浸透させる上で大きな役割を果たすと考えております。

    Ref.Stateについて

    Ref.Stateとは参照情報を表すStateの事で、使用時はトランザクションに含まれます。

    Ref.Stateは、含まれるトランザクションで消費されることはありませんが、Ref.State自体の更新では消費されます。

    調査内容

    調査1:Ref.Stateをトランザクションに含め、コントラクトで検証可能か

    調査2:あるノードで発行したRef.Stateのバックチェーンを別のノードで確認できるか

    調査3:消費済みのRef.Stateをトランザクションに含められるか

    調査結果について

    調査1,2については要望を満たすことができましたが、調査3はエラーが出力されました。

    調査3で出力されたエラーについてはデモンストレーション編にて説明したいと思います。

    ※調査1,2と調査3では実装が異なるため、サンプルプログラムは2つ作成しました。

    サンプルプログラムについて

    サンプルプログラムはSBI R3 Japan株式会社が提供しております Corda trainingのコードにAddressState(住所録)をRef.Stateとして加えました。Corda trainingで扱うプログラムはIOU(借用証書)の発行(Issue)、譲渡(Transfer)、返済(Settle)に関するプログラムになります。今回は必要最低限の機能を実装するためにRef.Stateの実装対象をIssueのみに絞りました。

    ファイルの説明

    新規作成ファイル

    • AddressState.java:
      • Ref.Stateに相当するStateになります
    • AddressContract.java:
      • AddressStateを発行するPublishコマンドと更新するMoveコマンドを定義し、それらに関する制約を加えました。
    • PublishFlow.java:
      • Publishに関するFLowを作成しました。
    • MoveFlow.java:
      • Moveに関するFlowを作成しました。

    既存のファイルの変更

    • IOUIssueFlow.java:(調査1,2)
      • 最新のAddressState(=Ref.State)をトランザクションに含める処理を追加
    • IOUContract.java:
      • AddressState(=Ref.State)の検証に関する制約を追加
    • IOUTransferFlow:
      • AddressState(=Ref.State)のバックチェーンを発行者以外のノードに渡す処理を追加
    • IOUIssueFlow.java:(調査3):
      • 1つ前のAddressState(=Ref.State)をトランザクションに含める処理を追加
      • ※消費済みのRef.Stateは1つ前のRef.Stateとしています。

    CDL

    AddressStateに関するCDL

    重要な制約のみ説明したいと思います。MoveのTLCですが、addressとaddress(new)は変わっていなければならないという制約があります。これはMoveしたのに住所が変更されていないという状況を防ぐためです。

    image

    IOUIssueに関するCDL

    こちらはIOUのissueにAddressStateが付属した形になります。IOUの発行者の住所録以外が付属されるという状況を防ぐため、IOUの発行者(borrower)とAddressStateの発行者(issuer)は等しくなければならないという制約を加えています。

    image

    コード解説

    ※重要な箇所のみをピックアップして解説したいと思います。

    AddressState.java

        private static int ID_AddressState=1;
        @NotNull
        private final Party issuer;
        @NotNull
        private final String address;
        @NotNull
        private final UniqueIdentifier linearId;

    PartyはAddressStateの発行者、Addressが住所、そしてLinearIdを宣言しました。ID_AddressStateというフィールドですがIOUContract.javaで使用するので、ひとまずここではID_AddressStateという1がハードコーディングされたフィールドがあるという認識で大丈夫です。

    AddressContract.java(Publishに関する制約)

          //1. About InputState
          require.using("No Address InputState should be consumed when publishing Address State",
                  tx.inputsOfType(AddressState.class).isEmpty());
    
          //2. About OutputState
          final List<AddressState> outList=tx.outputsOfType(AddressState.class);
          require.using("Only one OutputState should be created",
                  outList.size()==1);

    AddressContract.javaにはPublishとMoveの定義・制約が記述されています。まずPublishに関する制約を説明します。PublishなのでInputStateは0個、OutputStateは1個存在しなければいけないという制約を加えました。また、ここには記載されていませんが AddressStateの発行者(Publishした人)の署名がないと話にならないので追加されています。

    AddressContract.java(Moveに関する制約)

    続いて、Moveに関する制約を説明します。MoveなのでInputStateとOutputStateは1つずつ存在しなければならないという制約を加えました。また、Moveの際にAddressStateのaddressというフィールドは変更されていなければなりません。 引っ越しをしたのに住所が変更されないケースはないと思います。一方、issuerとlinearIdは不変でなければいけません。 それらがMove特有の制約になります。Publish同様、署名に関する制約は記述されています。

    PublishFlow.java

    PublishFlow.javaではPublishを制御するFlowが記述されています。基本的な流れは通常のFlowと同じですが、AddressStateの発行者とNotary間のみでやり取りをしている点に注意してください。

    MoveFlow.java

    MoveFlow固有の処理としてInputStateとして用いるAddressStateを探す必要があります。ここではVaultQueryを用いて検索しています。この処理を行うことで、issuerとLinearIdを新たに設定する必要がなくなります。issuerとLinearIdは変わらないのでセットする手間が省けました。

    IOUContract.java

         private int ID_Contract=1;

    ID_Contractというフィールドは、AddressStateで宣言されたID_AddressStateというフィールドと共にAddressStateの制約の一つとして使用されます。以下にAddressStateに関する制約を記載します。

    まず一つ目は、AddressStateのissuerとIOUのborrowerが等しくなければならないという制約を加えます。IOUを発行するのはborrowerになります。したがって、borrowerの住所がAddressStateに記載される必要があります。次に、ID_AddressStateとID_Contractが等しいか確認する制約を加えました。この制約が必要な理由として、要望1のRef.Stateをトランザクションに含め、コントラクトで検証か を確かめるためです。共に1がハードコーディングされているので少々強引ですが、これで要望1を満たすことができます。

    IOUIssueFlow.java(要望1,2)

    上記は、要望1,2の場合のIOUIssueFlow.java固有のgetAddressIssuer関数になります。トランザクションにAddressStateを含める過程で、含めるAddressStateを選択する重要な処理になります。 具体的には引数のaddressStateIssuerを元に、このissuerのAddressStateが本当に存在するか確認します。存在する場合はその情報をリストに格納してreturnしています。

    IOUIssueFlow.java(要望3)

    上記は、1つ前のAddressStateを探す処理になります。具体的にはまず最新のAddressStateを検索し、そこから1つ前のAddressStateにさかのぼる重要な処理になります。VaultQueryを使い最新のAddressStateのハッシュ値を取り出します。続いて、ValidatedTransactionでそのAddressStateの詳細な情報を調べます。そこからInputとして使われたAddressStateを見つけます。ここで注意していただきたい点は、Inputとして使われてたAddressStateは1つ前のAddressStateであるという点です。 そしてハッシュ値を取り出せばAddressStateのトレースバックは完了です。

    続いて前の処理で検索した1つ前のAddressStateのハッシュ値を元に、そのAddressStateの情報を取り出すgetAddressContent()という関数が必要になります。大まかな流れは要望1,2のgetAddressIssuer()と同じになります。異なる点として、issuerではなくハッシュ値で目的のAddressStateが存在するか確認しています。

    サンプルプログラムのURL

    調査1,2のサンプルプログラムです。

    https://github.com/ShotaIMO/investigation-of-ReferenceState

    調査3のサンプルプログラムです。

    https://github.com/ShotaIMO/investigation-of-Previous-ReferenceState

    サンプルプログラム作成にあたって、ご協力いただいたSBI R3 Japan株式会社のエンジニアチームの皆様

    ありがとうございました。

    終わりに

    以上がコード解説編になります。サンプルプログラムを実際に動かした結果は、 デモンストレーション編 で解説したいと思います。続きが気になる方は是非ご一読ください。ここまで読んでいただき、ありがとうございました。

    ➡️
    次の記事はこちら CordaにおけるReference Stateの活用方法(デモンストレーション編)
    📬
    最後までお読みいただきありがとうございます。当社へのご質問・ご要望がございましたら、📪SBI R3 Japan お問い合わせフォーム📪よりお気軽にお問い合わせください!

    <ご質問・ご要望の例>

    • Corda Portalの記事について質問したい
    • ブロックチェーンを活用した新規事業を相談したい
    • 企業でのブロックチェーン活用方法を教えて欲しい 等々
    📢
    また、厳選されたCordaに関する最新情報をお伝えるするメールマガジンやX、当社主催のイベントコミュニティを運営しております。ぜひご登録ください。
    • Cordaメールマガジンに登録
    • X(旧Twitter)をフォロー
    • 弊社イベントコミュニティ(Connpass)に参加
    ✍️
    Written by 井本 翔太 (Shota Imoto)
    image

    SBI R3 Japanインターン エンジニアリング部所属

    Cordaの技術調査・検証や記事執筆など多岐に渡ります

    趣味:英語学習(まだまだですが…)・テニス・映画鑑賞

    イギリス大学院進学 2025年9月~🇬🇧

    →筆者の記事一覧

    Logo

    © copyright SBI R3 Japan 2025

    GitHubYouTubeXFacebookLinkedIn
         List<AddressState> inputState=tx.inputsOfType(AddressState.class);
         List<AddressState> outputState=tx.outputsOfType(AddressState.class);
    
         //add constraints regarding Move command.
         //1. About InputState and OutputState
         require.using("Move transaction should only consume and create one InputState and One OutputState.",
                 inputState.size()==1 &&
                 outputState.size()==1);
    
         //2. About Address information
         require.using("Address information should be changed in Move transaction",
                 !(inputState.get(0).getAddress()).equals(outputState.get(0).getAddress()));
    
         //3. Other fields should not be changed.
         require.using("Only address field should be changed in Move transaction",
                 inputState.get(0).getIssuer().equals(outputState.get(0).getIssuer()) &&
                 inputState.get(0).getLinearId().equals(outputState.get(0).getLinearId()));
         //2. Add outputState and Command into TX.
         AddressState state=new AddressState(getServiceHub().getMyInfo().getLegalIdentities().get(0), address);
                  Command txCommand=new Command(new 
         AddressContract.Commands.Publish(),getServiceHub().getMyInfo().getLegalIdentities().get(0).getOwningKey());
                  TransactionBuilder txBuilder=new TransactionBuilder(notary)
                        .addOutputState(state, AddressContract.ADDRESS_CONTRACT_ID)
                        .addCommand(txCommand);
         //2. Find already published AddressState.
         StateAndRef<AddressState> oldState=getServiceHub().getVaultService().queryBy(AddressState.class).getStates().get(0);
         AddressState oldStateData=oldState.getState().getData();
         String newAddress=address;
         AddressState newAddressState=new AddressState(
                  oldStateData.getIssuer(),
                  newAddress,
                  oldStateData.getLinearId()
         );
         //whether matches IOU lender and AddressState issuer.
         AddressState addressState =tx.referenceInputRefsOfType(AddressState.class).get(0).getState().getData();
         require.using("The lender of IOUState and the issuer of AddressState should be matched.",addressState.getIssuer().equals(outputState.getBorrower()));
    
                    //ID constraints
         require.using("ID_AddressState and ID_Contract must be same.",ID_Contract==AddressState.getID_AddressState());
           public StateAndRef<AddressState> getAddressIssuer(Party addressStateIssuer){
    			Predicate<StateAndRef<AddressState>> byIssuer=addressISU
    					->(addressISU.getState().getData().getIssuer().equals(addressStateIssuer));
    			List<StateAndRef<AddressState>> addressLists = getServiceHub().getVaultService().queryBy(AddressState.class)
    					.getStates().stream().filter(byIssuer).collect(Collectors.toList());
    			if(addressLists.isEmpty()){
    				return null;
    			}else{
    				return addressLists.get(0);
    			}
    		}
                //1. get latest AddressState's hash by using vaultQuery.
    			QueryCriteria queryCriteria=new QueryCriteria.VaultQueryCriteria(Vault.StateStatus.ALL);
    			Vault.Page results =getServiceHub().getVaultService().queryBy(AddressState.class,queryCriteria);
    			//The index is set to 1 to see the item of "ref".
    			StateAndRef included_addressState=(StateAndRef) results.getStates().get(1);
    			SecureHash vault_addressHash=included_addressState.getRef().getTxhash();
    
    			//2. get previous AddressState's hash by using validatedTransaction.
    			StateRef results1 =getServiceHub().getValidatedTransactions().getTransaction(vault_addressHash).getInputs().get(0);
    			SecureHash previousHash=results1.getTxhash();
    			//Output previous hash to confirm.
    			System.out.println("previousHash="+previousHash);
         public StateAndRef<AddressState> getAddressContent(SecureHash previousHash){
    			QueryCriteria queryCriteria=new QueryCriteria.VaultQueryCriteria(Vault.StateStatus.ALL);
    			//search the hash equal to the previous hash and put it in the list.
    			Predicate<StateAndRef<AddressState>> byHash=address_hash
    					->(address_hash.getRef().getTxhash().equals(previousHash));
    			List<StateAndRef<AddressState>> addressHashLists=getServiceHub().getVaultService().queryBy(AddressState.class,queryCriteria)
    					.getStates().stream().filter(byHash).collect(Collectors.toList());
    			//Output previous hash to confirm.
    			System.out.println("the content of list="+ addressHashLists);
    			if(addressHashLists.isEmpty()){
    				return null;
    			}else{
    				return addressHashLists.get(0);
    			}
    		}