Rails & Mongoid で二段階認証実装

やりたかったこと

Railsのアプリケーションに二段階認証を入れたかった。

下記二つのサイトが分かりやすく、参考にしました。

Deviseを使っているRailsアプリに2段階認証を導入する - Qiita
deviseとGoogle Authenticatorを用いてRailsシステムに「二段階認証」を導入した話 - LiBz Tech Blog

今回利用したgem
github.com

しかし、今回mongodbを使っていて、結構詰まってしまったので備忘録メモ。

詰まったところ

Modelの設定

# Active Recordの場合は下記コマンドを叩くだけでOK!
$ bundle exec rails generate devise_two_factor MODEL ENVIRONMENT_VARIABLE

# こんな感じにModelが書き換えられる
class User
     devise :two_factor_authenticatable,
                 :otp_secret_encryption_key => "encryption_key"

Mongoidの場合は...

1. https://github.com/attr-encrypted/attr_encrypted のgemを追加。

2. Modelを書き換え。

Class User
    include Mongoid::Document
    include Mongoid::Timestamps

    # 自分でattr_encryptedを定義しない場合必要ない。
    extend AttrEncrypted

    # field定義。attr_encryptedより先に定義しておく必要がある
    field :encrypted_otp_secret,        type: String
    field :encrypted_otp_secret_iv,    type: String
    field :encrypted_otp_secret_salt, type: String
    field :consumed_timestep,           type: Time
    field :otp_required_for_login,        type: Boolean
    field :otp_backup_codes,              type: String

    # utf-8 のように設定しないとエラーが起きてしまったので、前もって設定しておく。
    # 設定しなければ、ここのURLの設定が利用される。
[https://github.com/tinfoil/devise-two-factor/blob/f3e40dc3ae5c26e42228d55d8df57cefab8eb167/lib/devise_two_factor/models/two_factor_authenticatable.rb#L15]
    
    attr_encrypted :otp_secret,
      encode: true, charset: "utf-8",
      key: 'please change your environment secret key',
      mode: :per_attribute_iv_and_salt unless self.attr_encrypted?(:otp_secret)

    # deviseは、attr_encryptedを設定した後で読み込む。
    devise :two_factor_authenticatable,
           # リカバリーコードを使う場合
           :two_factor_backupable, otp_number_of_backup_codes: 10

    # backup codeを利用したい場合、json型で保存されるので、取得するときに、parseするように設定
    # もっといい方法ありそうだけど、わからなかった...
    def otp_backup_codes
      JSON.parse(super)
    end

3. あとは上にリンクを貼ったサイトの通りにするとできました!

思ったこと

RailsのGemとか記事は、ActiveRecord前提で書かれているものが多く、調べるのに手こずった。
けど、device gem とかに少し詳しくなれたので、結果オーライ!
ただ、結構時間かかってしまったので、めんどくさがらずに最初からコード追っていけばよかったと公開。

サイトリニューアルでしたこと

概要

現在、サイトリニューアルを行なっている。 忘れないうちに、思ったこととかやったことをメモ。

はじめ

WEBサイトリニューアルをするということで、入社した。
入社時点で、PHPのCodeIgniterから、Railsに移行するということは決まっていた。
また、ReactとかVueを入れたいという要望も出ていた。

技術選定

フロントエンドをどうするかで結構悩んだ。
最初に挙げられた選択肢は 3つ。

  1. Rails に、webpackerで、Client Server Rendering。
  2. Rails に、hypernovaとか、react on rails とかで、Server Side Rendering。
  3. Rails & Slim を基本は使い、jQueryみたいに、一部分だけVueとか使う。

ただ、1は、SEO的にリスクとれなくて、SSRしたいということでNG。
2は、Nodeサーバの運用が誰も経験なくて、複雑そうだったので勇気でなくてNG。
3は、コンポーネント志向で開発したかったのでNG。
となった。

そこで、勉強会とか参加して、調査進めていくうちに、Next.js、Nuxt.js というものを知った。
簡単にいうと、SSRが簡単にできて、なおかつモダンな開発環境が簡単に作れるといったもの。
SEOのためにSSRしたい + Vueの方が日本語ドキュメント豊富 ということから、Nuxtを採択した。
Nodeサーバもリクルートさんとかで、運用事例が出てたので、これはいける!と思って、自身持って提案したらOKが出た。
選んでみて、フロントのモダンな開発環境が作成でき、簡単かつフォルダルールが明確で、選んで良かったと思う。

デザインリニューアル

入社時点で既にWEBサイトのデザインリニューアルの会議が行われてて、サイトリニューアル時についでにやっちゃおうって感じだった。
同時のタイミングでやってるので、いらなそうなコンテンツ削除とか、速度に影響を与えるコンテンツの仕様変更とか、提案しやすくてよかった。
一方で、リリースがデザイナー律速になる可能性があるので、サイトリニューアル急ぎの時は辞めた方がいいと思った。
今回は、急ぎなわけではないし、空き時間で別案件を行なってたので問題なかった。
悲しいこととして、リニューアルしていらない機能が減ると思ってたら、増えてる部分もあって、速度大丈夫か不安な部分もある。
SEO的にSSRしてるけど、一部コンテンツは、非同期でとってこないといけないかも...

それに合わせてアトミックデザインも考えたが、最初からうまく作れる気がしなかったので断念した。
その代わりといってはなんだけど、Storybookを入れた!
最近は追加できてないけど、先輩が手伝えそうということなので、どういうコンポーネントがあるかを整理して、Storybookにまとめておきたい。

構成

フロントエンドとバックエンドのサーバを分けて実装した。
Nuxtは、Expressのミドルウェアとして利用している。
1ページ毎にリニューアルし、完成したらALBにパスルーティングで、Express側に付け替えるって感じで進めている。

今までのURL

Route53 -> ALB -> CodeIgniter(PHP

リニューアルしたURL

Route53 -> ALB -> Express(Nuxt)-> Rails

フロントエンドとバックエンドを分離していて、今はサーバサイドエンジニアしかいないけど、フロントエンドエンジニアが入った時もばりばり作業ができるようになってる。
一人で開発してるからだと思うが、サーバとかリポジトリを分けたことによるメリットを感じないけど、デメリットも感じないので、これで良かったのか未だによく分からない...。
あと、 先輩のアドバイスから、Railsは外に繋げてない(ExpressからAPIを叩く必要がある)ので、Railsサーバに外からアクセスできないようにした。
Express側ではAPIアクセス時、http-proxy-middleware を利用してて、とても便利だった。

APIの仕様に関しては、GraphQLとか、Swaggerとか使おうかと思ったが、会社独自のAPIのURLルールがあったので、それに準拠した。

開発

Nuxt

TypeScriptを試してみたが、サンプルコードがほとんどJSで書かれてたり、Vueの実務経験もなかったので、時間がかかりすぎて2日で断念した。
今から始めるなら、NuxtがTypeScriptをサポートしたし、VueもTypeScript使ってる人多いから問題ないかも。

テストに関しては、レガシーからの脱却を掲げているにもかかわらず、未だに書けてない。書かないと...

あと、ページ特有のデータを、Globalにもつのが好きじゃなかったので、Vuexはそんなに使わずに、props渡しをメインにした。
一方で、UA情報とか動的な設定値とかもVuexに入れて、VuexはGlobal変数みたいな感じで使ってる。

APIのアクセスに関しては、最初 api.js ファイルみたいなのを作ってたが、基本Pagesから呼ぶようにしたので廃止した。
一方で、「いいね」ボタンが押された時に、APIアクセスするコードを、Component に置いているので分離させたいが、いい感じのやり方が思いつかない...

Nuxtや、Vueを使ってて特に良かったと思う時は、動きの部分を実装してる時で、vue の transition とか、スタイルバインディングとかで、デザイナーからの動きの要望を簡単に実現できた。
TOP画面にスライドショーを作ったが、今まではjQueryを使ってたけど、今回は自作してて、細かい要望に簡単に対応出来た。

あと、SSR時、Nuxt標準のサーバを利用する選択肢もあったが、今回はExpressを使った。
ライブラリが豊富だし、シンプルで使いやすいし、フロント側のサーバでできることが広がった。BFFの役割を将来的にはもたせたい。

あと、Adsenceの広告周りを、Vueで組み込めるかが不安だったけど、今のところ安定して動いていて、問題なさそう。

Rails

Rails Wayに準拠することを目標にしている。
テストコードを真面目に書くのは初めてだったが、今のところControllerと、BusinessLogic周りは全部書けている。
リファクタリングが怖くなくなったし、開発時もブラウザ確認回数が減るし、やっとテストの良さがわかった。
名前空間だけ未だに慣れない。ライブラリで同じ名前使われたりした時困って諦めた。

ActiveRecordは何が起こってるかわかんないときがあって、慣れるまで大変だったけど、慣れるとすごく便利。 パフォーマンス向上のため、生SQLをかかざるを得ない時もありますが...

Viewに関しては、jbuilderを使ってて、使いやすくて便利。
作り方覚えて、Scopeとかいい感じに設定できてくると、開発速度が凄く上がってるなって感じがする。
WEBサイトのAPIで、Railsがよく使われてる理由がわかった!

ただ、次はGoとかTypeScriptとかも試してみたい!

インフラ

EC2

インフラ側を一から構築するのは初めてだったので、先輩に教えてもらいながら行なった。

最初はEC2にサーバを最低2台ずつ立ち上げてた。
Nodeサーバは、Expressはデーモン起動のために、pm2を利用した。 Railsサーバはpuma。 どちらのサーバも、nginxをproxyと静的ファイルの配信と、ログ収集のために利用した。

access_log とかは、kinesisでlogをS3に送ってるらしい。 監視については、zabbix -> grafana で行なった。 デプロイは、CodePiplineで、ブランチへのpushを検知し、CodeDeployを利用して、Deployした。

AMIとか、セキュリティグループとかは、既に先輩が作ってたものを利用した。

問題として、CodeDeployでデプロイするときに、新旧サーバが起動してる瞬間があって、そのときに静的ファイルをnginxから配信すると、not foundエラーが起きる時があった。
詳しくは後述するが、なのでS3にあとで移動した。

ECS Fargate

社内のDocker推奨の機運が高まったので、ECSに移行することにした。
DockerFileはもともと勉強してたので、あんま問題なかった。 Nodeサーバは、Dockerだと、デーモン起動必要ないし、ログも標準出力で良かったので、node index.js みたいに起動した。 Railsサーバは引き続きpumaだけど、ログを標準出力に変えた。

デプロイは、CodePiplineを利用。
Githubのソースを元に、CodeBuildを走らせ、Docker buildして、ECRにImageをpushする。
次に、Nuxtの場合だけ、ECRのImageにbuildしたファイルが入ってるので、それを取得し、S3へアップロード(ブラウザからはCDNを介してアクセス)。
最後に、ECSでデプロイするって流れ。

先輩が、これらの設定を一発で行うshellファイルを作ってくれたので余裕でできた。(後から手でもやったが覚えれば簡単だった)

監視は、引き続き、nginxのログをkinesis agentでS3に送った。
あと、2xxとか3xxとかResponseTimeとかを見るために、ALBを監視してる。 また、CPUとかは、CloudWatchでもみてるが、DataDogをタスクに追加して、監視に利用してる。
DataDogは、APMを利用して、パフォーマンス測定も簡単にできそうなので、すごい!

Fargateは、SSHログインできないので、調査しにくいところが困ってる...
だいたいLocalで、Docker立ち上げれば原因は分かるけど、プロセスの状況を知れる方法が知りたい。
パフォーマンスチューニングをしたいけど、linuxのコマンド叩けなくて挙動が今のところ不明...
インフラ周りの勉強をもっとしなきゃいけない。

その他

自分がNuxtを導入した後、弊社のCTOが気に入って、それ以後の新規C向けサイトをNuxtで作成するようになった。 自分はほとんど説明してないのに、サイト毎にそれぞれ別の先輩が、ささーっと作ってたからNuxt(+ Vue)のドキュメントや分かりやすさが、すごいなと改めて思った。

感想

入社時は、PHPできます!コーディングできます!リスティングとかABテストとか施策考えられます!って感じだった。
それが、入社して9ヶ月で、

  • Railsで開発できます!
  • Vue & Nuxtで開発できます!
  • Express使ったことあります!
  • Dockerで環境構築できます!
  • ECSで本番環境構築できます!
  • 既存サイトのリニューアル経験あります!

って言えるようになったのは、先輩たちや与えられたポジションのおかげで、感謝しかない。

今までの会社では、個人とかチームで、企画考えて、CVRあげていこうってのがメインで楽しかったけど、企画から一歩離れて、技術に正面から向き合うのも楽しいなって思った。

今後

まずは、リニューアルを最後までやりきりたい。 あと、現在インフラ、バックエンド、フロントエンドを全部一人でやってるので、ドキュメント整備しないといけない。 一応、複数人で開発してるときと同じように、意識して作ったり、一部ドキュメント書いたりしてるけど、新しく入った人が困らないようにしないといけない。

また、会社として、マイクロサービス化を進めるので、先輩たちの仕事をキャッチアップしながら、勉強していきたい。
具体的な技術的には、

  • TypeScript
  • Go
  • Envoy
  • Kubenetes

を学んでいきたい。

あと、Ethereum を使うサービスを手伝ってるので、ブロックチェーン周りの技術も勉強進めていく。

再)Railsで、Ethereumのプライベートネット上のERC20Tokenの残高確認

前回上手くいかなかったので、リベンジ

やったこと

1. gethのインストール

プライベート・ネットに接続する · Ethereum入門
上記を参考にgethをインストール。
その中で、myGenesis.json を作成する部分がある。
このjsonファイルは、下記のサイトを参考に書き換える。

ブロックチェーンEthereum入門 2 | NTTデータ先端技術株式会社

下記の値を設定し直すとよかった。
difficulty: マイニングの難易度。下げると、マイニングの時間が減る。
alloc: これを設定すると初期状態で、etherが指定のアカウントが保持している状態にできる。
gasLimit: transaction 時に必要なgasの量。

参考に作ったのがこちら

{
  "config": {
    "chainId": 15
  },
  "nonce": "0x00006d6f7264656e",
  "difficulty": "0x0001",
  "mixhash": "0x00000000000000000000000000000000000000647572616c65787365646c6578",
  "coinbase": "0x0000000000000000000000000000000000000000",
  "timestamp": "0x00",
  "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
  "extraData": "0x",
  "gasLimit": "0xffffffffffff",
  "alloc": {
      "0x5F6bD87B34c3C1315aC82b3f6f84d5e1DF31422b": { "balance": "100000000000000000000" },
  }
}

alloc の keyの部分を何にすればいいかよくわからない方は、下記のサイトを参考にするとわかりやすいです! 一行で書くと、一度gethを起動して、アカウント作って、そのアカウントアドレスをkeyの部分に書き、再度genesisブロックの初期化を行って、もう一度gethを起動しました。

ethereum(private)の環境構築からJSON-RPC実行まで - Qiita

もし、Failed to write genesis block: database already contains ...と出たら、下記のサイト参考に、DBを削除してもう一回実行。

geth init で Failed to write genesis block: database already contains an incompatible genesis block - Qiita

2. gethの起動

自分は、下記で起動しました。

geth --networkid "15" --nodiscover --datadir "/Users/kei/eth_private_net" --rpc --rpcaddr "localhost" --rpcport "8545" --rpcapi "web3,eth,net,personal" --minerthreads 1" --rpccorsdomain "*" --mine

大事なのは、
- --mine とつけることで、勝手にminingを行ってくれる。(miningをしないと、transactionが処理できなかった。)
- --rpcaddr "localhost" --rpcport "8545" とつけて、http経由でアクセスできるようにする。(truffle というライブラリで、smart contractを作るときに、httpアクセスするため。)

起動オプションについては、こちらがわかりやすかったです! ethereum(private)の環境構築からJSON-RPC実行まで - Qiita

3. コンソールでログイン

geth attach http://127.0.0.1:8545 geth attach ipc:/Users/kei/eth_private_net/geth.ipc

どっちでもログインできる!

4. Railsで確認

こちらのgemをインストール
GitHub - EthWorks/ethereum.rb: Ethereum library for the Ruby language

$ bundle exec rails c
> client = Ethereum::IpcClient.new('/Users/kei/eth_private_net/geth.ipc')
> client.get_balance('0x5F6bD87B34c3C1315aC82b3f6f84d5e1DF31422b')
=> 100000000000000000000

こんな感じでアクセスできる。

5. ERC20のSmart Contract作成

下記のサイトを参考にして、tokenを作成。

【Truffle5.0対応】シンプルなERC20トークンを作成しよう! │ こじりょーの研究所

上記のサイトでは、Ganache を利用していますが、今回はgeth利用しているので、truffle-confing.jsに、起動したgethの設定を追記

development: {
  host: "127.0.0.1",
  port: 8545,
  network_id: "*"
},

あとは、上記サイトに書いてあるように、contract をデプロイする。
その前に、geth の console 上から、アカウントのアンロックを忘れずに。これをしないと、etherを使った操作ができない。

$ geth attach http://127.0.0.1:8545
// passwordが聞かれるので、自分がアカウント作成したときに、設定しているものを入力
> personal.unlockAccount(eth.coinbase)
$ truffle migrate
// 結果こんな感じ
⚠️  Important ⚠️
If you're using an HDWalletProvider, it must be Web3 1.0 enabled or your migration will hang.


Starting migrations...
======================
> Network name:    'development'
> Network id:      15
> Block gas limit: 208531973388668


1_initial_migration.js
======================

   Deploying 'Migrations'
   ----------------------
   > transaction hash:    0x788615947355935aa5df21b8ca16ffac7f6e562f506dd6affd4914425b9f5dc1
   > Blocks: 2            Seconds: 4
   > contract address:    0x4543dCf52aa78d32d667bBF39EEc7B98b56d120F
   > account:             0x5F6bD87B34c3C1315aC82b3f6f84d5e1DF31422b
   > balance:             1645
   > gas used:            252758
   > gas price:           20 gwei
   > value sent:          0 ETH
   > total cost:          0.00505516 ETH


   > Saving migration to chain.
   > Saving artifacts
   -------------------------------------
   > Total cost:          0.00505516 ETH


2_deploy_contracts.js
=====================

   Deploying 'SimpleToken'
   -----------------------
   > transaction hash:    0xdee089ce5f1384c5016b8f5c8d870e8fbf1fef4c4046e49f84a235d2f9f28c4e
   > Blocks: 0            Seconds: 8
   > contract address:    0x2FA965B7d62858Dc4DBCeA965271b0D7dB780A13
   > account:             0x5F6bD87B34c3C1315aC82b3f6f84d5e1DF31422b
   > balance:             1665
   > gas used:            1387474
   > gas price:           20 gwei
   > value sent:          0 ETH
   > total cost:          0.02774948 ETH


   > Saving migration to chain.
   > Saving artifacts
   -------------------------------------
   > Total cost:          0.02774948 ETH


Summary
=======
> Total deployments:   2
> Final cost:          0.03280464 ETH

この結果の、「0x2FA965B7d62858Dc4DBCeA965271b0D7dB780A13」が、contract addressとなるので大事。

6. RailsからERC20Tokenの残高確認

$ bundle exec rails c
> client = Ethereum::IpcClient.new('/Users/kei/eth_private_net/geth.ipc')
> name = '作成したコインの名前'
> contract_address = '0x2FA965B7d62858Dc4DBCeA965271b0D7dB780A13'
// ERC20Tokenを保持している人のアドレス
> account_address = '0x5F6bD87B34c3C1315aC82b3f6f84d5e1DF31422b'
// abiとは、Application Binary Interfaceの略で、このContractは、こんなファンクションもってるよって定義するもの。
// 今回は、name と、 balanceOf 以外省略してます。
> contract_abi = [{"constant":true,"inputs":[],"name":"name","outputs":[{"name":"","type":"string"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_owner","type":"address"}],"name":"balanceOf","outputs":[{"name":"balance","type":"uint256"}],"payable":false,"type":"function"}]
> contract = Ethereum::Contract.create(name: name, address: contract_address, abi: contract_abi.to_json, client: client)
> contract.call.name
=> "作成したコインの名前"
> contract.call.balance_of(account_address)
=> 100000000000000000000

7. Tokenの移動

$ bundle exec rails c
> client = Ethereum::IpcClient.new('/Users/kei/eth_private_net/geth.ipc')
> name = '作成したコインの名前'
> contract_address = '0x2FA965B7d62858Dc4DBCeA965271b0D7dB780A13'
// ERC20Tokenを保持している人のアドレス
> account_address = '0x5F6bD87B34c3C1315aC82b3f6f84d5e1DF31422b'
// ERC20Tokenを送りたい人のアドレス
> target_address = '0x37543c2d55bc48d5c87c49fbdd321834ffafbf43'

// ここでは、truffle で作成したときにできる、SimpleToken.json の abi部分を全部コピーしてみました。
> contract_abi =[{"constant":true,"inputs":[],"name":"name","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function","signature":"0x06fdde03"},{"constant":false,"inputs":[{"name":"spender","type":"address"},{"name":"value","type":"uint256"}],"name":"approve","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function","signature":"0x095ea7b3"},{"constant":true,"inputs":[],"name":"totalSupply","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function","signature":"0x18160ddd"},{"constant":false,"inputs":[{"name":"from","type":"address"},{"name":"to","type":"address"},{"name":"value","type":"uint256"}],"name":"transferFrom","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function","signature":"0x23b872dd"},{"constant":true,"inputs":[],"name":"decimals","outputs":[{"name":"","type":"uint8"}],"payable":false,"stateMutability":"view","type":"function","signature":"0x313ce567"},{"constant":false,"inputs":[{"name":"spender","type":"address"},{"name":"addedValue","type":"uint256"}],"name":"increaseAllowance","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function","signature":"0x39509351"},{"constant":true,"inputs":[{"name":"owner","type":"address"}],"name":"balanceOf","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function","signature":"0x70a08231"},{"constant":true,"inputs":[],"name":"symbol","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function","signature":"0x95d89b41"},{"constant":false,"inputs":[{"name":"spender","type":"address"},{"name":"subtractedValue","type":"uint256"}],"name":"decreaseAllowance","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function","signature":"0xa457c2d7"},{"constant":false,"inputs":[{"name":"to","type":"address"},{"name":"value","type":"uint256"}],"name":"transfer","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function","signature":"0xa9059cbb"},{"constant":true,"inputs":[{"name":"owner","type":"address"},{"name":"spender","type":"address"}],"name":"allowance","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function","signature":"0xdd62ed3e"},{"inputs":[{"name":"name","type":"string"},{"name":"symbol","type":"string"},{"name":"decimals","type":"uint8"},{"name":"initSupply","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"constructor","signature":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"name":"from","type":"address"},{"indexed":true,"name":"to","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Transfer","type":"event","signature":"0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"},{"anonymous":false,"inputs":[{"indexed":true,"name":"owner","type":"address"},{"indexed":true,"name":"spender","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Approval","type":"event","signature":"0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925"}]

> contract = Ethereum::Contract.create(name: name, address: contract_address, abi: contract_abi.to_json, client: client)
> contract.call.name
=> "作成したコインの名前"
> contract.transact_and_wait.transfer(target_address, 100)
> contract.call.balance_of(account_address)
=> 99999999999999999900
> contract.call.balance_of(target_address)
=> 100

transferをしたい時は、geth上で、coinbase のunlockAccountが必要なので注意。
あと、privateで、transactionを実行する時は、自分でminingをしないといけないので忘れずに。(--mine をつけてれば大丈夫)

Tokenの移動(とあるアカウント => とあるアカウントへ)

$ bundle exec rails c
> client = Ethereum::IpcClient.new('/Users/kei/eth_private_net/geth.ipc')
> name = '作成したコインの名前'
> contract_address = '0x2FA965B7d62858Dc4DBCeA965271b0D7dB780A13'
// Tokenを保持している & etherもいっぱいもってるアドレス
> master_address = '0x5F6bD87B34c3C1315aC82b3f6f84d5e1DF31422b'
// さっきTokenをもらったアドレス
> sender_address = '0x37543c2d55bc48d5c87c49fbdd321834ffafbf43'
// 今からTokenをもらうアドレス
> receiver_address = '0x7c278ea950e9af850ad089f93a94fd8fd7c62725'
// abiはさっきと一緒
> abi = ...

// まず、gasのために、etherがsender_addressに必要なので、master_addressから渡す
> client.personal_unlock_account(master_address, 'password')
> client.eth_send_transaction({ from: master_address, to: sender_address, value: "0x9184e72a"})

// デフォルトのアカウントを変更する
> client.default_account = sender_address
> contract = Ethereum::Contract.create(name: name, address: contract_address, abi: contract_abi.to_json, client: client)

// 送る前
> contract.call.balance_of(sender_address)
=> 100
> contract.call.balance_of(receiver_address)
=> 0

// 送信!
> client.personal_unlock_account(sender_address, 'password')
> transact_and_wait.transfer(sender_address, 40)

// 結果
> contract.call.balance_of(sender_address)
=> 60
> contract.call.balance_of(receiver_address)
=> 40

終わり

ここまでくるのに時間かかったが、etheriumとcontractについて、いくつか理解できた... 実際に、wallet を作成する時は、contract の address さえわかれば、etherscan のAPIを経由して、contract_abi、contract_name を取得ができる。
あとは、それを使ってcontractのインスタンス生成をし、ユーザーのアドレスの残高が確認できる模様。

結構理解できてきたので、まずは、wallet を自作できるようになりたい!!

Circle CI から、S3 へ Nuxt Assets をアップロード

やりたかったこと

デプロイ時に、NuxtのAssetsファイルをS3にアップロードしたかった。

下の記事でやったようにしたらできたが、buildspec.yml がdockerのbuildとs3のuploadという二つの役割を持っているのはあんま良くない。 okadak1990.hatenablog.com

で、他の人はどうやってるか調べたら、Circle CIでやってたので、やってみた。

参考

この記事がわかりやすいです。 Nuxt.js+CircleCIで静的ページをAWSのS3へデプロイする - Qiita

自分でもやってみた

ちなみに、CircleCIの管理画面から、S3への put 権限を持ったアカウントを登録する必要があります。

# Javascript Node CircleCI 2.0 configuration file
#
# Check https://circleci.com/docs/2.0/language-javascript/ for more details
#
references:
  commands:
    set_env: &set_env
      name: Set environment
      command: |
        ENV=development
        if [ ${CIRCLE_BRANCH} == "master" ]; then
            ENV=production
        fi
        echo "export ENV=${ENV}" >> $BASH_ENV

version: 2
jobs:
  build:
    docker:
      - image: circleci/node:11.8.0

    working_directory: ~/workspace

    steps:
      - checkout

      # Download and cache dependencies
      - restore_cache:
          key: projectname-{{ .Branch }}-{{ checksum "yarn.lock" }}

      - run:
          name: System information
          command: |
            echo "Node $(node -v)"
            echo "Yarn v$(yarn --version)"

      - run:
          name: Install dependencies
          command: yarn install

    #   - run:
    #       name: Run Linter
    #       command: yarn run lint

    #   - run:
    #       name: Run Test
    #       command: yarn run test

      - save_cache:
          paths:
            - node_modules
            - ~/.cache/yarn/
          key: projectname-{{ .Branch }}-{{ checksum "yarn.lock" }}

      - run: *set_env

      - run:
          name: Build assets
          command: |
            yarn build:${ENV}

      - persist_to_workspace:
          root: .
          paths:
            - .

  upload:
    docker:
      - image: cibuilds/aws:1.16.1

    working_directory: ~/workspace

    steps:
      - attach_workspace:
          at: .

      - run: *set_env

      - run:
          name: s3 upload
          command: |
            ENV=development
            if [ ${CIRCLE_BRANCH} == "master" ]; then
              ENV=production
            fi
            aws s3 cp .nuxt/dist/client/ s3://bucket-name/${ENV} --recursive --metadata-directive "REPLACE" --cache-control "max-age=31536000"

workflows:
  version: 2
  build_and_upload :
    jobs:
      - build:
          filters:
            branches:
              only:
                - master
                - develop

      - upload:
          requires:
            - build
          filters:
            branches:
              only:
                - master
                - develop

気づいてしまった...

Github に push があったら、Circle CI と、CodePipeline を走らせていた。 ということは、CodePipelineが、Circle CIのS3へのアップロードより先に終わる可能性がある... そうすると、assetsファイルが、404になるタイミングが発生するかもとのことで、断念... やっぱり、CodePipelineから、アップロード用のCodeBuild走らせて、S3へアップロードするのが綺麗だと思った。

Docker上で、Buildしたファイルをローカルに保存する。

やりたいこと

Docker上で、nuxt build したファイルを、S3にあげたい。 ただ、Docker上にAWSのアカウント情報をもたせたくなかった。

流れとして、github push -> code pipeline -> code build -> ecs deploy の code build 部分で、S3へ Nuxt の assets ファイルをデプロイしたかった。

やったこと

code pipeline とか、code build の基本設定は、もともとできてるとする。

code build 内では、Docker build が走っていて、そのDocker内で、Nuxt の build が走っていて、それが Image として生成されている。 code build の build_spec.yml で、以下の.sh を実行する。

IMAGE = [Nuxtをbuild済みのイメージの名前]
# docker container を作成。
docker create ${IMAGE}
# -q とするとcontainer ID がかえってくる。-a としないと、起動していないcontainerが見つからない
CONTAINER = $(docker ps -a -q --filter ancestor=${IMAGE})

# コンテナから、ファイルをコピー
docker cp ${CONTAINER}:/app/.nuxt /.nuxt

# S3へアップロード
aws s3 cp ../.nuxt s3://[バケット名] --recursive --metadata-directive "REPLACE" --cache-control "max-age=31536000"

ただ、code build を行うロールに、s3のバケットにputできるポリシーの追加が必要。

しかし...

code build や、code deploy で、S3へアップロードした方がいいとアドバイスをもらった。 docker push をしている code build は、docker に関することだけ行い、S3へのアップロードは新しいcode build 作成した方が役割分担できて良さそう。

Railsから、Ethereumのテストネットに繋げてみる

参考にしたページ

Ethereumのテストネット上にトークンをデプロイする - Qiita

前提

既にgethは、下記の通りインストール済み https://book.ethereum-jp.net/first_use/installing_geth.html

テストネットとは?

Ethereumのテスト環境的なもの。いくつか種類があるが、今回は「Rinkeby」を利用します。 ここでethereumを手に入れても、価値がない代わりに、テスト用にethereumをもらえるようです。

geth起動

こちらをそのまま実行 Ethereumのテストネット上にトークンをデプロイする - Qiita

// --syncmode "light"  とすると、eth.getBalanceがうまくいかなかったので外した。
$ geth --rinkeby --datadir "/Users/user/eth_test_net" --rpc --rpcaddr "localhost" --rpcport "8545"

別ターミナルを開いて、consoleを立ち上げる

$ geth attach ipc:/Users/user/eth_test_net/geth.ipc

// 同期していること確認。
> eth.syncing
{
  currentBlock: 37476,
  highestBlock: 3879274,
  knownStates: 88320,
  pulledStates: 72795,
  startingBlock: 0
}

コマンドの意味はこちらがわかりやすい! Ethereumクライアント geth起動時のオプションの意味 - Qiita

Qiitaにある通り行って、etherをもらう。

railsとの連携

gemを入れて、 GitHub - EthWorks/ethereum.rb: Ethereum library for the Ruby language

以下のようにアクセスできる

client = Ethereum::IpcClient.new("/Users/user/eth_test_net/geth.ipc")
client.get_balance([address])  // => etherの残高

しかし...

同期が終わって、get_balance してもetherをもらえていない。 etherscan 上は正しくtransactionが行われているのに、同期したファイルの、そのblocknumberは違うっぽい... 結局わからなかったので、private netですることにした。次へ続く。

ネットワーク関連メモ

イベントにネットワークの運営側として参加したので勉強になったことメモ

もっと詳しくなりたい...

Wireless LAN Controller と Access Point

Access Point は、Wifiを飛ばしたい場所に直接置く。

これを有線でインターネットに接続して、かつWireless LAN Controllerと接続しておけば、Wireless LAN Controller上から、全てのAccess Point の管理ができるのでめっちゃ便利! 離れた場所のAccess Point も一括管理可能!

例えば、システムのアップデートや、Access Point のステータス、Access Pointに繋がってる人数など。

Access Point の設置

Router -> 各部屋のPatch(LAN ケーブル繋げるところ)-> LAN ケーブル -> Switch(VLANに対応している必要があるとのこと。) -> LAN ケーブル -> Access Point と配置する。

各部屋にどのくらい、どの場所でAccess Point を置く必要があるかは、人数とか他の配置物とか、電源とかによる模様。

電源はSwitchからAccessPointに供給される。 PoE(Power over Ethernet)を使えば、電源ではなく、LANケーブルから電源供給できるので便利。

VLANの概要と基本的な仕組み https://news.mynavi.jp/article/vlan-1/

Wifi について

Access Point から、Wifi が飛ぶようになっていて、5ghzと、2.4GHzの帯域で飛んでいる。

2.4GHz は遮蔽物に強く、遠くまで届くが、通信速度が遅く、電子レンジとかbluetooth と同じ帯域なので混線しやすい。

一方、5GHzは遮蔽物に弱いが、通信速度が早く、混線もしにくい。

そのため、各部屋毎にAccess Point を設置しておくと、みんなが5GHz のWifiを使って、高速な通信が可能。

会場が元々提供している5GHzや2.4GHzの帯域がある場合、2.4GHzはチャンネル数、いわゆる通信ができる通路が少ないため、干渉する可能性があり、この点からも5GHzを使うことが良い。

ただ、5GHzは、気象レーダーや軍事レーダーでも使われている帯域のため、屋外での利用は難しいらしい。部屋の中で使うのは問題ない。

Wireless LAN Controller の監視

SNMPというプロトコルを用いて、zabbixを用いて監視する。

可視化はGrafanaを用いるとかっこいい!

多分こんな感じ。

https://qiita.com/k7tak29/items/dd0961cf9fc1ef3301a7