[Java] Reactive-Functional Fun on the Blockchain with web3j

原文はこちら。
https://community.oracle.com/docs/DOC-1011373

著者(Conor Svensson)について
Conor Svensson (@conors10) はweb3jというEthereumブロックチェーンとアプリケーションを統合するJavaライブラリの作者です。以前はcoHomeとHuffleというスタートアップの共同創業者で、その後、OtheraでCTOを務め、Otheraのブロックチェーン貸与プラットフォームおよび交換プラットフォームを構築しています。
coHome
https://www.cohome.co/
Huffle Home Loans
https://www.huffle.com.au/
Othera Group
https://www.othera.com.au/
また、Sydney Java User Groupの主催者の一人であり、よくスピーカーとして登場します。彼はテクノロジーとファイナンスに関するブログを書いており、ディスプレイの前にいないときは地元シドニーのMaroubraというビーチでサーフィンを楽しんでいます。

Java Magazine 2017年1/2月号で、ブロックチェーンテクノロジのEthereumを使って、その上でJavaアプリケーションを作成するためにweb3jの利用に関する入門記事を寄稿しています。
Java Magazine 2017年1月/2月号
http://www.javamagazine.mozaicreader.com/JanFeb2017/Default/36/0#&pageSet=36&page=0&contentItem=0
Ethereum Project
https://www.ethereum.org/
web3j - Lightweight Ethereum Java and Android integration library
https://web3j.io/
この記事では、web3jのreactive-functional APIを使用して、パブリックなEthereumブロックチェーンで発生するイベントを見る方法について説明します。

ここで記載したコードは全て以下のGitHubにUpしてあります。
web3j/examples
https://github.com/web3j/examples/tree/master/rx

Background

過去1年間、テクノロジーとFinancialの報道ではブロックチェーンとその破壊的な可能性でもちきりでした。ブロックチェーンは分散型の不変(Immutable)のデータストアです。不変ゆえに、データはブロックチェーンに追加することしかできません。これは、ブロックにグループ化されたトランザクションを使って実現されています。そのため、データはブロックチェーンの最後に追加されます。
f1.png
Figure 1. ブロックチェーンの構造
ブロックチェーンに存在するデータの状態は、以前発生したトランザクションやイベントを再生して構築されるため、分散イベントログのように考えることができます。ブロックチェーンは、完全に分散化されており、インターネットのような信頼できないパブリック環境で潜在的に関連のないノードに格納されます。さまざまなブロックチェーンテクノロジが存在しています中で、Ethereumは特にパブリックなブロックチェーン技術として生まれました。

ブロックチェーン技術の詳細については、Java Magazineの記事を参照してください)。
Scala: Deeply Functional, Purely Object-Oriented
http://www.javamagazine.mozaicreader.com/JanFeb2017/Default/47/0#&pageSet=36&page=0

Getting Started with Ethereum

分散化されたEthereumネットワークは、Ethereumネットワークのピア(Peer)を形成するクライアントから構成されています。ネットワークと対話するためには、これらの一つのクライアントにアクセスする必要があります。一番簡単な方法は、自身でクライアントを実行することです。2個の主要なクライアントは、GethとParityです。
ethereum/go-ethereum - Building Ethereum
https://github.com/ethereum/go-ethereum/wiki/Building-Ethereum
ethcore/parity - Setup
https://github.com/ethcore/parity/wiki/Setup
クライアントをインストール後、以下のようにしてクライアントを起動できます。
# Geth:
$ geth --fast --cache=512 --rpcapi personal,db,eth,net,web3 --rpc --testnet

# Parity:
$ parity --chain testnet
クライアントは他のノードを見つけて接続し、testnetブロックチェーンのローカルコピーの同期を開始します。mainnetとtestnetという、2個のパブリックなEthereumブロックチェーンがあり、それぞれ本番環境、テスト環境に相当します。

The web3j Library

クライアントを実行したので、Ethereumブロックチェーン上でクライアントと連携する、軽量なJavaライブラリのweb3jを使うと、Ethereumブロックチェーンと対話を始めるのは簡単です。web3jについて詳細を知りたい方は、以下のURLをご覧ください。
web3j - Lightweight Ethereum Java and Android integration library
https://web3j.io

Figure 2. web3jはJavaアプリケーションのためにEthereumブロックチェーンへの統合レイヤーを提供する
プロジェクトにweb3jを取り込むには、以下のように依存性をpom.xmlに追加します。
<dependency>
  <groupId>org.web3j</groupId>
  <artifactId>core</artifactId>
  <version>2.0.0</version>
</dependency>
Androidで作成しようとしているのであれば、 core-android を使ってください。

New Block Subscriptions

数行のコードで、Ethereumブロックチェーンに接続し、新しいブロックがブロックチェーンに追加されたことの通知を受けることができます。
Web3j web3 = Web3j.build(new HttpService());  // defaults to http://localhost:8545/
Subscription subscription = web3j.blockObservable(false)
                                 .subscribe( block -> {
                                                        System.out.println( "Sweet, block number " 
                                                                            + block.getBlock().getNumber()
                                                                            + " has just been created" );
                                                      },
                                             Throwable::printStackTrace );
TimeUnit.MINUTES.sleep(2);
subscription.unsubscribe();
blockObservable は、新しいブロックがEthereumブロックチェーンに追加される都度、ブロックオブジェクトをサブスクライバーに送信します。

プログラムの残りの部分とは異なる実行スレッドで非同期にサブスクリプションは実行するため、サブスクリプションにはsleepステートメントを含める必要があります。

各ブロックのトランザクション数の詳細と、ブロックを一意に識別するブロックハッシュ、前のブロックの親ハッシュをそれぞれ提供するために、この例に書き込みます。

この例を10ブロックに制限し、ラッチを使用して10ブロックが放出されるのを待ちます。
CountDownLatch countDownLatch = new CountDownLatch(1);
System.out.println("Waiting for " + COUNT + " transactions...");
Subscription subscription = web3j.blockObservable(true)
                                 .take(COUNT)
                                 .subscribe( ethBlock -> {
                                                 EthBlock.Block block = ethBlock.getBlock();
                                                 LocalDateTime timestamp = Instant.ofEpochSecond( block.getTimestamp()
                                                                                                     .longValueExact())
                                                                                                     .atZone(ZoneId.of("UTC"))
                                                                                                     .toLocalDateTime();
                                                 int transactionCount = block.getTransactions().size();
                                                 String hash = block.getHash();
                                                 String parentHash = block.getParentHash();
                                                 System.out.println( timestamp + " Tx count: " +
                                                                     transactionCount + ", Hash: " +
                                                                     hash + ", Parent hash: " + 
                                                                     parentHash );
                                                 countDownLatch.countDown();
                                              },
                                              Throwable::printStackTrace);
subscription.unsubscribe();
コードを実行すると、直近にEthereumブロックチェーンに追加された10個のブロックの詳細を確認できます。
2016-12-22T00:27:11 Tx count: 3, Hash: 0xbed93a59bcd30c0f11e155109a5dbff3e56a5354e90e514108917c1f54c4182d, Parent hash: 0x74f8bffd8734833a2fbaad0d9e48eb6206ee25dc9c55425cf1edca9dfe45aa06
2016-12-22T00:27:44 Tx count: 2, Hash: 0xb147a4ca79bc7c5e767ee92be648a9fd23ee15ec64f764b3f2dc8a4cb7229a50, Parent hash: 0xbed93a59bcd30c0f11e155109a5dbff3e56a5354e90e514108917c1f54c4182d
2016-12-22T00:27:51 Tx count: 1, Hash: 0xa2d8d3572592470e30b7fb504ce504444b1ea1208035f1545c85fc824a882e1b, Parent hash: 0xb147a4ca79bc7c5e767ee92be648a9fd23ee15ec64f764b3f2dc8a4cb7229a50
2016-12-22T00:28:09 Tx count: 2, Hash: 0xf4d4a7d7a2202f8c64e72b47947adb5ae419f2bdadabec9386cb9d4a27cd7ef8, Parent hash: 0xa2d8d3572592470e30b7fb504ce504444b1ea1208035f1545c85fc824a882e1b
2016-12-22T00:28:17 Tx count: 2, Hash: 0x43dc1da772b3f5aea3388fb4ddf14c188753646de4971b62e90b903774c5c2bc, Parent hash: 0xf4d4a7d7a2202f8c64e72b47947adb5ae419f2bdadabec9386cb9d4a27cd7ef8
2016-12-22T00:28:22 Tx count: 1, Hash: 0x80d03ac26a3aba27d01beecb530b9055dd28b199a52bcc53704c8e418ba437fa, Parent hash: 0x43dc1da772b3f5aea3388fb4ddf14c188753646de4971b62e90b903774c5c2bc
2016-12-22T00:28:27 Tx count: 0, Hash: 0x897af69eafa47c2951379780a40ee47fc3383959197419a4feadc59d1d2b2c13, Parent hash: 0x80d03ac26a3aba27d01beecb530b9055dd28b199a52bcc53704c8e418ba437fa
2016-12-22T00:28:28 Tx count: 0, Hash: 0xf12762e30caa3d0f1c6c7deb7fff93cd00727565575b8b35a7466ca75ba112e6, Parent hash: 0x897af69eafa47c2951379780a40ee47fc3383959197419a4feadc59d1d2b2c13
2016-12-22T00:28:43 Tx count: 1, Hash: 0x3754841b596ad15b17b37dd6293db77ebb672743918877dcf92e47407a2153e5, Parent hash: 0xf12762e30caa3d0f1c6c7deb7fff93cd00727565575b8b35a7466ca75ba112e6
2016-12-22T00:28:49 Tx count: 2, Hash: 0x6613c62dfd05761b3794658d341c1dab95f19db1e2bebd8cf091861c1ae88cd4, Parent hash: 0x3754841b596ad15b17b37dd6293db77ebb672743918877dcf92e47407a2153e5

Functional Composition

RxJavaのObservableを使っているので、簡単に機能を追加できます。
Observable
http://reactivex.io/documentation/observable.html
例えば、ブロックチェーンに書き込まれた新しいトランザクション全て通知するObservableを作成する場合、 blockObservable() を再度利用し、ブロックに含まれているトランザクションのリストを展開し、flatMapIterable()メソッドを使ってこれらのトランザクションを個別に通知することができます。
web3j.blockObservable(true)
     .flatMapIterable( ethBlock -> (List) ethBlock.getBlock()
                                                  .getTransactions());
今回は、パラメータとして true を渡しています。これはブロックとそれらのブロックに含まれている全てのトランザクションの詳細を要求することを意味しています。

web3jはすでにweb3j.transactionObservable()でこの構成を実装済みです。
web3j/web3j - JsonRpc2_0Rx.java
https://github.com/web3j/web3j/blob/master/src/main/java/org/web3j/protocol/rx/JsonRpc2_0Rx.java#L66

Counting Ether

Ethereumには、Etherという名前の独自のcryptocurrency(仮想通貨)があり、Bitcoinと同様に考えることができます。Etherを使ってネットワーク上で発生する取引への支払いをします。これらの取引では、Etherをある人から他の人に転送することもできます。取引に関連するEtherの詳細はEthereumのtransactionの value フィールドに含まれています。

以前のセクションで作成したトランザクション Observable を使い、簡単にリアルタイムでブロックチェーンから情報を取得し始めることができます。例えば、指定したブロックの個数で発生した取引の総額を取得する場合、以下のように利用することができます。
CountDownLatch countDownLatch = new CountDownLatch(COUNT);

System.out.println("Waiting for " + COUNT + " transactions...");
Observable<BigInteger> transactionValue = web3j.transactionObservable()
                                               .take(COUNT)
                                               .map(Transaction::getValue)
                                               .reduce(BigInteger.ZERO, BigInteger::add);

Subscription subscription = transactionValue.subscribe(total -> {
                                    System.out.println( "Transaction value: " + Convert.fromWei( new BigDecimal(total), Convert.Unit.ETHER) + " Ether");
                                    countDownLatch.countDown();
                                },
                                Throwable::printStackTrace);

countDownLatch.await(10, TimeUnit.MINUTES);
subscription.unsubscribe();
上記コードを実行すると、以下のような結果を得ることができます。
Transaction value: 5.011 Ether (5011000000000000000 Wei)
ここでは、発行された各トランザクションの値を取得し、reduce()メソッドを使用してそれらを合計していますが、reduce関数が最終結果を提供するために、有限ストリームを扱う必要があることに注意してください。
Reduce
http://reactivex.io/documentation/operators/reduce.html
また、すべてのトランザクションがEtherの移転になるわけではないので、トランザクション値として0を受け取っても驚かないでください。

Etherは数多くの異なる単位があり、Weiはその中で最も細かいものです。1 Weiは10e-18 Etherです。ConvertメソッドはWeiの値をトランザクションからEtherに変換します。

Further Observables

他のobservableも利用できます。例えば、まだブロックにグループ化されておらず、ブロックチェーンに追加されていない、保留中のトランザクションの詳細を取得するには、次のようにします。
Subscription subscription = web3j.pendingTransactionObservable().subscribe(tx -> {
    ...
});
また、web3jは全てのEthereum API呼び出しのためのobservableも提供しています。以下は、Ethereumクライアントのバージョンを取得する簡単な例です。
Web3j web3 = Web3j.build(new HttpService());  // defaults to http://localhost:8545/
web3j.web3ClientVersion().observable().subscribe(x -> {
    System.out.println(x.getWeb3ClientVersion());
});
上記コードの実行例です。
Client is running version:     Geth/v1.5.4-stable-b70acf3c/darwin/go1.7.3
利用可能なAPI呼び出しのリストは、interface Ethereumをご覧ください。
Interface Ethereum
https://github.com/web3j/web3j/blob/master/src/main/java/org/web3j/protocol/core/Ethereum.java
web3jでサブスクライブ可能な追加のイベントタイプがあります。詳細は以下のURLをご覧ください。
Filters and Events
https://docs.web3j.io/filters.html

Conclusion

reactive-functional APIを使ってEthereumブロックチェーンへの問合せを実行するweb3jの機能の概要を説明してきました。web3jやEthereumの詳細を知りたい方は、以下のURLをご覧ください。たくさんのリソースがあります。
web3j - Lightweight Ethereum Java and Android integration library
https://web3j.io

0 件のコメント:

コメントを投稿