Ethereumのオフチェーン技術：Raiden Networkを使って、ERC20トークンを送金してみた

Ethereumはスケーラビリティ問題を抱えている。

そう言われて久しいですが、
2017年の年末に[CryptoKitties](https://www.cryptokitties.co/)というゲームが流行ったときには、それだけでトランザクションに遅延が発生しました。（猫詰まり...）

もちろん、ブロックチェーンは分散ネットワークなので、高スループットが出ないことは当然と言えば当然かと思います。
しかし、それにしても、ゲームが1つ流行ったくらいで、遅延が発生するようではさすがに実用的であるとは言えないでしょう。

そこで、現在ではこのスケーラビリティ問題を解決するための技術が提案され、研究・開発が進められています。

それが、Raiden Network、Plasma、Shardingといった技術です。
今回扱わない2つの技術の詳細については以下のリンクをご覧ください。

- Plasma
[Plasma　ーBlockchains in Blockchainsー](http://www.udon.blog/entry/2017/12/16/114518)
[Plasma を理解するための Ethereum Protocol のキホン](https://speakerdeck.com/amachino/plasma-woli-jie-surutamefalse-ethereum-protocol-falsekihon)

- Sharding
[シャーディングがイーサリアムのスケーラビリティ問題を解決する理由](https://zoom-blc.com/sharding-ethereum)
[Ethereum Shardingの大まかな説明と概念図](https://individua1.net/ethereum-sharding-overview/)

今、この瞬間も開発は進められていますが、この問題が解決するにはまだ時間を要するでしょう。

と、言ってもスケーラビリティ問題を解決しうる技術を触ってみたい！
そんな欲も出て来る時期です。

そこで、今回はその一つであるRaiden Networkを使って、実際にERC20トークンを送金するところまで試してみました。

#1. Raiden Networkとは
一言で言うと、Bitcoinのオフチェーン技術であるLightning NetworkのEthereum版です。
これも、各所で分かりやすく説明されているので、そちらも合わせてご覧ください。

<img width="1432" alt="スクリーンショット 2018-06-06 11.13.03.png" src="https://qiita-image-store.s3.amazonaws.com/0/129706/b91de02e-7fdd-813c-a36f-c18c922c2423.png">

[Raidenの衝撃 ーEthereumでの超速ペイメントー](http://www.udon.blog/entry/2017/03/14/222359)

[What is the Raiden Network?](https://raiden.network/101.html)

スケーラビリティ問題に対して、ブロックチェーン上（オンチェーン）ではなく、オフチェーンで解決しようというソリューションです。

現在、v0.3.1まで開発が進んでいます。

Raidenは多対多でネットワークを作ります。
1対多でネットワークを作るμRaidenというものもあり、こちらはすでにmain netで動いているようです。

μRaidenについては以下の記事を参考にさせていただきました。
https://qiita.com/ryoppy/items/90e0f689ebb3e845f758

#2. 環境構築
今回使用したgethとraidenのバージョンは以下の通りです。

- go-ethereum v1.8.10
- raiden v0.3.1

基本的には以下の公式ドキュメントと@tomohataさんの記事を参考に環境構築を行いました。

http://raiden-network.readthedocs.io/en/stable/overview_and_guide.html
https://qiita.com/tomohata/items/21e33b0d68374090a697

Ropstenのネットワークでgethを起動し、ブロックの同期を行います。
ブロックの同期はfastモードで問題ありません。

```
geth --testnet --fast --rpc --rpcapi eth,net,web3,personal --bootnodes "enode://20c9ad97c081d63397d7b685a412227a40e23c8bdc6688c6f37e97cfbc22d2b4d1db1510d8f61e6a8866ad7f0e17c02b14182d37ea7c3c8b9c2683aeb6b733a1@52.169.14.227:30303,enode://6ce05930c72abc632c58e2e4324f7c7ea478cec0ed4fa2528982cf34483094e9cbc9216e7aa349691242576d552a2a56aaeae426c5303ded677ce455ba1acd9d@13.84.180.240:30303"
```

Raiden Networkを起動するためにはいくらかアカウントがETHを保持している必要があるため、MetaMaskなどから、gethのアカウントに送金しておいてください。
なお、今回はRaidenのEcho Nodeを使わずに、ローカルで送金を実施するため、アカウントは2つ用意しました。

```
> eth.accounts
["0xc1c4317e06f62d6378370ec66da5d662b3e81ea6", "0x50624159646b7a844647542e01ad2fb59e8969d2"]
```

今後、送金を行うアカウント（0xc1c4317e06f62d6378370ec66da5d662b3e81ea6）をA、受け取るアカウント（0x50624159646b7a844647542e01ad2fb59e8969d2）をBとします。

#3. Raiden Networkを起動する
Raiden Networkを起動します。

- port:9545 → A（0xc1c4317e06f62d6378370ec66da5d662b3e81ea6）

```
$ raiden --keystore-path  /Users/user1/Library/Ethereum/testnet/keystore/ --api-address 0.0.0.0:9545 --listen-address 0.0.0.0:4001 --nat stun
Welcome to Raiden, version 0.3.1.dev452+g1ceb077f!
2018-06-04 07:00:12 [info     ] Network port opened            [raiden.network.sockfactory] external=10.41.90.56:4001 internal=0.0.0.0:4001 method=upnp
The following accounts were found in your machine:

[  0] - 0xc1c4317e06f62d6378370ec66da5d662b3e81ea6
[  1] - 0x50624159646b7a844647542e01ad2fb59e8969d2

Select one of them by index to continue: 0
Enter the password to unlock c1c4317e06f62d6378370ec66da5d662b3e81ea6: 
Checking if the ethereum node is synchronized
You are connected to the 'ropsten' network and the DB path is: /Users/user1/.raiden/netid_3/c1c4317e/log.db
2018-06-04 07:00:44 [info     ] registered endpoint in discovery [raiden.network.discovery] host=10.41.90.56 node_address=c1c4317e port=4001
2018-06-04 07:00:44 [info     ] Lock 4614058840 acquired on /Users/user1/.raiden/netid_3/c1c4317e/.lock
The Raiden API RPC server is now running at http://0.0.0.0:9545/.

See the Raiden documentation for all available endpoints at
http://raiden-network.readthedocs.io/en/stable/rest_api.html
2018-06-04 07:02:41 [info     ] 127.0.0.1 - - [2018-06-04 16:02:41] "GET /api/1/address HTTP/1.1" 200 197 0.005883
```

- port:9546 → B（0x50624159646b7a844647542e01ad2fb59e8969d2）

```
$ raiden --keystore-path  /Users/user1/Library/Ethereum/testnet/keystore/ --api-address 0.0.0.0:9546 --listen-address 0.0.0.0:4002 --nat stun
Welcome to Raiden, version 0.3.1.dev452+g1ceb077f!
2018-06-04 07:10:05 [info     ] Network port opened            [raiden.network.sockfactory] external=10.41.90.56:4002 internal=0.0.0.0:4002 method=upnp
The following accounts were found in your machine:

[  0] - 0xc1c4317e06f62d6378370ec66da5d662b3e81ea6
[  1] - 0x50624159646b7a844647542e01ad2fb59e8969d2

Select one of them by index to continue: 1
Enter the password to unlock 50624159646b7a844647542e01ad2fb59e8969d2: 
Checking if the ethereum node is synchronized
You are connected to the 'ropsten' network and the DB path is: /Users/user1/.raiden/netid_3/50624159/log.db
2018-06-04 07:10:48 [info     ] endpoint already registered    [raiden.network.discovery] host=10.41.90.56 node_address=50624159 port=4002
2018-06-04 07:10:48 [info     ] Lock 4540752336 acquired on /Users/user1/.raiden/netid_3/50624159/.lock
The Raiden API RPC server is now running at http://0.0.0.0:9546/.

See the Raiden documentation for all available endpoints at
http://raiden-network.readthedocs.io/en/stable/rest_api.html
2018-06-04 07:11:03 [info     ] 127.0.0.1 - - [2018-06-04 16:11:03] "GET /api/1/address HTTP/1.1" 200 197 0.001496
```

なお、起動時にnatを明示的に指定しないとデポジットがあるにも関わらず、transferで「{"errors": "Payment couldn't be completed (insufficient funds or no route to target)」のようなエラーになりました。
（ここでかなり煮詰まりました...）

ネットで調べまくって、今回は--nat stunのオプションを追加することで、エラーを解消することができました。

- reference: https://github.com/raiden-network/raiden/issues/1086


#4. RTT（ERC20）を発行する
Raidenで送金できるトークンはERC20なので、独自に発行したトークンはネットワークに登録する必要があります。しかし、今回はその手間を省くためにRaiden Testnet Token（RTT）を利用しました。
gethコンソールを使って、RTTを発行します。
なお、RTTの情報は以下のEtherscanで確認することができます。
https://ropsten.etherscan.io/token/0x0f114a1e9db192502e7856309cc899952b3db1ed#readContract


```
$ geth attach rpc:http://localhost:8545
Welcome to the Geth JavaScript console!

instance: Geth/v1.8.6-stable/darwin-amd64/go1.9.2
coinbase: 0xdf39c716572fe7dc8574ac8a5a7d9155206f38cd
at block: 3369578 (Mon, 04 Jun 2018 14:27:26 JST)
 modules: eth:1.0 net:1.0 personal:1.0 rpc:1.0 web3:1.0

> rtt_token_abi = [{"constant":true,"inputs":[],"name":"name","outputs":[{"name":"","type":"string"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_spender","type":"address"},{"name":"_value","type":"uint256"}],"name":"approve","outputs":[{"name":"success","type":"bool"}],"payable":false,"type":"function"},{"constant":false,"inputs":[],"name":"mint","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"totalSupply","outputs":[{"name":"supply","type":"uint256"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_from","type":"address"},{"name":"_to","type":"address"},{"name":"_value","type":"uint256"}],"name":"transferFrom","outputs":[{"name":"success","type":"bool"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"decimals","outputs":[{"name":"","type":"uint8"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"version","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"},{"constant":true,"inputs":[],"name":"symbol","outputs":[{"name":"","type":"string"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"amount","type":"uint256"}],"name":"mint","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_to","type":"address"},{"name":"_value","type":"uint256"}],"name":"transfer","outputs":[{"name":"success","type":"bool"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"amount","type":"uint256"},{"name":"target","type":"address"}],"name":"mintFor","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_spender","type":"address"},{"name":"_value","type":"uint256"},{"name":"_extraData","type":"bytes"}],"name":"approveAndCall","outputs":[{"name":"success","type":"bool"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_owner","type":"address"},{"name":"_spender","type":"address"}],"name":"allowance","outputs":[{"name":"remaining","type":"uint256"}],"payable":false,"type":"function"},{"inputs":[{"name":"_tokenName","type":"string"},{"name":"_tokenSymbol","type":"string"}],"payable":false,"type":"constructor"},{"payable":false,"type":"fallback"},{"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"},{"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"}]
> rtt_token_address = "0x0f114a1e9db192502e7856309cc899952b3db1ed"
> rtt_token = web3.eth.contract(rtt_token_abi).at(rtt_token_address)
> rtt_token.mint({from: eth.accounts[0]})
"0xc7263e368f96d96b7e65399ee26432251849c07a2581eea7f2ef6ac4984b3680"
> rtt_token.balanceOf(eth.accounts[0])
100
```

※Bでも同様に、トークンを発行する。

#5. RTTのネットワークに参加する
送金を行うには、A, BをRTTトークンのネットワークに参加させる必要があります。
以下の作業はRaiden Networkの公式ドキュメントを最大限に活用させていただきました。
http://raiden-network.readthedocs.io/en/stable/api_walkthrough.html


- Aをconnectionする

```
$ curl -X PUT -H "Content-Type: application/json" -d '{"funds":100}' http://127.0.0.1:9545/api/1/connections/0x0f114a1e9db192502e7856309cc899952b3db1ed
```

（ログ）

```
2018-06-05 06:21:33 [info     ] 127.0.0.1 - - [2018-06-05 15:21:33] "PUT /api/1/connections/0x0f114a1e9db192502e7856309cc899952b3db1ed HTTP/1.1" 204 143 0.003032
```

- Bをconnectionする

```
$ curl -X PUT -H "Content-Type: application/json" -d '{"funds":100}' http://127.0.0.1:9546/api/1/connections/0x0f114a1e9db192502e7856309cc899952b3db1ed
```

(ログ）

```
2018-06-05 06:27:57 [info     ] 127.0.0.1 - - [2018-06-05 15:27:57] "PUT /api/1/connections/0x0f114a1e9db192502e7856309cc899952b3db1ed HTTP/1.1" 204 143 0.003706
```

#6. ノード間でチャネルを作成する
ネットワークに参加できたら、AとBの間でチャネルを作成します。

```
$ curl -X PUT -H "Content-Type: application/json" -d '{"partner_address": "0x50624159646b7a844647542e01ad2fb59e8969d2","token_address": "0x0f114a1e9db192502e7856309cc899952b3db1ed","balance": 100,"settle_timeout": 600}' http://127.0.0.1:9545/api/1/channels
```

なお、ここまでできたら、最新状態を反映させるために、ノードを再起動する必要があるようです。

再起動後にチャネルの情報を確認してみます。

```
$ curl -X GET http://127.0.0.1:9545/api/1/channels
{
"state": "opened",
"reveal_timeout": 10,
"balance": 100,
"token_address": "0x0f114a1e9db192502e7856309cc899952b3db1ed",
"settle_timeout": 600,
"partner_address": "0x50624159646b7a844647542e01ad2fb59e8969d2",
"channel_address": "0x53d98c5dbd0602fa1b21defa07170910d18fdc2f"
}

curl -X GET http://127.0.0.1:9546/api/1/channels
{
"settle_timeout": 600,
"reveal_timeout": 10,
"partner_address": "0xc1c4317e06f62d6378370ec66da5d662b3e81ea6",
"balance": 0,
"state": "opened",
"channel_address": "0x53d98c5dbd0602fa1b21defa07170910d18fdc2f",
"token_address": "0x0f114a1e9db192502e7856309cc899952b3db1ed"
}
```

これで、送金のための準備は完了です。

#7. RTTを送金する
では、いよいよ送金を実行します。

```
curl -H "Content-Type:application/json" -X POST --data '{"amount":1, "identifer": 42}' http://127.0.0.1:9545/api/1/transfers/0x0f114a1e9db192502e7856309cc899952b3db1ed/0x50624159646b7a844647542e01ad2fb59e8969d2

{"target_address": "0x50624159646b7a844647542e01ad2fb59e8969d2", "token_address": "0x0f114a1e9db192502e7856309cc899952b3db1ed", "identifier": 1037187736605808637, "amount": 1, "initiator_address": "0xc1c4317e06f62d6378370ec66da5d662b3e81ea6"}
```

無事、1RTTの送金ができました！！

チャネルの情報も確認してみます。

```
curl -X GET http://127.0.0.1:9545/api/1/channels

{
"balance": 99,
"settle_timeout": 600,
"state": "opened",
"partner_address": "0x50624159646b7a844647542e01ad2fb59e8969d2",
"token_address": "0x0f114a1e9db192502e7856309cc899952b3db1ed",
"reveal_timeout": 10,
"channel_address": "0x53d98c5dbd0602fa1b21defa07170910d18fdc2f"
}

curl -X GET http://127.0.0.1:9546/api/1/channels

{
"partner_address": "0xc1c4317e06f62d6378370ec66da5d662b3e81ea6",
"balance": 1,
"channel_address": "0x53d98c5dbd0602fa1b21defa07170910d18fdc2f",
"token_address": "0x0f114a1e9db192502e7856309cc899952b3db1ed",
"settle_timeout": 600,
"state": "opened",
"reveal_timeout": 10
}
```

ちなみに、何度か送金を試していると以下のように"is not reachable"や"doesnt have enough funds"といったログ吐き出されます。

```
2018-06-06 00:42:29 [info     ] partner for channel c1c4317e - 02f4b6bc is not reachable, ignoring [raiden.routing] 
2018-06-06 00:42:29 [info     ] channel c1c4317e - 2762318d doesnt have enough funds [1], ignoring [raiden.routing] 
2018-06-06 00:42:29 [info     ] channel c1c4317e - 5fe5696f doesnt have enough funds [1], ignoring [raiden.routing] 
2018-06-06 00:42:29 [info     ] channel c1c4317e - 6d04b0d8 doesnt have enough funds [1], ignoring [raiden.routing] 
2018-06-06 00:42:31 [info     ] 127.0.0.1 - - [2018-06-06 09:42:31] "POST /api/1/transfers/0x0f114a1e9db192502e7856309cc899952b3db1ed/0x50624159646b7a844647542e01ad2fb59e8969d2 HTTP/1.1" 200 379 2.010498
```

エラーになって、送金に失敗したのかな〜と思ったら、最後にレスポンスが200で返ってきて、送金が完了しました。
なぜ、このようなログが出るのか、謎です...

#8. その他（RTTをデポジットする）
ちなみに、チャネルを作成した後にも以下のAPIを使えば、トークンをデポジットできます。

```
curl -X PATCH -H "Content-Type: application/json" -d '{"balance":100}' http://127.0.0.1:9545/api/1/channels/0x53d98c5dbd0602fa1b21defa07170910d18fdc2f
```

デポジット自体は成功しますが、以下のように"unexpected exception"が表示されたりして、ログが読みづらいです...


```
2018-06-05 06:42:50 [info     ] deposit called                 [raiden.network.proxies.netting_channel] amount=100 contract=53d98c5d node=c1c4317e
2018-06-05 06:43:02 [info     ] deposit successful             [raiden.network.proxies.netting_channel] amount=100 contract=53d98c5d node=c1c4317e
2018-06-05 06:43:02 [exception] unexpected exception on alarm  [raiden.tasks] 
Traceback (most recent call last):
  File "/Users/user1/Desktop/workspace/raidenenv/raiden/raiden/tasks.py", line 97, in poll_for_new_block
    result = callback(current_block)
  File "/Users/user1/Desktop/workspace/raidenenv/raiden/raiden/raiden_service.py", line 384, in poll_blockchain_events
    for event in self.blockchain_events.poll_blockchain_events():
  File "/Users/user1/Desktop/workspace/raidenenv/raiden/raiden/blockchain/events.py", line 280, in poll_blockchain_events
    for event in self.poll_all_event_listeners(from_block):
  File "/Users/user1/Desktop/workspace/raidenenv/raiden/raiden/blockchain/events.py", line 249, in poll_all_event_listeners
    event_listener.abi,
  File "/Users/user1/Desktop/workspace/raidenenv/raiden/raiden/blockchain/events.py", line 41, in poll_event_listener
    log_event['event_data']
  File "/Users/user1/Desktop/workspace/raidenenv/raiden/raiden/network/rpc/smartcontract_proxy.py", line 31, in decode_event
    event_abi = topic_to_event_abi[event_id]
KeyError: HexBytes('0x9cb02993ef7311b37acc6bdfc1a8397160be258a877d78b31f4e366caf7bfcef')
```

#9. まとめ
今回はRaidenのノードをローカルに2つ立てて、送金処理の実行まで試してみました。

Ethereumのスケーラビリティ問題を解決する有力なソリューションの1つではありますが、正直、現在のバージョンでは挙動が不安定だったり、出力されるログが読みづらかったりと、まだまだ本番で使えるものではないな〜という印象を受けました。（v0.3なので当たり前だ！！）

今後の開発に期待です！！！