Hyperledger Fabricで個人情報をクエリする(RDB連携)

# Hyperledger Fabricのchaincodeで個人情報を検索します
Hyperledger Fabric(以下HF。HLFが正式な略称なのだろうか？)の`query`メソッドで個人情報を問い合わせます。前に投稿しましたが、HFの`State DB`に個人情報を持つのはよろしくないので、個人情報はRDB(PostgreSQL)から引き当てることにします。
// 最近はHFでも`chaincode`を`Smart contract`と呼ぶようになっている気がします。

# 環境について
動作環境については次の通りです。前回の投稿と(ry

Ubuntu 18.04.4 LTS
docker-compose 1.26.0
docker 19.03.11
HF 2.1.1
go 1.14.4
PostgreSQL(Dockerイメージ) 12.3(latest)

# 下準備
下準備をします。

### RDBのテーブル定義
新しく個人情報用のテーブルを定義しました。DB`asset`内に`owner`テーブルを定義しました。

```sql
asset=# \d owner
                     Table "public.owner"
 Column  |       Type        | Collation | Nullable | Default 
---------+-------------------+-----------+----------+---------
 id      | character(17)     |           | not null | 
 name    | character varying |           | not null | 
 country | character varying |           | not null | 
 city    | character varying |           | not null | 
 addr    | character varying |           | not null | 
Indexes:
    "owner_pkey" PRIMARY KEY, btree (id)

asset=#
```
名前と住所が入っているので危ないですね。idは車体番号(VIN)を利用して、同じくVINをKeyにしている`State DB`と紐付けることにします。

### 何はなくともダミーデータ
とりあえず1件だけ、マイカー「Mira Qiita」のオーナー情報を登録しました。

```sql
asset=# SELECT * FROM owner WHERE id = 'JMYMIRAGINO200302';
        id         |      name       | country |   city    |   addr    
-------------------+-----------------+---------+-----------+-----------
 JMYMIRAGINO200302 | ニ・キータ | 日本  | 東京都 | 足立区
(1 row)

asset=#
```
「ニ・キータ」さんの個人情報が入っています;-)
ニ・キータさんは足立区に住んでいるんですね。足立区は「修羅の国」と揶揄されることがあるのですが、同じ足立区民として心が痛いです…

# chaincodeを書く
さっそくコーディングしましょう。
### データ構造の定義
新しいデータを扱うので、新しいデータ構造を定義しました。

```go
// データ構造の定義
type Asset struct {
        Year     string `json:"year"`     // 初度登録年
        Month    string `json:"month"`    // 初度登録月
        Mileage  int    `json:"mileage"`  // 走行距離(km)
        Battery  int    `json:"battery"`  // バッテリーライフ(%)
        Location string `jasn:"location"` // 位置
}
type AssetWithOwner struct {
        Name    string  // 名前
        Country string  // 国
        City    string  // 都道府県
        Addr    string  // 市区町村
        Record  *Asset
}
```

構造体`AssetWithOwner`を追加しています。
### 実装しましょう

```go:asset.goから抜粋
func (s *SmartContract) QueryAssetWithOwner(ctx contractapi.TransactionContextInterface, key string) (*AssetWithOwner, error) {
        fmt.Println("QueryAssetWithOwner")

        assetAsBytes, err := ctx.GetStub().GetState(key)

        if err != nil {
                return nil, fmt.Errorf("Failed to read from world state. %s", err.Error())
        }

        if assetAsBytes == nil {
                return nil, fmt.Errorf("%s does not exist", key)
        }

        asset := new(Asset)
        _ = json.Unmarshal(assetAsBytes, asset)

        db, err := sql.Open("postgres", "host=pgsql port=5432 user=postgres password=secret dbname=asset sslmode=disable")
        defer db.Close()
        if err != nil {
                return nil, fmt.Errorf("sql.Open: %s", err.Error())
        }

        sql := "SELECT * FROM owner WHERE id = '" + key + "';"
        rows, err := db.Query(sql)
        if err != nil {
                return nil, fmt.Errorf("db.Query: %s", err.Error())
        }

        var id string
        awo := new(AssetWithOwner)
        for rows.Next() {
                rows.Scan(&id, &awo.Name, &awo.Country, &awo.City, &awo.Addr)
                awo.Record = asset
        }

        return awo, nil
}
```

新規メソッド`QueryAssetWithOwner`を実装しました。通常の`query`メソッドと同じく、`GetState`で`State DB`から情報をGETした後、RDBから個人情報を検索して`AssetWithOwner`構造体へ両方を合体セットしています。
SQL文は文字列を整形して作っています。VINはユニークなidなので、1件しか検索できない前提で書いてあります。2件以上見つかったら後勝ちになるでしょう:-(
当初、何も考えず文字列変数`Key`を使っていたのですが上手くいかず。`PostgresSQL`のコンテナに入って手打ちでSQL文を叩いても上手くいかず…なぜか問い合わせたKey文字列が小文字になってしまうんです:-(

```sql
asset=# SELECT * FROM owner WHERE id = JMYMIRAGINO200302;
ERROR:  column "jmymiragino200302" does not exist
LINE 1: SELECT * FROM owner WHERE id = JMYMIRAGINO200302;
                                       ^
asset=#
```
数時間ハマった挙げ句、シングルクォーテーションで挟めば良いことが分かりました。RDBなんて普段使わないから、そんなローカルルールは知らないよ！
「なぜ小文字になる！！」「なぜ小文字になる！」「なぜ小文字になるのよ(涙)」「ワケワカメ」(数時間経過)
論理的な仕様ではないと思うのですが。大昔のDOSも大文字と小文字を区別しなかったけど、レガシーなソリューションにはそういう名残があるのかしら:-|

# 動作確認
実際にQueryしてみましょう！

```json
# ./QueryAssetWithOwner.sh | jq
{
  "Name": "ニ・キータ",
  "Country": "日本",
  "City": "東京都",
  "Addr": "足立区",
  "Record": {
    "year": "2003",
    "month": "02",
    "mileage": 43871,
    "battery": 100,
    "Location": "ＱＩＩＴＡ東京販売"
  }
}
#
```
ちゃんとRDBから情報が取れています。前の投稿では、車がニ・キータさんのところにあっても`Location`は`Owner`でした。逆にオーナーがニ・キータさんでも`Location`が別の場所だと誰が真のオーナーかは分からなかったのです。
個人情報を加えることで、
「オーナーがニ・キータさんの車がＱＩＩＴＡ東京販売に存在する」ということが分かるようになります。
履歴情報も見てみましょう。

```json
# ./GetHistoryOfAsset.sh | jq
[
  {
    "TxId": "9b99263e5b75378af40e8466d28dd85654e3b8e427572a6ae038501a8dfebe97",
    "Timestamp": "2020-06-22 21:52:52 +0000 UTC",
    "IsDelete": false,
    "Record": {
      "year": "2003",
      "month": "02",
      "mileage": 43871,
      "battery": 100,
      "Location": "ＱＩＩＴＡ東京販売"
    }
  }
]
#
```
履歴情報には個人情報が残っていないですね。個人情報保護法対策もバッチリ(？)です。

# 最後に
ブロックチェーンの主キーをマスターにして、レガシーRDB含め色んな周辺システムと連携が取れそうです。ブロックチェーンをシステム間のハブの位置付けで利用するのも面白いと思いました。

ちなみに、ニ・キータさんは架空の人物です;-)
念の為。