備忘69

個人的な備忘録です。細かいtips多め。


Hyperledger Fabric bna deployでコケる

症状

xxxx-xx-xx 07:38:40.662 UTC [chaincode-platform] generateDockerfile -> DEBU 525
FROM ibmblockchain/fabric-baseos:0.3.1
ADD binpackage.tar /usr/local/bin
LABEL org.hyperledger.fabric.chaincode.id.name="project-test" \
     org.hyperledger.fabric.chaincode.id.version="0.13.0" \
     org.hyperledger.fabric.chaincode.type="GOLANG" \
     org.hyperledger.fabric.version="1.0.1" \
     org.hyperledger.fabric.base.version="0.3.1"
ENV CORE_CHAINCODE_BUILDLEVEL=1.0.1
xxxx-xx-xx 07:38:40.695 UTC [util] DockerBuild -> DEBU 526 Attempting build with image ibmblockchain/fabric-ccenv:1.0.1
xxxx-xx-xx 07:39:48.650 UTC [dockercontroller] deployImage -> DEBU 527 Created image: dev-peer0.org1.example.com-project-test-0.13.0-02208064ee2904b44e223320e32de8877802e6faf608fa7043c12fba3162b076 xxxx-xx-xx 07:39:48.650 UTC [dockercontroller] Start -> DEBU 528 start-recreated image successfully
xxxx-xx-xx 07:39:48.650 UTC [dockercontroller] createContainer -> DEBU 529 Create container: dev-peer0.org1.example.com-project-test-0.13.0
xxxx-xx-xx 07:39:48.696 UTC [dockercontroller] createContainer -> DEBU 52a Created container: dev-peer0.org1.example.com-project-test-0.13.0-02208064ee2904b44e223320e32de8877802e6faf608fa7043c12fba3162b076
xxxx-xx-xx 07:39:48.871 UTC [dockercontroller] Start -> DEBU 52b Started container dev-peer0.org1.example.com-project-test-0.13.0
xxxx-xx-xx 07:39:48.871 UTC [container] unlockContainer -> DEBU 52c container lock deleted(dev-peer0.org1.example.com-project-test-0.13.0)
xxxx-xx-xx 07:43:40.188 UTC [eventhub_producer] validateEventMessage -> DEBU 52d ValidateEventMessage starts for signed event 0xc4216ada10
xxxx-xx-xx 07:43:40.188 UTC [eventhub_producer] deRegisterHandler -> DEBU 52e deregistering event type: BLOCK
xxxx-xx-xx 07:43:40.201 UTC [eventhub_producer] Chat -> ERRO 52f error during Chat, stopping handler: rpc error: code = Canceled desc = context canceled

以下の環境変数でchaincodeのコンテナからピアが見えるようになるらしい。
- CORE_PEER_ADDRESSAUTODETECT=true

nodejs用フレームワークnestjsのサンプルを触ってみた(1)

公式docs

https://docs.nestjs.com/
早い話がサーバーサイド版Angular(?)

cats-app example

https://github.com/nestjs/nest/tree/master/examples/01-cats-app

起動

git clone https://github.com/nestjs/nest.git  
cd ./nest/examples/01-cats-app/
npm i  
npm run start

localhost:3000 にサーバーが立ち上がります。

とりあえず基本的な部分を使ってみる

その前に cats.controller.ts の中身を一部変更する。

  @Post()
  // @Roles('admin')
  async create(@Body() createCatDto: CreateCatDto) {
  this.catsService.create(createCatDto);
  }

権限の問題を避けるため @Roles デコレーターをコメントアウトした。 ユーザー権限をメソッド単位で管理できるのは便利そう。

create() メソッドを動かすためのPOSTリクエストを投げてみる。

##Doraemon を追加
$curl -v -H "Accept: application/json" -H "Content-type: application/json" -X POST -d '{ "name":"Doraemon","age":12,"breed":"R-01FR001-MKII"}' http://localhost:3000/cats/
##Doramichan を追加
$curl -v -H "Accept: application/json" -H "Content-type: application/json" -X POST -d '{ "name":"Doramichan","age":10,"breed":"R-01FR001-MKII"}' http://localhost:3000/cats/

次に findAll() メソッドを動かすためのGETリクエストを投げてみる。

$curl http://localhost:3000/cats/
##catsが帰ってくる。
{"data":[{"name":"Doraemon","age":12,"breed":"R-01FR001-MKII"},{"name":"Doramichan","age":10,"breed":"R-01FR001-MKII"}]}

こんな感じの簡単なHTTPサーバーになっていることがわかる。

構成

/nest/examples/01-cats-app/src$ tree
.
├── app.module.ts
├── cats
│   ├── cats.controller.spec.ts
│   ├── cats.controller.ts  ★
│   ├── cats.module.ts
│   ├── cats.service.ts  ★
│   ├── cats.service.spec.ts  ★(作成)

│   ├── dto
│   │   └── create-cat.dto.ts
│   └── interfaces
│   └── cat.interface.ts
(略)
└── main.ts

★...とりあえず今回見てみたところ

コントローラー

クライアントからリクエストを受けてレスポンスを返すクラス。API と処理を記述。
cats/cats.controller.ts

(略)
@Controller('cats')
@UseGuards(RolesGuard)
@UseInterceptors(LoggingInterceptor, TransformInterceptor)
export class CatsController {
  constructor(private readonly catsService: CatsService) {}

  @Post()
  // @Roles('admin')
  async create(@Body() createCatDto: CreateCatDto) {
  return this.catsService.create(createCatDto);
  }

  @Get()
  async findAll(): Promise<Cat[]> {
  return await this.catsService.findAll();
  }

  @Get(':name')
  async findOne(@Param() params):Promise<Cat> {
  return await this.catsService.findOne(params.name);
  }
}

constructor で catsServiceを消費しているあたり Angular でいう *component.ts でDIしている感じ。

(参考:https://angular.io/guide/dependency-injection)

最後にGet http://localhost:3000/cats/:name を追加して、猫を名前で検索できるようにした。 @Param() は expressでいうところの req.params で、 params.name のようにプロパティにアクセスできる。

コンポーネント

コントローラー(や他のコンポーネント)にDIされるもの。サービスのコンポーネントにはDBとやりとりするためのSQLやORMを書く。
cat.service.ts

(略)
/* 追加 */
import { HttpException } from "@nestjs/core";

@Component()
export class CatsService {
  private readonly cats: Cat[] = [];

  create(cat: Cat) {
  this.cats.push(cat);
  return {"message":"success"};
  }

  findAll(): Cat[] {
  return this.cats;
  }

  findOne(name: String): Cat {
  let cat: Cat = this.cats.find(cat => cat.name == name);

  if (!cat) {
  throw new HttpException('Specified cat does not exist !', HttpStatus.BAD_REQUEST);
  } else {
  return cat;
  }

  }
}

テスト

cats.service に対するunitテストを書いた。
cat.service.spec.ts

import { Test } from '@nestjs/testing';;
import { CatsService } from './cats.service';
import { Cat } from './interfaces/cat.interface';
import { CreateCatDto } from './dto/create-cat.dto';

describe('CatsService', () => {
  let catsService: CatsService;

  beforeEach(async () => {
  const module = await Test.createTestingModule({
  components: [CatsService],
  }).compile();

  catsService = module.get<CatsService>(CatsService);
  });

  describe('create Test', () => {
  it('should return a success message', async () => {
  let cat: Cat = { "name": "Doraemon", "age": 12, "breed": "R-01FR001-MKII" };
  let result = { "message": "success" };

  expect(await catsService.create(cat)).toEqual(result);
  });

  });

  describe('findAll Test', () => {
  it('should return an array of cats', async () => {
  let cat1, cat2: Cat;

  cat1 = { "name": "Doraemon", "age": 12, "breed": "R-01FR001-MKII" };
  cat2 = { "name": "Doramichan", "age": 10, "breed": "R-01FR001-MKII" };
  let result = [cat1,cat2];

  await catsService.create(cat1);
  await catsService.create(cat2);

  expect(await catsService.findAll()).toEqual(result);
  });
  });

  describe('findOne Test', () => {

  it('should retrive a specified cat from cats array', async () => {
  let cat1, cat2: Cat;

  cat1 = { "name": "Doraemon", "age": 12, "breed": "R-01FR001-MKII" };
  cat2 = { "name": "Doramichan", "age": 10, "breed": "R-01FR001-MKII" };

  await catsService.create(cat1);
  await catsService.create(cat2);

  expect(await catsService.findOne("Doraemon")).toEqual(cat1);
  expect(await catsService.findOne("Doramichan")).toEqual(cat2);
  });

  it('should return an error object when incoming name does not exist in cats array', async () => {
  let cat1, cat2: Cat;

  cat1 = { "name": "Doraemon", "age": 12, "breed": "R-01FR001-MKII" };
  cat2 = { "name": "Doramichan", "age": 10, "breed": "R-01FR001-MKII" };

  await catsService.create(cat1);
  await catsService.create(cat2);

  expect( () => { catsService.findOne("mi-chan") }).toThrow();

  });

  });

});

expect(FUNCTION).toThrow(); expectの引数はfunctionなので、無名関数の形で渡す必要がある。
(参考:https://ajsblackbelt.wordpress.com/2014/05/18/jasmine-tests-expect-tothrow/)

最後に作った猫検索を試してみる。

## Doraemon 取得
 $curl http://localhost:3000/cats/Doraemon/
{"data":{"name":"Doraemon","age":12,"breed":"R-01FR001-MKII"}}

##  Doramichan 取得
$ curl http://localhost:3000curl http://localhost:3000/cats/Doramichan/
{"data":{"name":"Doramichan","age":10,"breed":"R-01FR001-MKII"}}

##  mi-chan 取得
$ curl http://localhost:3000/cats/mi-chan/
{"statusCode":400,"message":"Specified cat does not exist !"}

感想

nodejs + express から typescript に移行するのが中々難しいなと思っていたので、nestjsのようなフレームワークの存在は非常にありがたいです。
様々な機能があるみたいなので、今後も使ってみようと思います。

Hyperledger Composer Queryの中でORDER BY が使えない

Hyperledger Composerでクエリ

Hyperledger Composer ではSQLライクにブロックチェーンネットワークから特定のアセットや参加者を取得するためのクエリが準備できる。
(クエリを使う場合、channelに参加するpeerのDBをデフォルトのlevelDBからCouchDBに変更しておく必要がある。)
クエリは queries.qry の中に以下のように定義する。

query sampleQuery{
    description: "sampleQuery"
    statement:
        SELECT jp.org.acme.sampleAsset
    WHERE (_$parameter < fieldName)
              ORDER BY [fieldName ASC]
                    LIMIT 25 
}

ただし、しばらくORDER BYを使うとエラーとなっていた。

解決法

channelに参加しているpeerに紐付いたCouchDBのコンテナ内で以下のコマンドを叩けばOK.

$ curl -X POST --header 'Content-Type: application/json' --header 'Accept: application/json' -d ' {   "index": {     "fields": [       {         "data.<FIELD_NAME>": "<asc or desc>"       }     ]   },   "type": "json" }' 'http://localhost:5984/<CHANNEL_NAME>/_index'

参考

stackoverflow.com

Hyperledger Composer Node.js SDK から Fabric Network のクエリを実行する場合の定形

コード

var bizNetworkConnection = new BusinessNetworkConnection();
bizNetworkConnection.connect(connectionProfileName, businessNetworkIdentifier, user, password)
        .then(() => {
            // exec a query
            return bizNetworkConnection.query('specificQuery',{parameter:value});
        })
        .then((results) => {
            return Promise.all(results.map( (result) => {
                    // output or substitute
                    console.log(result.specificField);               
                }));
        })
        .catch((error) => {
            throw error;
        })
        .then(() => {
            return bizNetworkConnection.disconnect();
        });;

説明

bizNetworkConnection.query()Resource クラスのアレイを返し、要素の各フィールドにpropertyとしてアクセスできる。
AssetRegistryParticipantRegistry と勘違いして toJSON メソッドを使ったらエラーになったため覚書き。

注意点

composer-client@0.13.0の段階のもの。現在は変更あり。
Writing a Node.js application | Hyperledger Composer

Hyperledger ComposerのBNAファイルをundeployする

BNA(Business Network Archive)ファイルとは

ネットワークピアで実行されるスマートコントラクトのモデル定義(参加者、アセット、トランザクションなど)とトランザクションロジックの情報が書かれたもの。 Hyperledger Composer を使わない場合、GolangでChaincodeと呼ばれるスマートコントラクトを一から書かなければならないが、BNAはそれをラップして便利にした感じ(語弊あり)。
デプロイの仕方についてはまぁまぁ情報があるが、BNAを修正したい場合の情報は少ないのでメモ。

デプロイしたBNAファイルを消す

以下に書き方があるのだが、 card という概念は比較的最近のもので古いヴァージョンでは対応していない。
https://hyperledger.github.io/composer/reference/composer.network.undeploy.htmlhyperledger.github.io

自分の環境 ver 0.13.0 の場合以下でイケた。
composer network undeploy -n <businessNetworkName> -p <connectionProfileName> -i PeerAdmin -s randomString
ちなみにこの businessNetworkName には composer-src/package.json 内の"name" フィールドを書けば良い。

ver 0.13.0 のhelp

/usr/local/bin/composer network undeploy [options]

Options:
  --help                       Show help  [boolean]
  -v, --version                Show version number  [boolean]
  --businessNetworkName, -n    The business network name  [string] [required]
  --connectionProfileName, -p  The connection profile name  [string] [required]
  --enrollId, -i               The enrollment ID of the user  [string] [required]
  --enrollSecret, -s           The enrollment secret of the user  [string]

注意点

undeployしたネットワークを再度deployした場合その場でエラーは吐かないが、Node.js SDK APIからアクセスできず、businessNetworkNameを別物にするハメになった。

おそらく単にbnaを修正する目的の場合はpackage.json内の"version"を上げて行くのが良いと思われる。

参考

github.com

nest.jsが起動しない

症状

nest.jsが npm run start で起動しない。 initializeが完了という表示のみ。

解決法

typescriptts-node のヴァージョン下げたら動いた。

ts-node version to ~3.2.0
typescript version to ~2.3.4

参考

github.com

注意点

typescript@2.3.4enum型に数値しか設定できないので、思わぬコンパイルエラーが出たりする...