Cloud FunctionsでNEMのブロック承認を監視してプッシュ通知を送るアプリを作った

この記事は nem Advent Calendar 2018 の18日目の記事です。

担当の[りゅーた](https://twitter.com/ryuta461)です。

NEMを使ったアプリ([RaccoonWalletのiOS版](https://itunes.apple.com/jp/app/raccoonwallet/id1437034062?mt=8)、[PWA版](https://ryuta46.github.io/raccoonwallet-web-app/#/top?tab=home) とか)、[nem-kotlin](https://github.com/ryuta46/nem-kotlin)、[nem-swift](https://github.com/ryuta46/nem-swift)などのライブラリの開発を行ってます。

Advent Calendar 、せっかくなので何か作ってみようぜと思いたち、スマホアプリを一つ作ってみました。

以前チラ見せしたやつです。

<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">NEM のAdvent Calendar 向けのネタアプリ。<br>Cloud Schedular と Cloud Functions で NEM の承認済みブロックを定期的に取得して、指定アドレス（または指定モザイク）のトランザクションがあれば端末にプッシュ通知を送る。（Flutter 製）<br>コア部分は作った。 <a href="https://t.co/FHj0rKxDDU">pic.twitter.com/FHj0rKxDDU</a></p>&mdash; りゅーた@田舎フリーランス (@ryuta461) <a href="https://twitter.com/ryuta461/status/1066450355225976832?ref_src=twsrc%5Etfw">2018年11月24日</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>


Firebase、Flutter、NEM っていう最近自分が興味あるものを組み合わせたアプリです。
まぁこんなことできるよっていうデモアプリみたいなものだと思っていただければ。

デモアプリと言いつつせっかく作ったのでリリースしました。Android/iOS 両対応で作ったんですが([Flutter](https://flutter.io/) という最近 1.0 になったクロスプラットフォーム環境を使ってます)、残念ながら iOS の方は審査が間に合わずリリースできてません。

<a href='https://play.google.com/store/apps/details?id=com.ttechsoft.nemblockmonitorapp&pcampaignid=MKT-Other-global-all-co-prtnr-py-PartBadge-Mar2515-1'><img alt='Google Play で手に入れよう' src='https://play.google.com/intl/ja/badges/images/generic/ja_badge_web_generic.png' width='200' /></a>

## このアプリは何？

* 指定したアドレスに対してXEM、モザイクが送信されてきたらスマホ端末にプッシュ通知を送る
* 指定したモザイクの送信を確認したらスマホ端末にプッシュ通知を送る

メインの機能としてはほぼそれだけです。

### 使い方

アプリ右下の + をタップすると監視対象のアドレスかモザイクを追加できます。

<img width="375" alt="IMG_0058.PNG" src="https://qiita-image-store.s3.amazonaws.com/0/160444/b049edec-40ae-0588-bade-e5e0f4eed3fb.png">

監視対象に追加したアドレスに関係するトランザクションが承認されたらプッシュ通知が届きます。

<img width="375" alt="IMG_0059.PNG" src="https://qiita-image-store.s3.amazonaws.com/0/160444/27a92f23-0da4-9f64-d14f-4074c814acd6.png">

アドレスにはラベルが設定できます。通知には指定したラベルが表示されます。

モザイクを監視対象に指定すると、そのモザイクが移動した時に通知が来るようになります。

<img width="375" alt="IMG_0057.PNG" src="https://qiita-image-store.s3.amazonaws.com/0/160444/51575293-b50a-56ea-7e91-4cb7b0f6bf86.png">


### 使いみち

アドレス監視の方は、寄付アドレスへの入金とか投げ銭があったことのお知らせとして。
モザイク監視の方は、モザイクと紐付いたサービスを作っている方が、自分の発行したモザイクがブロックに刻まれていくのを見てモチベーションを上げるためとか。

## 実装面

アプリ全体の構成です。[shohei さんがとても良い図を書いてくれた](https://qiita.com/hukusuke1007/items/6b0f831bb42ee33b8528) ので、それに倣って構成図を書いてみました。


![for_blog.001.png](https://qiita-image-store.s3.amazonaws.com/0/160444/81084118-5212-f774-1b99-9f27a4551241.png)



バックエンドの処理の主体は Cloud Functions for Firebase (以下 Functions) が行っています。

Functions 内で NEM のブロックチェーンの情報を取得し、ユーザが監視対象にしているアドレス、モザイクの情報があれば Firebase Cloud Messaging (以下 FCM) 経由でユーザ端末にプッシュ通知を送るという構成です。

Functions は node.js でバックエンドの処理を書くことができ、とても便利な機構なのですが、Functions 自体には定期的に処理を実行するという機能がありません。

そこで、先日 Beta版がリリースされた [Cloud Sheduler](https://cloud.google.com/scheduler/) を使います。

Cloud Sheduler は定期的にユーザが指定した動作を実行する機構です。これを使って、同じく GCPファミリーの Cloud Pub/Sub に1分おきにメッセージを配信し、それをトリガにして Functions が実行される、という流れになっています。


フロントエンドは Flutter で書いていて、コードの殆どは Android と iOS で共通のものを使っています。アプリの主な役割は、ユーザごとに監視対象を管理している Firestore へのデータの書き込みと、トランザクションの詳細情報を NEM のブロックチェーンから探してきて表示する、という部分になります。


[バックエンドもフロントエンドもコードはGitHub においてます](https://github.com/ryuta46/nem-block-monitor)ので興味があればどぞ。

### バックエンド実装詳細

前述のとおり、バックエンドは GCP を使ったサーバレスな環境セットを使っていて、メインの処理は Functions になります。

[このあたり](https://github.com/ryuta46/nem-block-monitor/tree/master/firebase/functions/src)が Functions のコードになります。


Functions は node.js が使用可能です。TypeScript も使えます。というわけで、ここは [NEM Library](https://nemproject.github.io/nem-library-docs/) の出番ですね。

Functions 内でNEM Libraryを使って行っていることは主に2つです。

* 現在のブロック高の取得 (chain/height API)
* 前回チェック時から現在のブロック高までのブロックの取得 (block/at/public API)

指定アドレスのトランザクションを調べる場合、アカウント関連の API (account/transfers/all など) を叩くのが手っ取り早い気がしますが、Functions 上で全ユーザが指定したアドレスに対していちいち API を叩くのは負荷がかかりすぎると判断して、ブロック情報の方を取得する方法を取りました。（このアプリがそんなにたくさんのユーザから使われたらそれはそれで嬉しいけど）

結局はブロックチェーン、承認されたブロックに情報がつまってるはずなので、必要な情報は block の API からとってしまおうという話です。これなら 1ブロックにつき 1回の API コールで済みます。

NEM のブロック承認の間隔は 1分に 1回程度なので、1回の Functions の呼び出しで処理するのは数ブロックに収まり、API の呼び出しも同様に少なく抑えることができます。

ブロックの情報が取得できたらあとは監視対象のアドレス、モザイクに合致した場合に FCM 経由でプッシュ通知を送るという流れになります。

### フロントエンド

フロントエンドは [Flutter](https://flutter.io/) を使っています。

Flutter は最近 1.0 がリリースされて話題にもなった、Google 製のクロスプラットフォームのアプリ開発環境です。

Dart という言語で記述することで Android/iOS 両対応のアプリを簡単につくることができます。UIもマテリアルデザインのパーツが最初から用意されていて、レイアウトも作りやすいと感じています。

1.0 リリースになる以前からちょこちょこ触っていたのですが、今回は試しにアプリ使ってみようと思い、採用してみました。

#### Flutter から NEM のブロックチェーンにアクセスする

Flutter で使用する言語は Dart です。

再び、[shohei さんの記事](https://qiita.com/hukusuke1007/items/6b0f831bb42ee33b8528) にもあるように、今は NEM の API を使う時は言語ごとに用意されているライブラリを使うことが多いです。

しかし、Dart 向けのNEM ライブラリは現在はなさそうな感じでした。

とはいえ、NEMのAPIは REST の API で、HTTP通信とJSONのデシリアライズができれば情報を取得するところはできるので、アプリの方で Dart でHTTPアクセス、データモデルを取得する部分を作りました。ライブラリ化してメンテする自信がなかったのでアプリのコードにそのまま入れてます。

実装は[この辺](https://github.com/ryuta46/nem-block-monitor/tree/master/nem_block_monitor_app/lib/net/nem)です。


このアプリで NIS へのアクセスは、トランザクションの詳細な情報を調べることに限定されているので、今回実装した Dart の処理も部分的なものになります。もし使えそうな部分があればお好きに持っていってください。


#### Dart だけだと難しそうな箇所の対処

殆どは NIS から取得した数値や文字列をそのままモデル化するだけで済むのですが、一点そのまま扱うと不便な箇所がありました。

トランザクションの署名者（送信者）の情報です。NIS から取得できるのは公開鍵の情報ですが、ユーザ的にはアドレスの方がわかりやすいので、アドレスに変換して表示する必要があります。

KotlinやらSwift やらで公開鍵 -> アドレス変換は実装しているのでおおよそ変換方法はわかるのですが、RIPEMD-160 などのハッシュ関数をいくつか使わなければなりません。

Dart にもそれらのハッシュ関数の実装はありそうでしたが、現在 Flutter で使える Dart2 ではこれらのライブラリを取り込むとエラーになるというような情報もありました。

そこで、公開鍵 -> アドレス変換に関しては Flutter の機能である [platform channels](https://flutter.io/docs/development/platform-integration/platform-channels) を使うことにしました。

この機能を使うと、Flutter の Dart コードから Android、iOS のそれぞれのプラットフォームごとの処理を呼び出すことができます。Android なら Kotlin、iOS なら Swift のコードを呼ぶことができます。
これを使って、Android環境では nem-kotlin、iOS環境では nem-swift のそれぞれの公開鍵 -> アドレス変換の処理を呼び出して Dart コードの方に返す実装にしました。

<img width="489" alt="スクリーンショット 2018-12-18 9.32.39.png" src="https://qiita-image-store.s3.amazonaws.com/0/160444/4fdc86ff-b9ae-8743-069b-3d02f5050802.png">


コードは[この辺](https://github.com/ryuta46/nem-block-monitor/blob/master/nem_block_monitor_app/lib/bridge/app_method_channel.dart)

Dartで必要なライブラリが無いって時でもその部分だけ Android/iOS それぞれ向けにコード書けばなんとかなるっていうのは本当ありがたいです。

同じ方法を使えば、Ed25519 の署名の処理だけ Kotlin、Swift の方で行うといったこともできそうでした。（今回のアプリは署名しないのでそういったコードは入れてません）

ちなみにアドレス取得したいだけなら account/get/from-public-key API でもできます。実装時はこのことをすっかり忘れてました。

#### NEM 関連以外の部分

NEM関連以外の部分だと、Firestore へのアクセスや FCM の通知受信部分になるのですが、さすが Google、そこはバッチリ Flutter 向けのライブラリを作ってくれていて、これらを使って簡単に実装することができます。

* [cloud_firestore](https://pub.dartlang.org/packages/cloud_firestore) Cloud Firestore でのデータのやり取り
* [firebse_messaging](https://pub.dartlang.org/packages/firebase_messaging) FCM のプッシュ通知の受信

Firebase も Flutter も Google からリリースされているので、この辺の連携はかなり強い感じです。
[Firebase のチュートリアルにもFlutter でのセットアップ方法が書かれてたりします。](https://firebase.google.com/docs/flutter/setup?hl=ja)

## さいごに

予告どおり、何か作ってみました。

RaccoonWallet でもプッシュ通知を入れる検討を行っていますので、今回のアプリの内容が参考になるかもしれないし、全然違った方法での実装になるかもしれません。