Chainとは
VISA主導で進められていたブロックチェーンプロジェクトで、
Chain Core Developer Editionというエディションに限りオープンソース化されている。
Chainの環境構築までは、構築手順を記事にしているのでそちらを参照。
Chain環境構築
公式ページのドキュメントにサンプルコードもあるので参考に。
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