ChainAPIの使い方

#Chainとは

VISA主導で進められていたブロックチェーンプロジェクトで、
Chain Core Developer Editionというエディションに限りオープンソース化されている。
Chainの環境構築までは、構築手順を記事にしているのでそちらを参照。
[Chain環境構築](http://qiita.com/akitoh/items/e27e7b951ded4e5d90d4)

公式ページのドキュメントにサンプルコードもあるので参考に。
https://chain.com/docs/core/get-started/introduction

基本的にはサンプルコードやJavadoc、SDKのソースコードを見れば、何をすればいいかはわかるはず。

まだコントラクトなどは実装されていない。

実行したトランザクションや作成したアカウントの情報などは、Chainを実行しているサーバの1999ポートにブラウザアクセスして見るダッシュボード上でも確認できる。

#API解説

###分類

大きくわけて、検索クエリの実行と、送金などのトランザクション実行がある。
クエリの実行には認証のためのKeyは不要だが、出金などトランザクションの発生するAPIの実行には認証Keyが必要となる。

また、Chainでは内部的なIDなども見ることはできるが、
ほとんどはalias（別名）を通貨やアカウントなどに定義して作成し、
クエリやトランザクションの実行もaliasを使って実行する。

###事前準備

Chainを実行しているサーバのURL、ポート、アクセスキーをメモしておく。

```
	public static final String TARGET_URL = "http://XX.XXX.XX.XXX:1999/";
	public static final String TOKEN = "client:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX";
	public static final String KEY_ALIAS = "test";
```

まず、サーバに接続するためのClientクラスを生成。

```
Client client = new Client(TARGET_URL,TOKEN);
```

検索クエリを実行する場合はClientだけでよいが、トランザクションを実行する場合は下記の認証キーの設定が必要。

```
MockHsm.Key key = null;
MockHsm.Key.Items keys = new MockHsm.Key.QueryBuilder().addAlias(ApiSettings.KEY_ALIAS).execute(client);
if (keys.hasNext()){
	key = keys.next();
}else{
	key = MockHsm.Key.create(client,ApiSettings.KEY_ALIAS);
}
HsmSigner.addKey(key, MockHsm.getSignerClient(client));
```

この認証キー作成時に指定したaliasが異なると認証エラーになり送金などはできない。
送金するアカウントを作成したときに使ったKeyを送金トランザクションの認証時にも使わなければならない。

```
MockHsm.Key key = MockHsm.Key.create(client);
```
のように、aliasを指定せずにKeyを作成した場合、無名のKeyが無制限に作られてしまうため、作るときは以下のようにaliasを指定して、特定のキーで作成する。

```
MockHsm.Key key = MockHsm.Key.create(client,ApiSettings.KEY_ALIAS);
```
これにより、作成済みのキーはクエリで検索し、再利用することができる。
逆に、aliasを指定しておかないと、そのアカウントの認証に必要なキーをトランザクション実行時に明示的に指定できなくなる為注意。
その後、アカウント作成やアセット（通貨）定義、送金などを実行する。


#個別にAPI実装例を。
あくまで参考だが、公式ページのサンプルをそのまま使っていくと、認証の扱いでエラーに悩まされると思われる。

###アカウント作成

```
@Service
public class AccountService extends BaseService {

	private static final Logger LOGGER = LoggerFactory.getLogger(AccountService.class.getName());

	public String create(String account) throws ChainException{
		Client client = createClient();
		MockHsm.Key key = createKey(client);
		HsmSigner.addKey(key, MockHsm.getSignerClient(client));

		Account.Items accounts = new Account.QueryBuilder()
			.setFilter("alias=$1")
			.addFilterParameter(account)
			.execute(client);
		Account created = null;
		if (accounts.hasNext()){
			created = accounts.next();
			LOGGER.info("account is exists.");
		}else{
			created = new Account.Builder()
				.setAlias(account)
				.addRootXpub(key.xpub)
				.setQuorum(1)
				.create(client);
			LOGGER.info("account created.");
		}
		return created.alias;
	}
}
```
ここで、BaseServiceは以下のようになっている。
	    
```
@Service
public class BaseService {

	/**
	 * 接続Clientを取得する
	 * @return client
	 * @throws ChainException
	 */
	protected Client createClient() throws ChainException{
		return new Client(ApiSettings.TARGET_URL,ApiSettings.TOKEN);
	}

	/**
	 * 共通認証Keyを取得する
	 * @param client
	 * @return key
	 * @throws ChainException
	 */
	protected MockHsm.Key createKey(Client client) throws ChainException{
		MockHsm.Key key = null;
		MockHsm.Key.Items keys = new MockHsm.Key.QueryBuilder().addAlias(ApiSettings.KEY_ALIAS).execute(client);
		if (keys.hasNext()){
			key = keys.next();
		}else{
			key = MockHsm.Key.create(client,ApiSettings.KEY_ALIAS);
		}
		return key;
	}
}
```

まず、QueryBuilderを使ってアカウントが存在するか検索している。

```
		Account.Items accounts = new Account.QueryBuilder()
			.setFilter("alias=$1")
			.addFilterParameter(account)
			.execute(client);
```
AccountやAsset、Balance、MockHsm.Keyなど主要なクラスにはすべて**QueryBuilder()**メソッドが用意されており、
同じような書き方で検索できる。

**setFilter("条件")**には検索条件を文字列で指定する。
ここに指定できる条件は公式ドキュメントのリファレンスのAPI Objectのページに書かれている。
https://chain.com/docs/core/reference/api-objects

次の**addFilterParameter(account)**には、setFilterで指定した**\$1,$2**などに埋め込む文字列を指定する。
ここでは引数で渡されたアカウント名文字列(alias)を指定している。
filterを設定したらexecuteで実行する。

クエリについては以下のページに書かれている。
https://chain.com/docs/core/build-applications/queries

結果はAccount.Items や Balance.Itemsなどitelatorで帰ってくるので

```
		if (accounts.hasNext()){
			created = accounts.next();
```

で取得している。

これは例えば、

```
		while (balances.hasNext()) {
		  Balance b = balances.next();
		  LOGGER.info("balance of " + b.sumBy.get("asset_alias") + ": " + b.amount);
		}
```

のようにwhileで回すなり、

```
	    assets.forEachRemaining(s -> {
		      assetList.add(new AssetDto(s.id,s.alias));
	    });
```

のようにラムダで回すなりしてもいい。

最後に、検索結果がなかった場合にアカウントを新規作成している。

```
			created = new Account.Builder()
				.setAlias(account)
				.addRootXpub(key.xpub)
				.setQuorum(1)
				.create(client);
			LOGGER.info("account created.");
```
この処理時に使われるMockHsm.Keyが事前準備で作ったKeyとなるが、このアカウントに関連したトランザクションの実行にも同じKeyを使う必要がある。
アカウント別に作成すべきだが、管理が複雑になるためここでは共通キー（alias）を使っている。

###アセット（通貨）生成
ここではトランザクションを実行している。
通貨の生成と同時にその通貨を指定したアカウントに渡している。
入力と出力は必ずセットになる。

**Issue**アクションで通貨を定義し、
**ControlWithAccount**アクションで、生成した通貨を指定のアカウントに渡している。

```
@Service
public class IssueService extends BaseService {

	private static final Logger LOGGER = LoggerFactory.getLogger(IssueService.class.getName());

	public void issue(String assetName,String issueAccount,Long amount) throws ChainException{

		Client client = createClient();
		MockHsm.Key key = createKey(client);
		HsmSigner.addKey(key, MockHsm.getSignerClient(client));
		
		// assetNameの通貨が定義されていない場合は生成する。
		if (!isExistAsset(assetName,client)){
		    new Asset.Builder()
		      .setAlias(assetName)
		      .addRootXpub(key.xpub)
		      .setQuorum(1)
		      .create(client);
		}
		// 通貨定義トランザクション。
		// IssueアクションからControlWithAccountアクションでissueAccountにassetNameの通貨をamount分配っている。
		Transaction.Template issuanceToProgram = new Transaction.Builder()
	      .addAction(new Transaction.Action.Issue()
	        .setAssetAlias(assetName)
	        .setAmount(amount)
	      ).addAction(new Transaction.Action.ControlWithAccount()
	        .setAccountAlias(issueAccount)
	        .setAssetAlias(assetName)
	        .setAmount(amount)
	      ).build(client);
		// トランザクション署名と実行
	    Transaction.Template signedIssuanceToProgram = HsmSigner.sign(issuanceToProgram);
	    Transaction.submit(client, signedIssuanceToProgram);
	}

	private boolean isExistAsset(String assetName,Client client) throws ChainException {

		Asset.Items assets = new Asset.QueryBuilder()
	      .setFilter("alias=$1")
	      .addFilterParameter(assetName)
	      .execute(client);

	    if (assets.hasNext()){
	    	return true;
	    }
	    return false;
	}
}
```

###定義済みアセット（通貨）検索
ほかのクエリ同様の書き方。

```
@Service
public class AssetQueryService extends BaseService {

	public List<AssetDto> getAssetList(String assetName) throws ChainException {

		Client client = createClient();

	    Asset.Items assets = new Asset.QueryBuilder()
	      .setFilter("alias=$1")
	      .addFilterParameter(assetName)
	      .execute(client);

	    List<AssetDto> assetList = new ArrayList<>();
	    assets.forEachRemaining(s -> {
		      assetList.add(new AssetDto(s.id,s.alias));
	    });
	    return assetList;
	}
}

```

###残高確認
これもBalanceクラスを使う以外は同様の書き方。
以下の例では2つの検索パラメータを使っている。

```
@Service
public class BalanceService extends BaseService {

	private static final Logger LOGGER = LoggerFactory
			.getLogger(BalanceService.class.getName());

	public ResponseBalanceDto getBalance(String assetName, String account)
			throws ChainException {
		Client client = createClient();

		Balance.Items balances = new Balance.QueryBuilder()
			.setFilter("account_alias=$1 AND asset_alias=$2")
			.addFilterParameter(account)
			.addFilterParameter(assetName)
			.execute(client);
		Balance balance = null;
		ResponseBalanceDto responseDto = new ResponseBalanceDto();
		if (balances.hasNext()){
			balance = balances.next();
			responseDto.setBalance(balance.amount);
			LOGGER.info("account:{} asset:{} amount:{} ",account,assetName,balance.amount);
		}
		return responseDto;
	}
}

```

###送金
**SpendFromAccount**アクションで、送金元から通貨を出金し、
**ControlWithAccount**アクションで、送金先に通貨を渡している。
aliasだけでトランザクションが実行されているわけではないので、Keyなどに注意すること。

```
@Service
public class TransferService extends BaseService {

	private static final Logger LOGGER = LoggerFactory.getLogger(TransferService.class.getName());

	/**
	 * 送金実行
	 * @param fromAccount 送金元アカウント
	 * @param toAccount   送金先アカウント
	 * @param assetName   通貨名
	 * @param amount      金額
	 * @throws ChainException
	 */
	public void transfer(String fromAccount, String toAccount,
			String assetName, Long amount) throws ChainException {
		Client client = createClient();
		MockHsm.Key key = createKey(client);
		HsmSigner.addKey(key, MockHsm.getSignerClient(client));
		Transaction.Template transfertran = new Transaction.Builder()
				.addAction(
						new Transaction.Action.SpendFromAccount()
								.setAccountAlias(fromAccount)
								.setAssetAlias(assetName).setAmount(amount))
				.addAction(
						new Transaction.Action.ControlWithAccount()
								.setAccountAlias(toAccount)
								.setAssetAlias(assetName).setAmount(amount))
				.build(client);

		Transaction.Template signedTransfertran = HsmSigner.sign(transfertran);
		Transaction.submit(client, signedTransfertran);

	}
}

```

###key
Chain,blockchain,API