Oracle Blockchain Cloud Service（OBCS）で、独自チェーンコードの作成|デプロイ|実行する

# 0.はじめに
[前回記事（Oracle Blockchain Cloud Service（OBCS）を使ってみたら、まじ「ネ申」だった。）](https://qiita.com/nobkovskii/items/e389c2f26fd186915fc1)の続きです。
今回は、サンプルのチェーンコードを少しだけいじった「オリジナル（笑）チェーンコード」のデプロイ〜テスト実行までを実施します。

チェーンコードの言語は「Go」か「Node.js」で記述することが可能ですが、私がGoに馴染みがあまり無いので、Node.jsで記述していきます。
Go使いの方は、適宜読み替えていただければと思います。

デプロイ時に、チェーンコードファイルをZip化してから、デプロイするのですが、[公式](https://docs.oracle.com/cd/E83857_01/paas/blockchain-cloud/user/write-chaincode.html#GUID-64153568-41D2-40E6-9F66-EF384AFEC8E9)曰く、GoとNode.jsで、Zip化するファイルが異なるので、その点のみ注意が必要です。

* Goの場合
    * チェーンコードのファイルをZip化して、デプロイする
* Node.jsの場合
    * チェーンコードのファイルとpackage.jsonをZip化して、デプロイする。
        * 「scripts」セクションに、起動方法を記述する
        * 「dependencies」セクションに、依存関係を記述する

コード全行載せていたり、スクショを多めにしているので、やや長大にみえますが、実作業としては、30分もかからないかと思います。

早速、始めていきますが、結論から言います。
「**OBCSは、エンジニアをダメにします。取扱には注意しましょう**（褒め言葉）」


# 1.チェーンコード作成
## 1.1.チェーンコード記述（ほぼコピペ）
▼サンプル（fabcar）を参考に、チェーンコードを記述
https://github.com/hyperledger/fabric-samples/tree/release-1.2/chaincode/fabcar/node

使用するファイルは下記の通りです。

* obcs_cc_sample.js
    * fabcar.jsをコピペして修正したファイル
    * ファイル名等は、適当でも問題ありません
* package.json
    * ほぼ、そのまんま
    * 最低でも「scripts」＞「start」の内容は、↑のファイル名を指定するように修正しておきます

内容としては「どうなんだ？」と思われますが、「fabcar」を修正し、「人（名前・誕生日）」をブロックチェーンに記録する内容に変更しました。
だいたい「car」を「human」に変えたくらいです。
これで「**オリジナル（笑）チェーンコード**」の完成です。
コードは下記になります。
※コメントを修正していないのは、ごめんなさい。無視してください
※コードのコピペが面倒な方は、[私のGithub](https://github.com/nobkovskii/obcs_cc_sample)にZipファイルも配置してあるので、そちらをお使いください

``` obcs_cc_sample.js
/*
# Copyright IBM Corp. All Rights Reserved.
#
# SPDX-License-Identifier: Apache-2.0
*/

'use strict';
const shim = require('fabric-shim');
const util = require('util');

let Chaincode = class {

  // The Init method is called when the Smart Contract 'fabcar' is instantiated by the blockchain network
  // Best practice is to have any Ledger initialization in separate function -- see initLedger()
  async Init(stub) {
    console.info('=========== Instantiated obcs_cc_sample chaincode ===========');
    return shim.success();
  }

  // The Invoke method is called as a result of an application request to run the Smart Contract
  // 'fabcar'. The calling application program has also specified the particular smart contract
  // function to be called, with arguments
  async Invoke(stub) {
    let ret = stub.getFunctionAndParameters();
    console.info(ret);

    let method = this[ret.fcn];
    if (!method) {
      console.error('no function of name:' + ret.fcn + ' found');
      throw new Error('Received unknown function ' + ret.fcn + ' invocation');
    }
    try {
      let payload = await method(stub, ret.params);
      return shim.success(payload);
    } catch (err) {
      console.log(err);
      return shim.error(err);
    }
  }

  async queryHuman(stub, args) {
    if (args.length != 1) {
      throw new Error('Incorrect number of arguments. Expecting HumanNumber ex: HUMAN01');
    }
    let humanNumber = args[0];

    let humanAsBytes = await stub.getState(humanNumber);
    if (!humanAsBytes || humanAsBytes.toString().length <= 0) {
      throw new Error(humanNumber + ' does not exist: ');
    }
    console.log(humanAsBytes.toString());
    return humanAsBytes;
  }

  async initLedger(stub, args) {
    console.info('============= START : Initialize Ledger ===========');
    let human = [];
    human.push({
      name: 'Tanaka',
      birthday: '19900101'
    });
    human.push({
      name: 'Yamada',
      birthday: '19800401'
    });

    for (let i = 0; i < human.length; i++) {
      human[i].docType = 'human';
      await stub.putState('Human' + i, Buffer.from(JSON.stringify(human[i])));
      console.info('Added <--> ', human[i]);
    }
    console.info('============= END : Initialize Ledger ===========');
  }

  async createHuman(stub, args) {
    console.info('============= START : Create Human ===========');
    if (args.length != 3) {
      throw new Error('Incorrect number of arguments. Expecting 3');
    }

    var human = {
      docType: 'human',
      name: args[1],
      birthday: args[2]
    };

    await stub.putState(args[0], Buffer.from(JSON.stringify(human)));
    console.info('============= END : Create Human ===========');
  }

  async queryAllHuman(stub, args) {

    let startKey = 'Human0';
    let endKey = 'Human999';

    let iterator = await stub.getStateByRange(startKey, endKey);

    let allResults = [];
    while (true) {
      let res = await iterator.next();

      if (res.value && res.value.value.toString()) {
        let jsonRes = {};
        console.log(res.value.value.toString('utf8'));

        jsonRes.Key = res.value.key;
        try {
          jsonRes.Record = JSON.parse(res.value.value.toString('utf8'));
        } catch (err) {
          console.log(err);
          jsonRes.Record = res.value.value.toString('utf8');
        }
        allResults.push(jsonRes);
      }
      if (res.done) {
        console.log('end of data');
        await iterator.close();
        console.info(allResults);
        return Buffer.from(JSON.stringify(allResults));
      }
    }
  }

  async changeHumanName(stub, args) {
    console.info('============= START : changeCHumanName ===========');
    if (args.length != 2) {
      throw new Error('Incorrect number of arguments. Expecting 2');
    }

    let humanAsBytes = await stub.getState(args[0]);
    let human = JSON.parse(humanAsBytes);
    human.name = args[1];

    await stub.putState(args[0], Buffer.from(JSON.stringify(human)));
    console.info('============= END : changeCHumanName ===========');
  }
};

shim.start(new Chaincode());
```

``` package.json
{
	"name": "obcs_cc_sample",
	"version": "1.0.0",
	"description": "obcs chaincode implemented in node.js",
	"engines": {
		"node": ">=8.4.0",
		"npm": ">=5.3.0"
	},
	"scripts": {
		"start": "node obcs_cc_sample.js"
	},
	"engine-strict": true,
	"license": "Apache-2.0",
	"dependencies": {
		"fabric-shim": "~1.2.0"
	}
}
```

## 1.2.Zip化
▼作成したファイルを選択して圧縮
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/306608/163a9d7b-7e65-3cd4-6f59-7416ebb60c74.png)

## 1.3.チェーンコードのテスト
Peerにチェーンコードをデプロイして、正しく動作するかをテストします。
間違いが許されないチェーンコードだからこそ、こういう仕組みがあるのは嬉しいポイント（と、書いたものの、本当にそういう意味かは理解していないです）。

## 1.3.1.チェーンコードのインストール＆インスタンス化
▼「Chaincodes」を選択し、「Deploy a New Chaincode」を選択
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/306608/bd76f41b-1a94-c0f9-a4d1-cd2fbdfd8a14.png)

▼「Quick Deployment」を選択
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/306608/36f2cc8b-4330-d678-ab8e-277e09495757.png)

▼必要に応じて情報を修正し、「Upload Chaincode File」を選択し、先ほどZip化したファイルを選択
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/306608/8fde1398-2a19-04c0-3e29-b2471a56a538.png)

▼「Submit」＞「Yes」を選択
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/306608/37fdbde6-22f8-46c2-9593-3ee21f61cd2e.png)

▼インスタンス化するので、しばし待つ
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/306608/74beca05-143e-2511-5203-26aaddf902ea.png)

▼成功していたら、モーダルを閉じる
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/306608/222be3e2-3621-81c5-f020-bd91758c1a71.png)

▼追加されてる！！
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/306608/f3ddeb18-2c79-63e2-8077-08d5f8b8aec1.png)

## 1.3.2.cURL実行
詳細は公式参考
https://docs.oracle.com/en/cloud/paas/blockchain-cloud/rest-api/UsecURL.html

▼コンソールを開いて、コマンド（cURL）を実行
cURLのインストール方法などについては割愛します。
コマンドで記載している「id」「pass」はOracleCloudへのログインと同じもの。

|項目|説明|
|---|---|
|id|OracleCloudへのログインと同じもの|
|pass|OracleCloudへのログインと同じもの|
|route|REST ProxyのRouteパス|

* ↓id/pass：OracleCloudへのログインと同じもの
    * ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/306608/44cf31d5-50bb-fe7f-5da9-bcd618b76c38.png)
* ↓route：REST ProxyのRouteパス
    * ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/306608/0ae66fab-7e1f-379f-34aa-137693bcbe0a.png)


▼cURL（InitLedger）
・コマンド

``` 
$ curl -i -u id:pass -H "Content-type:application/json" -X POST route/restproxy1/bcsgw/rest/v1/transaction/invocation -d '{"channel":"alpha001","chaincode":"obcs_cc_sample","method":"initLedger","args":["human001"],"chaincodeVer":"v1"}'
```
・結果

``` 
HTTP/1.1 200 
Date: Wed, 17 Apr 2019 08:01:26 GMT
Content-Type: application/json
Content-Length: 98
Connection: keep-alive

{"returnCode":"Success","txid":"5c38e5b531b3b466170f352939bbe0b79b756ab1f6fd92ae51e2f87520218964"}
```

▼cURL（queryAll）
・コマンド

``` 
$ curl -i -u id:pass -H "Content-type:application/json" -X POST path/restproxy1/bcsgw/rest/v1/transaction/query -d '{"channel":"alpha001","chaincode":"obcs_cc_sample","method":"queryAllHuman","args":["human001"],"chaincodeVer":"v1"}'
```
・結果

```
HTTP/1.1 200 
Date: Wed, 17 Apr 2019 08:02:23 GMT
Content-Type: application/json
Content-Length: 283
Connection: keep-alive

{"returnCode":"Success","result":{"message":"","payload":"[{\"Key\":\"Human0\",\"Record\":{\"name\":\"Tanaka\",\"birthday\":\"19900101\",\"docType\":\"human\"}},{\"Key\":\"Human1\",\"Record\":{\"name\":\"Yamada\",\"birthday\":\"19800401\",\"docType\":\"human\"}}]","encode":"UTF-8"}}
```

▼「Channels」＞「チェーンコードをインストールしたチャネル」
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/306608/0d4360fb-a0c6-b46e-7a3d-f259d10046ae.png)

▼Blockを確認
実行したトランザクションが、台帳に書き込まれています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/306608/8a461bd3-1428-e7ea-cbdc-6766c601437a.png)

## 2.おわりに
以上で、チェーンコードの作成〜実行までが完了しました。
チェーンコードはほぼコピペだったものの、チェーンコードのデプロイや実行ってこんなに簡単だったっけかな？と思います。

OracleCloudを始め、AWSやAzure、Firebaseなどの「クラウドマネージドサービス（○aaS）」の登場により、とても便利な世の中になりました（と言っている私のエンジニア歴は浅い）。

使えるリソースはふんだんに使い、サービスを組み合わせ、
「**爆速で空想を実現できる。**」
そんな世の中で、エンジニアとしての職につけていることに少しの誇りだけを持ちつつ、そういったサービスを提供/保守してくれている方々に感謝の意を述べて締めようと思います。

次回は、外部システムとの連携について記事を書こうと思います。

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