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

## まえがき
今回、Reference State(以下Ref.State)に関する細かな動作確認を行うため調査を行いました。
調査にあたりましてサンプルプログラムを作成しました。**コード解説編**と **[デモンストレーション編](https://qiita.com/ShotaIMO/items/8be229ab8cd873d87c89)**の２部編成でお届けしたいと思います。**本記事はRef.Stateについて詳しく知りたいCordaに精通していらっしゃるエンジニアの方やセールスの方を対象としております。** Cordaについて詳しく知りたい方は **[R3公式ドキュメント](https://docs.r3.com/)** や **[株式会社digglueさんの記事](https://baasinfo.net/?p=2205#outline__2_2)** をご覧ください。また、Cordaの概念であるUTXOモデルについて知りたい方は、**[こちらの解説記事](https://medium.com/corda-japan/corda%E3%83%87%E3%83%BC%E3%82%BF%E3%83%A2%E3%83%87%E3%83%AB%E3%81%AE%E7%89%B9%E5%BE%B4-701d49f02cd)** をご覧ください。

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


## Ref.Stateについて
Ref.Stateとは参照情報を表すStateの事で、使用時はトランザクションに含まれます。　　
Ref.Stateは、含まれるトランザクションで消費されることはありませんが、Ref.State自体の更新では消費されます。　　


## 調査内容
**調査１：Ref.Stateをトランザクションに含め、コントラクトで検証可能か**
**調査２：あるノードで発行したRef.Stateのバックチェーンを別のノードで確認できるか**
**調査３：消費済みのRef.Stateをトランザクションに含められるか**


## 調査結果について
調査１,２については要望を満たすことができましたが、**調査３はエラーが出力されました。**　　
調査３で出力されたエラーについては**デモンストレーション編**にて説明したいと思います。
<font color="Red">**※調査１,２と調査３では実装が異なるため、サンプルプログラムは2つ作成しました。**</font>


## サンプルプログラムについて
サンプルプログラムはSBI R3 Japan株式会社が提供しております **[Corda trainingのコード](https://github.com/sbir3japan/corda-dev-traning-sbir3japan)**に**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:(<font color="Red">調査1,2</font>)**
    - 最新のAddressState(=Ref.State)をトランザクションに含める処理を追加
- **IOUContract.java:**
    - AddressState(=Ref.State)の検証に関する制約を追加
- **IOUTransferFlow:**
    - AddressState(=Ref.State)のバックチェーンを発行者以外のノードに渡す処理を追加
- **IOUIssueFlow.java:(<font color="Red">調査3</font>):**
    - １つ前のAddressState(=Ref.State)をトランザクションに含める処理を追加
    - **<font color="Red">※消費済みのRef.Stateは1つ前のRef.Stateとしています。</font>**
    
## CDL
#### AddressStateに関するCDL
重要な制約のみ説明したいと思います。MoveのTLCですが、addressとaddress(new)は変わっていなければならないという制約があります。これは**Moveしたのに住所が変更されていない**という状況を防ぐためです。
![CDL.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2678414/456cd817-112f-8416-cbe9-ba96db4f85e5.png)

#### IOUIssueに関するCDL
こちらはIOUのissueにAddressStateが付属した形になります。IOUの発行者の住所録以外が付属されるという状況を防ぐため、IOUの発行者(borrower)とAddressStateの発行者(issuer)は等しくなければならないという制約を加えています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2678414/9e22af5f-0074-43f7-96d2-f86fba1b25d9.png)



## コード解説
**<font color="Red">※重要な箇所のみをピックアップして解説したいと思います。</font>**
### AddressState.java
```java: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
```java: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した人)の署名がないと話にならないので追加されています。


```java:AddressContract.java(Moveに関する制約)
     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()));
```
続いて、Moveに関する制約を説明します。MoveなのでInputStateとOutputStateは1つずつ存在しなければならないという制約を加えました。また、**<font color="Red">Moveの際にAddressStateのaddressというフィールドは変更されていなければなりません。</font>** 引っ越しをしたのに住所が変更されないケースはないと思います。**<font color="Red">一方、issuerとlinearIdは不変でなければいけません。</font>** それらがMove特有の制約になります。**Publish同様、署名に関する制約は記述されています。**
### PublishFlow.java
```java:PublishFlow.java
     //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);
```
PublishFlow.javaではPublishを制御するFlowが記述されています。基本的な流れは通常のFlowと同じですが、**AddressStateの発行者とNotary間のみ**でやり取りをしている点に注意してください。
### MoveFlow.java
```java:MoveFlow.java
     //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()
     );
```
MoveFlow固有の処理として**InputStateとして用いるAddressStateを探す**必要があります。ここではVaultQueryを用いて検索しています。この処理を行うことで、issuerとLinearIdを新たに設定する必要がなくなります。issuerとLinearIdは変わらないのでセットする手間が省けました。
### IOUContract.java
```IOUContract.java
     private int ID_Contract=1;
```
ID_Contractというフィールドは、**AddressStateで宣言されたID_AddressState**というフィールドと共にAddressStateの制約の一つとして使用されます。以下にAddressStateに関する制約を記載します。
```IOUContract.java
     //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());
```
まず一つ目は、AddressStateのissuerとIOUのborrowerが等しくなければならないという制約を加えます。IOUを発行するのはborrowerになります。したがって、borrowerの住所がAddressStateに記載される必要があります。次に、ID_AddressStateとID_Contractが等しいか確認する制約を加えました。この制約が必要な理由として、**要望1のRef.Stateをトランザクションに含め、コントラクトで検証か** を確かめるためです。共に1がハードコーディングされているので少々強引ですが、これで要望1を満たすことができます。
### IOUIssueFlow.java(要望1,2)
```java:IOUIssueFlow.java
       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,2の場合のIOUIssueFlow.java固有のgetAddressIssuer関数になります。<font color="Red">**トランザクションにAddressStateを含める過程で、含めるAddressStateを選択する重要な処理になります。**</font> 具体的には引数のaddressStateIssuerを元に、このissuerのAddressStateが本当に存在するか確認します。存在する場合はその情報をリストに格納してreturnしています。
### IOUIssueFlow.java(要望3)
```java:IOUIssueFlow.java
            //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);
```
上記は、1つ前のAddressStateを探す処理になります。具体的には<font color="Red">**まず最新のAddressStateを検索し、そこから１つ前のAddressStateにさかのぼる重要な処理になります。**</font>VaultQueryを使い最新のAddressStateのハッシュ値を取り出します。続いて、ValidatedTransactionでそのAddressStateの詳細な情報を調べます。そこからInputとして使われたAddressStateを見つけます。**ここで注意していただきたい点は、Inputとして使われてたAddressStateは１つ前のAddressStateであるという点です。** そしてハッシュ値を取り出せばAddressStateのトレースバックは完了です。
```java:IOUIssueFlow.java
     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);
			}
		}
```
続いて前の処理で検索した1つ前のAddressStateのハッシュ値を元に、そのAddressStateの情報を取り出すgetAddressContent()という関数が必要になります。大まかな流れは要望1,2のgetAddressIssuer()と同じになります。異なる点として、**issuerではなくハッシュ値**で目的のAddressStateが存在するか確認しています。

### サンプルプログラムのURL
調査1,2のサンプルプログラムです。
[**https://github.com/ShotaIMO/investigation-of-ReferenceState**](https://github.com/ShotaIMO/investigation-of-ReferenceState)
調査3のサンプルプログラムです。
[**https://github.com/ShotaIMO/investigation-of-Previous-ReferenceState**](https://github.com/ShotaIMO/investigation-of-Previous-ReferenceState)

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

## 終わりに
以上がコード解説編になります。サンプルプログラムを実際に動かした結果は、 **[デモンストレーション編](https://qiita.com/ShotaIMO/items/8be229ab8cd873d87c89)** で解説したいと思います。続きが気になる方は是非ご一読ください。ここまで読んでいただき、ありがとうございました。