F#を使って超高速ブロックチェーンの Hedera Hashgraph のウォレットを作ってみた



# はじめに

UL Systems Advent Calendar 2019 の12/14の記事になります。

今年ももうすぐ終わりに近づいて参りました。
振り返ってみると、まったくもって個人的な話ですが、bitcoin、ブロックチェーン周りの技術を扱ってみたり、F#で新規サービスを構築する、なんてチャレンジングな取り組みに関わってきました。

ということで、F#を使い、高速ブロックチェーン（正確には分散管理台帳）のHedera Hashgraph へアクセスし、仮想通貨のやりとりをするプログラムを作ってみましたので紹介します。

# F# とは

マイクロソフトが作ったプログラミング言語です。.Netです。関数型プログラミング言語でありつつ、実行環境は.Netなので、これまで積み上げてきたライブラリ等の資産をそのまま活用することができます。これはデカい。

逆に、関数型プログラミング言語でありつつも、.Netのライブラリも使えるもんだからい様々な書き型が出来てしまい、いろいろと迷う、というデメリットもあります。まあ、決めればいいってものですが。

# Hedera Hashgraph とは

ヘデラ社が公開しているオープンな分散管理台帳技術です。冒頭で「正確には・・・」とつけましたが、ブロックチェーンではなく独自の「ハッシュグラフ」というデータ構造を持っています。
Goshipプロトコル、仮想投票というハッシュグラフ独自のアルゴリズムを採用することで、ブロックチェーンに代表される他の分散管理台帳で課題となっている以下の点を解決しています。

- 厳密なファイナリティ：ブロックチェーンはマイナーの手によってチェーンが分岐することがあり、確率的には取引が破棄される可能性がゼロにはならない
- 承認速度：ブロックチェーンではブロックが格納されるまで取引が承認されない。
- 承認順序：ブロックチェーンではブロックに格納されるまで取引の順序が確定しない

詳しくは、以下の公式サイトをご参照ください。

本家サイト
https://www.hedera.com/

日本語サイト
https://www.hedera.asia/

# 準備

プログラムを作る前に必要なアカウントの取得やSDKのインストールを実施します。

## Hedera Hashgraphのアカウントを取得

Hedera Hashgraphは最近までクローズドな環境のみだったのですが、今年の9月からオープンアクセスが開始しました。
自分の仮想通貨を持つためにはアカウントが必要です。自分のアカウントは以下のサイトで作成します。
https://portal.hedera.com/register

アカウントの作成にはスマホからのアクセスと運転免許証などの登録（KYCプロセス）がありますので準備をお願いします。

## 鍵の生成と登録

### 鍵の生成

アカウントが作成できたら鍵を生成して登録します。
鍵の生成にはツールが提供されているので、以下のコマンドで実行します。

```
> git pull https://github.com/hashgraph/hedera-keygen-java
> java -jar hedera-sdk-keygen-1.3-run.jar
```

GUIツールが立ち上がるので「Generate Key Pair」タブの「Generate」ボタンを押下し、鍵情報を生成します。

### 鍵の登録

ポータルサイト（https://portal.hedera.com）に自分の公開鍵情報を登録します。
公開鍵は先ほど生成した「Public Key Encoded」の項目の文字列を登録します。

ポータルサイトのYOUR HEDERA ACCOUNTSの欄にPUBLIC KEYが表示されれば登録完了です。

## Hedera Hashgraph .Net SDK

続いてアプリケーションからHedera HashgraphにアクセスするためのSDKをインストールします。
今回はF#でプログラムを書くので.Net Core 環境からHedera Hashgraphを操作するためのSDKを利用します。.Net版のSDKはHederaの公式なSDKではなく、コミュニティが作っています。

https://github.com/bugbytesinc/Hashgraph

## プロジェクトの作成

それではさっそくF#でHedera Hashgraph を使ったアプリを作っていきましょう。
はじめにF#のコンソールプロジェクトを作成します

```
C:\develop\hashgraph\ws\FsharpWallet>dotnet new console -lang F#
```

以下のような表示が出れば作成完了です。

```
The template "Console Application" was created successfully.

Processing post-creation actions...
Running 'dotnet restore' on C:\develop\hashgraph\ws\FsharpWallet\FsharpWallet.fsproj...
  C:\develop\hashgraph\ws\FsharpWallet\FsharpWallet.fsproj の復元が 202.37 ms で完了しました。

Restore succeeded.
```

続いて、前述のSDKをプロジェクトに追加します。

```
C:\develop\hashgraph\ws\FsharpWallet>dotnet add package Hashgraph
  Writing C:\Users\kei.akashi\AppData\Local\Temp\tmp114C.tmp
info : パッケージ 'Hashgraph' の PackageReference をプロジェクト 'C:\develop\hashgraph\ws\FsharpWallet\FsharpWallet.fsproj' に追加しています。
info : C:\develop\hashgraph\ws\FsharpWallet\FsharpWallet.fsproj のパッケージを復元しています...
info :   GET https://api.nuget.org/v3-flatcontainer/hashgraph/index.json
info :   OK https://api.nuget.org/v3-flatcontainer/hashgraph/index.json 598 ミリ秒
～中略～
info : 復元をコミットしています...
info : アセット ファイルをディスクに書き込んでいます。パス: C:\develop\hashgraph\ws\FsharpWallet\obj\project.assets.json
log  : C:\develop\hashgraph\ws\FsharpWallet\FsharpWallet.fsproj の復元が 1.15 min で完了しました。

```

一旦、実行してみるとHello Worldが出力されます。

```
C:\develop\hashgraph\ws\FsharpWallet>dotnet run
Hello World from F#!
```

# ウォレット機能の開発

ここまでで F# でHedera Hashgraphにアクセスする準備が整いました。
ウォレット機能の開発をはじめます。
ウォレットに必要な機能は、
- 残高照会
- 出金
だけです。


## 手始めに残高照会

多くのブロックチェーンでは、アドレスという口座に該当する概念があり、それぞれのアドレスがどのくらい残高を持っているかを容易に調べることができます。

残高照会するための関数を作成してみました。
引数の意味は以下の通りです。

- gatewayUrl: Hedera Hashgraphに接続するためのゲートウェイのURL(2.testnet.hedera.com:50211など)
- gatewayAccountNo: Hedera Hashgraphに接続するためのゲートウェイアカウント(5など)
- queryAccountNo: 残高を確認したい口座番号など

```
let balance gatewayUrl gatewayAccountNo queryAccountNo =
    let client = new Client(fun ctx -> 
        ctx.Gateway <- new Gateway(gatewayUrl, int64(0), int64(0), gatewayAccountNo)
    )

    let account = new Address(int64(0), int64(0), queryAccountNo)
    let balance = client.GetAccountBalanceAsync(account) |> Async.AwaitTask |> Async.RunSynchronously
   
    printfn "Account Balance for %d is %d tinybars." account.AccountNum balance
```

残高を取得するために、まずはHedera Hashgraph のゲートウェイに接続します。
以下のコードでclientオブジェクトがネットワークに接続するためのクライアントにあたり、接続先となるゲートウェイ情報を設定しています。

```
let client = new Client(fun ctx -> 
    ctx.Gateway <- new Gateway(gatewayUrl, int64(0), int64(0), gatewayAccountNo)
)
```


続いて、`client.GetAccountBalanceAsync` を使って残高を問い合わせます。

```
let account = new Address(int64(0), int64(0), queryAccountNo)
let balance = client.GetAccountBalanceAsync(account) |> Async.AwaitTask |> Async.RunSynchronously
   
printfn "Account Balance for %d is %d tinybars." account.AccountNum balance

```

実行すると以下のように残高が出力されます。

```
Account Balance for 14680 is 100498132010 tinybars.
```

## 出金

続いて支払いを表す出金です。

出金は支払先の口座を表すアドレス番号と、支払元のアドレス番号に対応する秘密鍵情報が必要です。

出金のためのtransfer関数を用意しました。
引数の意味は以下の通りです。

- gatewayUrl: Hedera Hashgraphに接続するためのゲートウェイのURL(2.testnet.hedera.com:50211など)
- gatewayAccountNo: Hedera Hashgraphに接続するためのゲートウェイアカウント(5など)
- payerAccountNo: 支払元となるアドレス番号
- payerPrivateKey: 支払元のアドレスに対応する秘密鍵文字列
- receiptantAccountNo: 支払先のアドレス番号
- ammount: 支払額

```
let transfer gatewayUrl gatewayAccountNo payerAccountNo (payerPrivateKey:ReadOnlyMemory<byte>) receiptantAccountNo amount=

    let payer = new Address(int64(0), int64(0), payerAccountNo)
    let client = new Client(fun ctx -> 
        ctx.Gateway <- new Gateway(gatewayUrl, int64(0), int64(0), gatewayAccountNo)
        ctx.Payer <- payer
        ctx.Signatory <- new Signatory(payerPrivateKey)
    )

    let receiptant= new Address(int64(0), int64(0), receiptantAccountNo)
    let transactionReceipt = client.TransferAsync(payer, receiptant, amount) |> Async.AwaitTask |> Async.RunSynchronously

    printfn "status:%s" (transactionReceipt.Status.ToString())
```


出金の場合は、HederaHashgraphに接続するためのclientオブジェクトの初期化時にゲートウェイの情報と支払い元の口座番号と秘密鍵の情報を設定しています。

```
let payer = new Address(int64(0), int64(0), payerAccountNo)
let client = new Client(fun ctx -> 
    ctx.Gateway <- new Gateway(gatewayUrl, int64(0), int64(0), gatewayAccountNo)
    ctx.Payer <- payer
    ctx.Signatory <- new Signatory(payerPrivateKey)
)
```

続いて、`client.TransferAsync` を使って出金処理を行います。

```
let receiptant= new Address(int64(0), int64(0), receiptantAccountNo)
let transactionReceipt = client.TransferAsync(payer, receiptant, amount) |> Async.AwaitTask |> Async.RunSynchronously
```

実行すると、以下のように出力されます。

```
Status:Success
```

# 動かしてみよう

残高照会と出金処理を起動引数で切り替えられるようにプログラムを書いてみました。

```
open System
open Hashgraph

let balance gatewayUrl gatewayAccountNo queryAccountNo =
    let client = new Client(fun ctx -> 
        ctx.Gateway <- new Gateway(gatewayUrl, int64(0), int64(0), gatewayAccountNo)
    )

    let account = new Address(int64(0), int64(0), queryAccountNo)
    let balance = client.GetAccountBalanceAsync(account) |> Async.AwaitTask |> Async.RunSynchronously
   
    printfn "Account Balance for %d is %d tinybars." account.AccountNum balance

let transfer gatewayUrl gatewayAccountNo payerAccountNo (payerPrivateKey:ReadOnlyMemory<byte>) receiptantAccountNo amount=

    let payer = new Address(int64(0), int64(0), payerAccountNo)
    let receiptant= new Address(int64(0), int64(0), receiptantAccountNo)

    let client = new Client(fun ctx -> 
        ctx.Gateway <- new Gateway(gatewayUrl, int64(0), int64(0), gatewayAccountNo)
        ctx.Payer <- payer
        ctx.Signatory <- new Signatory(payerPrivateKey)
    )
    let transactionReceipt = client.TransferAsync(payer, receiptant, amount) |> Async.AwaitTask |> Async.RunSynchronously
    //let transactionReceipt = client.TransferAsync(payer,receiptant,amount, new Signatory(payerPrivateKey)) |> Async.AwaitTask |> Async.RunSynchronously
    printfn "status:%s" (transactionReceipt.Status.ToString())

[<EntryPoint>]
let main argv = 

    printfn "Hello Hedera Hashgraph from F#!"

    match argv.[0] with
    | "balance" ->
        balance argv.[1] (int64(argv.[2])) (int64(argv.[3]))
    | "transfer" ->
        transfer argv.[1] (int64(argv.[2])) (int64(argv.[3])) (Hex.ToBytes(argv.[4])) (int64(argv.[5])) (int64(argv.[6]))
    | command ->
        printfn "Invalid command:%s" command

    0 // return an integer exit code  
```

上記のプログラムを使って、出金の前後で残高が変わるのを確認します。
以下のように実行します。

```
C:\develop\hashgraph\ws\FsharpWallet>dotnet run balance 2.testnet.hedera.com:50211 5 1
Hello Hedera Hashgraph from F#!
Account Balance for 1 is 6863528961 tinybars.

C:\develop\hashgraph\ws\FsharpWallet>dotnet run transfer 2.testnet.hedera.com:50211 5 14680 302e02010030～中略～ 1 1000
Hello Hedera Hashgraph from F#!
status:Success

C:\develop\hashgraph\ws\FsharpWallet>dotnet run balance 2.testnet.hedera.com:50211 5 1
Hello Hedera Hashgraph from F#!
Account Balance for 1 is 6863529961 tinybars.
```

出金前の残高照会でアドレス番号1の口座に「6863528961」残高が残っています。
出金処理でアドレス番号1の口座に「1000」の仮想通貨を送ります。
出金後の残高照会でアドレス番号1の口座に「6863529961」残高が残っています。出金処理を行う前に比べて「1000」増えていることが確認できます。

# まとめ

Hedera Hashgraph を F#を使って操作するウォレットの機能を開発してみました。
Hedera Hashgraph の.NetのSDKを使うと非常に簡単に開発ができることがお分かりいただけたと思います。ブロックチェーンや分散台帳技術はその仕組みは複雑ですが、単純に利用するだけであれば、実は中身の仕組みはあまり意識しなくても利用することができます。こういったSDKがもっと知られることによって、まだまだ浸透していない仮想通貨の利用も広がっていくのではないでしょうか。

Hedera Hashgraphは今回紹介した仮想通貨の機能だけでなく、スマートコントラクトやファイルといったAPIを備えてます。今年のオープンアクセスと共にHedera Hashgraphを使った様々なサービスが公開されています。機会があれば他のAPIの解説もしてみようかな、と思ってます。

また、今回はF#ならではの書きっぷりというか使い方はあまりしていません。こちらもどこかでご紹介する機会があったらな、と。

ではでは。