MacにPostgreSQLインストールとSQL実行計画の確認

はじめに

最近は業務でDBMSをよく扱うことからデータベース関連の書籍を読んでいます。
SQL実践入門」は性能に目を向けた内容で面白かったです。

SQL実践入門──高速でわかりやすいクエリの書き方 (WEB+DB PRESS plus)

SQL実践入門──高速でわかりやすいクエリの書き方 (WEB+DB PRESS plus)

  • 作者:ミック
  • 発売日: 2015/04/11
  • メディア: 単行本(ソフトカバー)

本書籍ではSQLの実行計画を読み解くことでSQLをチューニングしています。 紹介されている内容を実際に試してみるため、MacBookに本書籍で説明に用いているPostgreSQLをインストールすることにしました (業務で扱っているのはHiRDBですが、基本的な考え方はどのDBMSでも通用するはずなのであまり気にしないことにします)。

PostgreSQLのインストール

Homebrewでインストールします。インストールしてない場合は次のコマンドでインストールしてください。

$ /usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"

続けてPostgreSQLをインストールして、'initdb'でデータベースクラスタ*1を作成します。

$ brew install postgresql
$ initdb /usr/local/var/postgres -E utf8                                                                                                                    
$ postgres --version
 postgres (PostgreSQL) 10.1

postgresサーバの起動

PGDATA環境変数でデータの物理的な配置を指定します。

~/.bash_profile

export PGDATA=/usr/local/var/postgres

'pg_ctl'でサーバを起動します。 -Dオプションを使用するとデータベースファイルの場所を指定することができます。 以下の例では省略しているため.bash_profileのPGDATA環境変数で指定した内容で動作します。

$ pg_ctl -l /usr/local/var/postgres/server.log start
waiting for server to start.... done
server started

データベースの作成

新規作成するデータベースの名称と所有者とするユーザを指定して'createdb'を実行します。

$ createdb example-db -O hiroki_sawano
$ psql -l
                                          List of databases
    Name    |     Owner     | Encoding |   Collate   |    Ctype    |        Access privileges        
------------+---------------+----------+-------------+-------------+---------------------------------
 example-db | hiroki_sawano | UTF8     | en_US.UTF-8 | en_US.UTF-8 | 
 postgres   | hiroki_sawano | UTF8     | en_US.UTF-8 | en_US.UTF-8 | 
 template0  | hiroki_sawano | UTF8     | en_US.UTF-8 | en_US.UTF-8 | =c/hiroki_sawano               +
            |               |          |             |             | hiroki_sawano=CTc/hiroki_sawano
 template1  | hiroki_sawano | UTF8     | en_US.UTF-8 | en_US.UTF-8 | =c/hiroki_sawano               +
            |               |          |             |             | hiroki_sawano=CTc/hiroki_sawano
(4 rows)

データベースへの接続とSQLの実行

'psql'(PostgreSQL対話的ターミナル)でデータベースに接続します。
試しに'numbers'表を作成し、複数の行を適当に挿入しました。

$ psql -U hiroki_sawano example-db
psql (10.1)
Type "help" for help.

example-db=# create table numbers(num integer primary key);
CREATE TABLE
example-db=# insert into numbers values(1);
INSERT 0 1
example-db=# insert into numbers values(3);
INSERT 0 1
example-db=# insert into numbers values(4);
INSERT 0 1
example-db=# insert into numbers values(7);
INSERT 0 1
example-db=# insert into numbers values(8);
INSERT 0 1
example-db=# insert into numbers values(9);
INSERT 0 1
example-db=# insert into numbers values(12);
INSERT 0 1
example-db=# select * from numbers;
 num
-----
   1
   3
   4
   7
   8
   9
  12
(7 rows)

SQL実行計画の確認

EXPLAINコマンドに続けてSQLを実行することで実行計画を確認できます。
WHERE句を省略したSELECT文ではSeq Scanで全行が順にスキャンされ、 WHERE句に主キー'num'への条件を付与したSELECT文ではインデクスが効いてIndex Only Scanされている様子が確認できます。

example-db=# explain
example-db-# select * from numbers;
                        QUERY PLAN                         
-----------------------------------------------------------
 Seq Scan on numbers  (cost=0.00..35.50 rows=2550 width=4)
(1 row)

example-db=# select * from numbers where num = 3;
 num 
-----
   3
(1 row)

example-db=# explain
example-db-# select * from numbers where num = 3;
                                   QUERY PLAN                                    
---------------------------------------------------------------------------------
 Index Only Scan using numbers_pkey on numbers  (cost=0.15..8.17 rows=1 width=4)
   Index Cond: (num = 3)
(2 rows)

参考

MacにPostgreSQLをインストール - Qiita
PostgreSQL: Documentation: 10: PostgreSQL 10.13 Documentation

*1:1つのサーバインスタンスで管理されるデータベースの集合

Synology DiskStation DS215jにSpring bootアプリをデプロイ

はじめに

自宅で常時稼働しているSynologyのDS215jに先日構築した'roomba_client'をデプロイし、
ネットワーク上の各デバイスからいつでも簡単にルンバを操作できるようにしました。

hiroki-sawano.hatenablog.com

環境

DS215jにSSH接続

DS215jにSSH接続するためには設定を変更する必要があります。
Control Panelで右上の「Advance Mode」を押下します。

f:id:hiroki-sawano:20180106043316p:plain

遷移先の画面で「Terminal & SNMP」を選択します。 f:id:hiroki-sawano:20180106043408p:plain

Enable SSH Serviceにチェックを入れ、Portにポート番号を指定します。 f:id:hiroki-sawano:20180106043430p:plain

以上で、ターミナル等からSSH接続が可能になります。

$ ssh <user_name>@<ds215j_ip_address> -p <port_number>

ビルド

DS215jにデプロイするSpring bootのFully Executable Jarを作成するため、
build.gradleに次の設定を追加し、gradle buildします。

build.gradle

springBoot {
  executable = true
}
$ gradle build

デプロイとサービス登録

DS215にビルドしたroomba_client.jarを配置します。

$ ls <deploy_destination>
roomba_client.jar

Upstartでサービスを管理するため/etc/initにroomba-client.confを作成します。
これでroomba-clientがdaemon化され、システム起動時に自動で立ち上がるようになりました。 /etc/init/roomba-client.conf

description "roomba_client daemon"
author "Hiroki Sawano <hiroki.sawano.2512@gmail.com>"
start on runlevel [2345]
stop on runlevel [016]
respawn # attempt service restart if stops abruptly
exec <java_installation_path>/java -jar <deploy_destination>/roomba_client.jar

動作確認

roomba-clientを起動します。 ここでは手動で起動しています。

$ initctl list | grep roomba-client
roomba-client stop/waiting
$ sudo start roomba-client
roomba-client start/running, process 11518
$ sudo tail -f /var/log/upstart/roomba-client.log
…
…jp.gr.java_conf.hs.roomba.client.Roomba  : Connected to Roomba
…
…j.g.j.hs.roomba.client.RoombaClient      : Started RoombaClient in 67.775 seconds (JVM running for 71.916)

Webブラウザからアプリケーション(<ds215j_ip_address>:8080)にアクセスすると正常起動していることが確認できます。

f:id:hiroki-sawano:20171203174800p:plain

Travis-CIとCoverallsでJavaアプリケーションの単体テスト

はじめに

今更ながら継続的インテグレーション(CI)環境を整え、自動単体テストのための仕掛けを用意しましたので、 本エントリに実施した事項をまとめます。

以降では、先日紹介したroomba_clientプロジェクトを対象に説明します。

hiroki-sawano.hatenablog.com

テストコードの実装

次のようなテストコードを用意します。 今回は単にテストを動かすことが目的なので詳細は説明しませんが、 RoombaControllerTests::index()では"/"にアクセスした際のページ遷移先や、Viewに渡すデータが期待通りに設定されていることを確認しています。 RoombaControllerTests::addCommand()では"/invoke?add="へのPOST要求でDBへの行挿入のメソッドが呼び出されていることを確認しています。

@RunWith(SpringRunner.class)
@WebMvcTest(RoombaController.class)
public class RoombaControllerTests {
    @Autowired
    private MockMvc mvc;
    @MockBean
    private MyDataRepository repo;
    @MockBean
    private Settings settings;
    @MockBean
    private Roomba roomba;

    private Command c1 = new Command("c1", "seq1");
    private Command c2 = new Command("c2", "seq2");

    private Map<String, String> selectItems = new HashMap<String, String>() {
        private static final long serialVersionUID = 1L;
        {
            put("foo", "fooVal");
            put("bar", "barVal");
        }
    };

    @Before
    public void before() {
        given(this.roomba.connect("192.168.0.20", 9001)).willReturn(true);
        given(this.roomba.send("128")).willReturn(true);
        given(this.roomba.disconnect()).willReturn(true);
    }

    @Test
    public void index() throws Exception {
        List<Command> commandList = new ArrayList<Command>();
        commandList.add(c1);
        commandList.add(c2);
        given(this.settings.getSequences()).willReturn(selectItems);
        given(this.repo.findAll()).willReturn(commandList);
        MvcResult mvcResult = this.mvc.perform(get("/")).andDo(print()).andExpect(status().isOk())
                .andExpect(view().name("index")).andExpect(model().attributeExists("userInput"))
                .andExpect(model().attribute("dataTable", commandList)).andReturn();
        ModelMap modelMap = mvcResult.getModelAndView().getModelMap();
        Object uio = modelMap.get("userInput");
        assertThat(uio, is(not(nullValue())));
        assertThat(uio, is(instanceOf(UserInput.class)));
        UserInput ui = (UserInput) uio;
        assertThat(ui.getSelectItems(), is(selectItems));
    }

    @Test
    public void addCommand() throws Exception {
        this.mvc.perform(post("/invoke?add=").param("name", "command_name").param("selectedSequence", "")
                .param("arbitrarySequence", "128 131")).andDo(print()).andExpect(redirectedUrl("/"))
                .andExpect(model().hasNoErrors());
        verify(repo).saveAndFlush(Mockito.any(Command.class));
    }

Travis-CIの設定

続けてTravis-CI(https://travis-ci.org/)の設定です。
試験対象プロジェクトのリポジトリと連携した後、 .travis.ymlをGitリポジトリのルートに配置します。 今回はSpring bootアプリなのでlanguageにはjavaを設定し、jdkを指定します。

.travis.yml

language: java
jdk:
 - oraclejdk8

以上でリモートリポジトリにPushする度に自動的にビルド(テスト含む)が実行されるようになりました。 f:id:hiroki-sawano:20171224165144p:plain

Coverallsプラグインの追加

Travis-CIで実行されたテストのカバレッジレポートを確認できるようにするため、Coveralls(https://coveralls.io)と連携します。 カバレッジレポートはJacocoプラグインで出力します。

まず、build.gradleにjacocoとcoveralls-gradle-pluginを追加します。 jacocoTestReportにはレポートの出力形式を指定できますが、どうやらCoverallsはXML形式のレポートが必要なようなので xml.enabled = trueを設定します。

build.gradle

buildscript {
    ext {
        springBootVersion = '1.5.7.RELEASE'
    }
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
        classpath("org.kt3k.gradle.plugin:coveralls-gradle-plugin:2.6.3")
    }
}

apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'org.springframework.boot'
apply plugin: 'jacoco'
apply plugin: 'com.github.kt3k.coveralls'

jacocoTestReport {
    reports {
        xml.enabled = true //coveralls plugin depends on xml format report
        html.enabled = true
    }
}

続けて、Travis-CIでビルド後にCoverallsにカバレッジ情報を転送するための設定です。
.travis.ymlのafter_successに次の設定を追記します。

.travis.yml

language: java
jdk:
 - oraclejdk8
after_success:
 - ./gradlew test jacocoTestReport coveralls

カバレッジレポートの確認

以上の設定により、GitにPushするとCoverallsで単体テストの結果が確認できるようになります。 f:id:hiroki-sawano:20171224162251p:plain

f:id:hiroki-sawano:20171224162508p:plain

f:id:hiroki-sawano:20171224162525p:plain

GithubのREADMEにバッジを追加

最後にGitのREADME.mdにビルド結果とカバレッジ率を示すバッジを貼り付ければ完成です。 f:id:hiroki-sawano:20171224170943p:plain f:id:hiroki-sawano:20171224162308p:plain f:id:hiroki-sawano:20171224161953p:plain

[Roomba Hack] Spring boot Webアプリからルンバ800(+RooWiFi)を操る

はじめに

ルンバ800をハックしました。 Webアプリからルンバと通信することで掃除をさせたり、音楽を流したり、スケジュールを変更したり、できるようにしました。

準備

ルンバと無線通信するため「RooWiFiルンバ用無線LANモジュール(RB-Roo-01)」をルンバに搭載しました。
http://www.robotshop.com/jp/ja/roowifi-wifi-module-roomba-v2.html

インストール手順はこちらを参照しました。
http://www.roowifi.com/wp-content/uploads/RwRemote_User_Guide_v2_rev15.pdf

ルンバとTCP/IP通信

ルンバを操作するためのインタフェースはThe Roomba Open Interface(OI)として公開されています。 こちらの仕様に記載のコマンドをソケット通信でルンバに送り込めばルンバを好きなように操ることができます。
http://www.irobot.lv/uploaded_files/File/iRobot_Roomba_500_Open_Interface_Spec.pdf

Spring bootアプリの構築

ルンバと通信するためのWebアプリケーション(roomba_client)を構築しました。
日々改良中ですが、形になってきたので公開します。 https://github.com/hiroki-sawano/roomba_clientgithub.com

roomba_clientの起動

アプリケーションを起動し、ブラウザよりアクセスすると次の画面が表示されます。 f:id:hiroki-sawano:20171203174800p:plain

アプリケーションの立ち上げ時にはルンバへの接続が成功したことを示すメッセージが出力されます。
roomba_client/event.log

2017-12-07 05:12:58.522 ... hs.roomba.client.RoombaController : Connected to Roomba

複数コマンドの連続実行

任意のコマンド名と前述したOIの仕様に従ったシリアルシーケンスを入力し、'ADD COMMAND'ボタンでデータテーブルにコマンドを実行順に追加していきます。
シリアルシーケンスは直接入力(Arbitrary Sequence)でもかまいませんが、いくつかセレクトボックス(Selectable Sequence)で選択可能としています。
こちらの例では、①セーフモードへの移行、②曲の登録、③曲の再生を実行し、ルンバから音楽を再生しています。
f:id:hiroki-sawano:20171207052956g:plain

ログ情報は次の通りです。
roomba_client/event.log

2017-12-07 05:16:42.356 ... hs.roomba.client.RoombaController : 
Adding new command : name=start serialSequence=128 131
2017-12-07 05:16:48.832 ... hs.roomba.client.RoombaController : 
Adding new command : name=song serialSequence=140 0 7 76 16 76 32 79 16 79 16 77 16 74 16 72 32
2017-12-07 05:16:55.268  ... hs.roomba.client.RoombaController : 
Adding new command : name=play serialSequence=141 0
2017-12-07 05:16:56.944  ... hs.roomba.client.RoombaController : 
Sending serial sequence : 128 131
2017-12-07 05:16:57.959  ... hs.roomba.client.RoombaController : 
Sending serial sequence : 140 0 7 76 16 76 32 79 16 79 16 77 16 74 16 72 32
2017-12-07 05:16:58.965  ... hs.roomba.client.RoombaController : 
Sending serial sequence : 141 0

単一コマンドの即時実行

コマンドを入力後に'EXECUTE COMMAND'を押下するとデータテーブルにコマンドを登録せずに即実行します。 この例では、③曲の再生を指示するシーケンスを直接入力してルンバに送信しています。
f:id:hiroki-sawano:20171207054704g:plain

roomba_client/event.log

2017-12-07 05:17:07.290 ... hs.roomba.client.RoombaController : 
Sending serial sequence : 141 0

その他機能

データテーブルに一度登録したコマンドは実行、編集、削除が可能です。

環境設定

使用する際にはapplication.ymlにルンバのIP(roomba.ip)とポート番号(roomba.port)を最低限設定することが必要です。
必要に応じて画面中のセレクトボックスで選択可能なシリアルシーケンス(roomba.sequences)を登録することもできます。
application.yml

roomba:
  ip: 192.168.0.19
  port: 9001
  sequences:
    start_and_safe: 128 131
    start_and_full: 128 132
    clean: 135
    max: 136
    spot: 134
    seek_dock: 143
    song: 140 0 7 76 16 76 32 79 16 79 16 77 16 74 16 72 32
    play: 141 0
    end: 128
    daily_schedule: 167 62 0 0 12 0 12 0 12 0 12 0 12 0 0 0

さいごに

今でも少し遊ぶ分には十分ですが実際に日常で役立つような使い方ができるように、組み立てたシーケンスの保存やコマンドの実行間隔の指定など今後改良を加えていきます。

ブロックチェーン基盤 Hyperledger Fabric v1.0 アプリケーションの実装

はじめに

今回はHyperledger Fabricのサンプルアプリケーションを動かしてみます。本エントリは次のページで紹介されている手順に従って進めていきます。

Writing Your First Application — hyperledger-fabricdocs master documentation

実行環境

  • Max OS High Sierra Version 10.13
  • Docker version 17.09.0-ce
  • Docker Compose version 1.16.1
  • Go version 1.9
  • Node.js version 8.6

Hyperledger Fabricのダウンロード

次のコマンドを実行します。

$ mkdir ~/fabric
$ cd ~/fabric
$ curl -sSL https://goo.gl/Q3YRTi | bash

これによりブロックチェーンネットワークを構築するために必要な次のバイナリを入手します。

  • cryptogen
  • configtxgen
  • configtxlator
  • peer
$ ls bin
configtxgen     configtxlator       cryptogen       get-docker-images.sh    orderer         peer

スクリプトはHyperledger FabricのdockerイメージをDocker Hubよりダウンロードします。

$ docker images
REPOSITORY                     TAG                    IMAGE ID            CREATED             SIZE
hyperledger/fabric-ca          latest                 2736904862db        10 days ago         218MB
hyperledger/fabric-ca          x86_64-1.1.0-preview   2736904862db        10 days ago         218MB
hyperledger/fabric-tools       latest                 c584c20ac82b        10 days ago         1.42GB
hyperledger/fabric-tools       x86_64-1.1.0-preview   c584c20ac82b        10 days ago         1.42GB
hyperledger/fabric-couchdb     latest                 5b8a15e6e972        10 days ago         1.57GB
hyperledger/fabric-couchdb     x86_64-1.1.0-preview   5b8a15e6e972        10 days ago         1.57GB
hyperledger/fabric-kafka       latest                 cf09c5534ef9        10 days ago         1.37GB
hyperledger/fabric-kafka       x86_64-1.1.0-preview   cf09c5534ef9        10 days ago         1.37GB
hyperledger/fabric-zookeeper   latest                 ac127485fdc7        10 days ago         1.37GB
hyperledger/fabric-zookeeper   x86_64-1.1.0-preview   ac127485fdc7        10 days ago         1.37GB
hyperledger/fabric-orderer     latest                 2fccc91736df        10 days ago         159MB
hyperledger/fabric-orderer     x86_64-1.1.0-preview   2fccc91736df        10 days ago         159MB
hyperledger/fabric-peer        latest                 337f3d90b452        10 days ago         165MB
hyperledger/fabric-peer        x86_64-1.1.0-preview   337f3d90b452        10 days ago         165MB
hyperledger/fabric-javaenv     latest                 cd459b218651        10 days ago         1.49GB
hyperledger/fabric-javaenv     x86_64-1.1.0-preview   cd459b218651        10 days ago         1.49GB
hyperledger/fabric-ccenv       latest                 82489d1c11e8        10 days ago         1.35GB
hyperledger/fabric-ccenv       x86_64-1.1.0-preview   82489d1c11e8        10 days ago         1.35GB

開発環境のセットアップ

Hyperledger Fabricのサンプルをgitでcloneします。

$ git clone https://github.com/hyperledger/fabric-samples.git
$ cd fabric-samples

以降では次のfabcarディレクトリで作業します。

$ cd fabcar
$ ls
enrollAdmin.js  invoke.js   package.json    query.js    registerUser.js startFabric.sh

念のためdockerコンテナとネットワークを削除しておきます。

$ docker rm -f $(docker ps -aq)
$ docker network prune

すでに以降の作業を実施済みの場合には次のコマンドでfabcarチェインコード用のdockerイメージも削除します。

$ docker rmi dev-peer0.org1.example.com-fabcar-1.0-5c906e402ed29f20260ae42283216aa75549c571e2e380f3615826365d8269ba

アプリケーションの実行に必要なライブラリをインストールするため次のコマンドを実行します。 以降ではCAサーバにアクセスするためにnode_modules/fabric-ca-client、 peerやordering serviceと対話するためにnode_modules/fabric-clientを使用していきます。

$ npm install

続けて次のスクリプトを実行することでネットワークを起動します。このコマンドはFabricの各コンテナを用意します。 fabcarネットワークの詳細はこちらに説明があります。
Understanding the Fabcar Network — hyperledger-fabricdocs master documentation

$ ./startFabric.sh
$ docker images
REPOSITORY                                                                                               TAG                    IMAGE ID            CREATED             SIZE
dev-peer0.org1.example.com-fabcar-1.0-5c906e402ed29f20260ae42283216aa75549c571e2e380f3615826365d8269ba   latest                 bf75bac8b3f9        2 minutes ago       151MB
...
$ docker ps
CONTAINER ID        IMAGE                                                                                                    COMMAND                  CREATED             STATUS              PORTS                                            NAMES
b4b2e2be6dc7        dev-peer0.org1.example.com-fabcar-1.0-5c906e402ed29f20260ae42283216aa75549c571e2e380f3615826365d8269ba   "chaincode -peer.a..."   4 minutes ago       Up 4 minutes                                                         dev-peer0.org1.example.com-fabcar-1.0
a4f01226a050        hyperledger/fabric-tools                                                                                 "/bin/bash"              6 minutes ago       Up 6 minutes                                                         cli
dbce2a81cb36        hyperledger/fabric-peer                                                                                  "peer node start"        7 minutes ago       Up 7 minutes        0.0.0.0:7051->7051/tcp, 0.0.0.0:7053->7053/tcp   peer0.org1.example.com
c83fd30320e2        hyperledger/fabric-couchdb                                                                               "tini -- /docker-e..."   7 minutes ago       Up 7 minutes        4369/tcp, 9100/tcp, 0.0.0.0:5984->5984/tcp       couchdb
0dd907f8cce3        hyperledger/fabric-orderer                                                                               "orderer"                7 minutes ago       Up 7 minutes        0.0.0.0:7050->7050/tcp                           orderer.example.com
86ef00f4705d        hyperledger/fabric-ca                                                                                    "sh -c 'fabric-ca-..."   7 minutes ago       Up 7 minutes        0.0.0.0:7054->7054/tcp                           ca.example.com

ユーザの登録

前述のネットワーク立ち上げ時にadminユーザが認証局(CA)に登録されています。
enrollAdmin.jsでadminの電子証明書(eCert)を取得します。 このプログラムは公開鍵と秘密鍵をhfc-key-storeに作成し、証明書署名要求(CSR)で公開鍵証明書を発行します。
続けて、registerUser.jsではadminユーザでCAと通信しLedgerにアクセスするuser1ユーザを登録します。このプログラムもまた、CSRを実行し証明書と鍵をhfc-key-storeに出力します。

$ node enrollAdmin.js
$ node registerUser.js
$ ls hfc-key-store/
admin                                   
...-pub
...-priv
user1
...-pub
...-priv

Ledgerの参照

query.jsを使用し、fabcarチェインコードのqueryAllCarsを呼び出します。

fabric-samples/fabcar/query.js

const request = {
    chaincodeId: 'fabcar',
    fcn: 'queryAllCars',
    args: ['']
};
$ node query.js
Store path:.../fabric-samples/fabcar/hfc-key-store
Successfully loaded user1 from persistence
Query has completed, checking results
Response is  
[{"Key":"CAR0", "Record":{"colour":"blue","make":"Toyota","model":"Prius","owner":"Tomoko"}},
 {"Key":"CAR1", "Record":{"colour":"red","make":"Ford","model":"Mustang","owner":"Brad"}},
 {"Key":"CAR2", "Record":{"colour":"green","make":"Hyundai","model":"Tucson","owner":"Jin Soo"}},
 {"Key":"CAR3", "Record":{"colour":"yellow","make":"Volkswagen","model":"Passat","owner":"Max"}},
 {"Key":"CAR4", "Record":{"colour":"black","make":"Tesla","model":"S","owner":"Adriana"}},
 {"Key":"CAR5", "Record":{"colour":"purple","make":"Peugeot","model":"205","owner":"Michel"}},
 {"Key":"CAR6", "Record":{"colour":"white","make":"Chery","model":"S22L","owner":"Aarav"}},
 {"Key":"CAR7", "Record":{"colour":"violet","make":"Fiat","model":"Punto","owner":"Pari"}},
 {"Key":"CAR8", "Record":{"colour":"indigo","make":"Tata","model":"Nano","owner":"Valeria"}},
 {"Key":"CAR9", "Record":{"colour":"brown","make":"Holden","model":"Barina","owner":"Shotaro"}}]

実行したチェインコードの関数は次の通りです。

fabric-samples/chaincode/fabcar/fabcar.go

func (s *SmartContract) queryAllCars(APIstub shim.ChaincodeStubInterface) sc.Response {

    startKey := "CAR0"
    endKey := "CAR999"

    resultsIterator, err := APIstub.GetStateByRange(startKey, endKey)

LedgerにアクセスするAPIの仕様はこちらにあります。 godoc.org github.com

Ledgerの更新

fabcarチェインコードのcreateCarで新たなCARをLedgerに追加します。

fabric-samples/fabcar/invoke.js

var request = {
    chaincodeId: 'fabcar',
    fcn: 'createCar',
    args: ['CAR10', 'Chevy', 'Volt', 'Red', 'Nick'],
    chainId: 'mychannel',
    txId: tx_id
};

fabric-samples/chaincode/fabcar/fabcar.go

func (s *SmartContract) createCar(APIstub shim.ChaincodeStubInterface, args []string) sc.Response {

    if len(args) != 5 {
        return shim.Error("Incorrect number of arguments. Expecting 5")
    }

    var car = Car{Make: args[1], Model: args[2], Colour: args[3], Owner: args[4]}

    carAsBytes, _ := json.Marshal(car)
    APIstub.PutState(args[0], carAsBytes)

    return shim.Success(nil)
}
$ node invoke.js 
Store path:.../fabric-samples/fabcar/hfc-key-store
Successfully loaded user1 from persistence
Assigning transaction_id:  97ae720f071443b8f73b5ab1e8ed1a87d8a04ba3904479f24cdb146486edcbfa
Transaction proposal was good
Successfully sent Proposal and received ProposalResponse: Status - 200, message - "OK"
info: [EventHub.js]: _connect - options {}
The transaction has been committed on peer localhost:7053
Send transaction promise and event listener promise have completed
Successfully sent transaction to the orderer.
Successfully committed the change to the ledger by the peer

最後にCAR10が正しく追加されているかを確認します。 fabcarのqueryCarに引数CAR10を渡し、query.jsを実行します。

query.js

const request = {
    chaincodeId: 'fabcar',
    fcn: 'queryCar',
    args: ['CAR10']
};

fabric-samples/chaincode/fabcar/fabcar.go

func (s *SmartContract) queryCar(APIstub shim.ChaincodeStubInterface, args []string) sc.Response {

    if len(args) != 1 {
        return shim.Error("Incorrect number of arguments. Expecting 1")
    }

    carAsBytes, _ := APIstub.GetState(args[0])
    return shim.Success(carAsBytes)
}
$ node query.js 
Store path:.../fabric-samples/fabcar/hfc-key-store
Successfully loaded user1 from persistence
Query has completed, checking results
Response is  {"colour":"Red","make":"Chevy","model":"Volt","owner":"Nick"}

期待通りの結果が得られました。

さいごに

本エントリではサンプルアプリケーションfabcarを動かしてみました。 Ledgerにアクセスするアプリケーションの実装イメージが少し湧いた気がします。
次回はまだ未定ですが、JavaアプリケーションからHyperledger Fabric SDKを使用してみたいと考えてます。

vimからmake

はじめに

vimを開発環境として使う場合に標準搭載されているmakeコマンドを使用すると便利です。

サンプルプログラムの作成

次の通りCのプログラムとMakefileを用意します。

$ ls
Makefile    hello.c

hello.c

#include<stdio.h>

int main(){
    printf("Hello!\n");

    return 0;
}

Makefileにはbuildとrunのタスクを定義します。

Makefile

build :
         cc hello.c
run :
        ./a.out

vimrcにショートカットを設定

vimからMakefileに定義したタスクを簡単に実行するため、 ショートカットキーを設定しておきます。 これにより、vim上ではコマンドモード、挿入モードに関わらず F5でファイルの保存と:make buildの実行、F6でファイルの保存と:make runの実行ができるようになります。
makeの実行結果はQuickfix Listで確認できますが、毎度:copenするのは煩わしいので makeの後に自動でQuickfix Listが開くようにau[tocmd]も次の通り設定しておきます。

~/.vimrc

nnoremap <F5> :w<CR>:make build<CR>
inoremap <F5> <Esc>:w<CR>:make build<CR>
nnoremap <F6> :w<CR>:make run<CR>
inoremap <F6> <Esc>:w<CR>:make run<CR>
au QuickfixCmdPost make copen

動作確認

hello.cの最終行から閉じ括弧をわざと削除しておきます。
f:id:hiroki-sawano:20171117023708p:plain

F5でmake buildを実行します。
f:id:hiroki-sawano:20171117022533p:plain

実行結果はQuickfix Listで確認できます。
f:id:hiroki-sawano:20171117023755p:plain

コードを正しく修正し、再度make buildを行います。
f:id:hiroki-sawano:20171117023850p:plain

コンパイルが正常終了しました。
f:id:hiroki-sawano:20171117024015p:plain

F6でmake runを実行します。
f:id:hiroki-sawano:20171117023856p:plain

正常にプログラムが動作しました。
f:id:hiroki-sawano:20171117022839p:plain

ブロックチェーン基盤 Hyperledger Fabric v1.0 アーキテクチャ概説

はじめに

オープンソースブロックチェーン基盤であるHyperledger Fabric(v1.0)を最近学習中なので理解した範囲で本エントリーにまとめます。 Hyperledger Fabricのアーキテクチャトランザクションの流れを理解することや、 アプリケーションの実装方法を学ぶことが目的なので、ブロックチェーンそのものがどのような場面で適用できるかなどは述べる気はないです。そういった話はここを読むといいかもしれません。
Introduction — hyperledger-fabricdocs master documentation

システム・アーキテクチャ

以降ではここに書いてある説明を参考にしています。
http://hyperledger-fabric.readthedocs.io/en/latest/arch-deep-dive.html

まず、Hyperledger Fabricでは次に示すように複数のノードが協調して元帳(Ledger)の管理を実現しています。
f:id:hiroki-sawano:20171029200149p:plain

  • Application(SDK)
    トランザクションの実行を要求します。 ClientはNode.jsやJavaで実装できます(今後PythonやGo用のSDKも提供される予定)。
  • Membership
    PeerUserの登録・承認をすることで、許可されたノードだけがブロックチェーンネットワークに参加できます。 不特定多数が参加するBitcoinのようなブロックチェーンには存在しない、Enterprise用に実装されているHyperledger Fabricならではの機能です。
  • Peer
    すべてのPeerが同じデータをPeerLedgerに保持しています。 事前にPeerにデプロイしたChaincode(スマートコントラクト)によって、同トランザクション中に各PeerLedgerが更新されます。 ChaincodeはGoやJavaで実装できます。
  • Ordering-service
    Peerトランザクションの実行を承認した後、Ordering-serviceトランザクションの順序を整列します。続けて、すべてのPeerに対して更新情報を伝達します。

トランザクションの種類

公式では次の4種類のトランザクションについて触れられています。

  • Deploy transaction
    新しいChaincodeをデプロイします。Deploy transactionの成功にともなって、Chaincodeブロックチェーン上にインストールされます。
  • Invoke transaction
    事前にデプロイされたChaincodeが提供するオペレーションを実行します。 Invoke transactionの実行は、指定するオペレーションによってLedgerの更新や参照結果の返却を伴います。
  • Query transaction(included in v1)
    v1.0で追加となった読み取り専用トランザクションです。
  • Cross-chaincode transaction(post-v1 feature)
    複数のChaincodeを使用したトランザクションですが、v1.0では未実装です。

トランザクション承認の基本フロー

前述のInvoke Transactionを例に絵を書いてみました(大体あってるはずです..)。Deploy transactionの場合には、tx.txPayloadChaincodeソースコード等が設定される点で違いがありますが流れは基本的に同じようです。Query transactionは後日確認します。

f:id:hiroki-sawano:20171029183421p:plain

要約すると次の流れになります。

  1. [Client]トランザクションの提案
    PROPOSEメッセージでどのChaincodeのメソッドを実行したいのかをEndorsing Peer(承認者となるPeer)に伝える。

  2. [Peer]トランザクションのシミュレーション
    Endorsing Peerは自身のPeerLedger上でトランザクションを仮実行し、read version dependencies(readset)とstate updates(writeset)を取得する(おそらく下図のようなイメージです...)。

f:id:hiroki-sawano:20171029210749p:plain

  1. [Peer]トランザクションの承認
    Peerは内部的にtran-proposalを自身の承認ロジックに渡す。デフォルトでは単にtran-proposalに署名するだけだが、 任意の機能を実行することも可能(e.g. txtran-proposalを入力としたレガシーシステムとの連携によるトランザクション承認)。

  2. [Client]承認されたトランザクションOrdering-serviceに送信
    TRANSACTION-ENDORSEDメッセージをすべて受信した後にOrdering-servicebroadcast(blob)を実行する。 Ordering-serviceに渡すblobは署名されたTRANSACTION-ENDORSEDメッセージのコレクションであり、endorsementとも呼ばれる。 endorsementが集まらなかった場合にはトランザクション提案を取り下げる(後でリトライすることもできるらしい)。

  3. [Ordering-service]トランザクションを整列してPeerに更新情報を転送
    トランザクションを整列して、Peerdeliverイベントを実行する。 多くの場合には効率的に処理するため、Clientから送信された各々のトランザクション(blobs)を出力せずにグルーピングし、1つのdeliverイベントにblocksを設定するとのこと。

  4. [Peer]承認内容の検証
    今回処理するトランザクションseqno未満のseqnoで処理される状態の更新が完了した後に、 blob.endorsementが妥当であるかをChaincode(blob.tran-proposal.chaincodeID)のポリシーに従って検証する。

  5. [Peer]MVCC
    read version dependencies(blob.endorsement.tran-proposal.readset)が破られていないかを検証する。 つまり、承認時のLedgerの状態が他の更新系トランザクションによって変わっていないか(Anomalyの発生)を確認する必要があるということだと思う(i.e. MultiVersion Concurrency Controlを用いたSnapshot Isolationの実装(トランザクション分離レベルではいわゆるRead Commited))。 (MVCCについては理解が浅いので別途確認します.. )

  6. [Peer]トランザクションのコミット
    トランザクションをコミットし、blob.endorsement.tran-proposal.writesetブロックチェーンの状態に反映する。

さいごに

本エントリーでは、Hyperledger Fabric v1.0の構成要素やトランザクション実行の流れを説明しました。 次回は実際に開発環境を準備し、ブロックチェーンを活用したアプリケーションの実装方法を確認していきます。