[Symbol] ノード運営者向けハーベスト還元ツールを開発しました

# 概要
- ハーベスト報酬の内ノード報酬をハーベスターに還元するノード運営者向けツールを開発してOSSとして公開しました
- 本記事では、実際のツールの導入・設定・実行について解説します。
    - [[Symbol] ハーベスト報酬99%還元ノード運営始めます](https://qiita.com/nobu_kyutech/items/19e406a00057f5b54c98) の続編です
- Symbol投げ銭プラットホームのQUESTも実施しました。
    - https://quest-bc.com/quest/zcCRqmz4SdtqWSO8EEhi

https://github.com/ancte0721/symbol-nodefee-kickback-tools

## ノード宣伝
- 「**symbol-node.takagi-tech.com**」
    - 本ツールを利用してノード報酬の **75%** をハーベストした方へ還元します
    - 残り **25%** はSymbol関連の開発や技術記事の執筆活動に充てます
    - 還元による高効率なハーベストをしたい方、自分の活動を応援してくださる方のハーベスト委任お待ちしております

# このツールでできること
自分のノード委任者に対して、「ノード報酬の〇〇%を還元します」や「ハーベスト時にボーナスで〇〇XYMをプレゼントします」といったハーベスターへの還元作業を自動で行えます。
具体的な機能としては、下記の通りです。

- 指定範囲のブロック高でハーベストしたアドレスへの送金
- 送金額に関して、ノード報酬に対する割合、または絶対値を任意に設定可能
- 送金あたりの限度額が設定でき、その金額以上の送金をプログラムが行おうとした場合、送金作業を停止
- 実際には送金しないが、どのアドレスにいくら送金するか事前に確認できるドライラン機能

# 事前準備
## 実行環境の準備
Python3.7以降でsymbol SDKをインストールします。

```sh
pip install symbol-sdk-core-python
```

## ツールのダウンロード
[githubリポジトリ](https://github.com/ancte0721/symbol-nodefee-kickback-tools)からダウンロードします。
gitを使い慣れている方はcloneして使ってください。
gitが使えない方は、下記の画像の赤枠「Code -> Download ZIP」からzipでダウンロードして任意の場所で解凍してください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/183156/23afa494-2b36-1be2-e8f2-0cdf074ba6d9.png)

# ツールの実行
## 設定方法
まずは `config.ini.example` ファイルをコピーして `config.ini` という名前に変更します。
次に、ご自身のノード情報や還元ルールに沿ってファイルの設定値を変更してください。
各項目の詳細はこのようになっています。

|設定変数  |概要  |入力値  |
|---|---|---|
|dryrun  |実際には送金処理を行わない確認モード<br>Falseで実際に送金まで行います  |True or False  |
|private_key  |ノード報酬の受け取りウォレットの秘密鍵<br>このウォレットで受け取った報酬が発生したハーベストが対象になります  |英数文字列  |
|limit_xym_amount|1送金あたりの最大XYM数量<br>これ以上の送金を検知した場合、処理を停止します<br>右の例「50.0」の場合、50XYM以上の送金が1件以上あれば送金を中止します|例) 50.0|
|from_block_height|還元対象のブロック高の最低値<br>(このブロック数以上のハーベストが対象)|例) 40000|
|to_block_height|還元対象のブロック高の最大値。0の場合は最新ブロック高<br>(このブロック数以下のハーベストが対象)|例) 50000|
|autoupdate_from_block_height|<b>from_block_height</b>の自動更新<br>（前回実行時の最終ハーベストブロック高+1に更新）|True or False|
|fixed_kickback|固定の還元XYM数量<br> (0より大きい値の場合、次項の<b>kickback_rate</b>は無視されます)|例) 0<br>例) 30.0|
|kickback_rate|ノード報酬の還元レート(<b>fixed_kickback</b>が0以下の場合のみ有効)<br>（75%還元の場合、0.75）|例) 0.75|
|max_txfee|還元トランザクションの最大手数料XYM数量|例) 1.0|
|message|還元トランザクションに添えるメッセージ<br> (#BLOCK# というワードがハーベストブロック高に置換されます)|例) node fee kickback [block:#BLOCK#]|
|node|ハーベスト還元対象のノード<br>（このノードに委任しているハーベスターのハーベストが還元対象になります）|例) http://xxx.yyy.zzz:3000|
|receipt_type_enum|ハーベストのシステム定数<br>（メインネットの場合は <b>0x2143</b> から変更不要）|0x2143|
|birth_time|Symbol誕生のUTC秒 <br>(メインネットの場合は<b>1615853185</b>から変更不要)|1615853185|
|exp_time|トランザクションの有効期限<br>(単位はhour。右の例だと2時間)|例) 2|
|mosaic_id|XYMのモザイクID <br>(メインネットの場合は<b>0x6BED913FA20223F8</b>から変更不要)|0x6BED913FA20223F8|


## 設定例
「<b>自身のノードでハーベストした人にノード報酬の50%を還元する</b>」という場合は下記のような設定になります。

```ini
dryrun = False
private_key = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
limit_xym_amount = 30
from_block_height = 470000
to_block_height = 0
autoupdate_from_block_height = True
fixed_kickback = 0
kickback_rate = 0.5
max_txfee = 5
message = !!!開発テスト!!! node fee kickback [#BLOCK#]
node = http://symbol-node.takagi-tech.com:3000
receipt_type_enum = 0x2143
birth_time = 1615853185
exp_time = 2
mosaic_id = 0x6BED913FA20223F8
```

また、 `autoupdate_from_block_height = True` とすることで、実行完了時に `from_block_height ` に還元が完了したブロック高+1が設定されるので、1日1回定期的に自動実行するだけで重複なく毎日のハーベストに対して還元送金ができます。

## 実行例
```
python nodefee-kickback.py
```

前項の設定例を確認モード `dryrun = True` で実行した際の出力はこのようになります。

```
> python nodefee-kickback.py
2021-09-05 01:41:31,300     INFO My public key: 10943DCA976F8C17433F759400B9AF0257D1B36E7D69859B31250039E771004D
2021-09-05 01:41:31,300     INFO My wallet address: NDJIK24FC643NUR3TMJ5LXALE3ZHDEUNX2VLE4Y
2021-09-05 01:41:35,512     INFO Harvesters address: 8
2021-09-05 01:41:39,835     INFO Harvest history from height: 470000
2021-09-05 01:41:39,835     INFO Harvest history to height: 0
2021-09-05 01:41:39,836     INFO ### block height, harvest address, node fee amount ###
2021-09-05 01:41:39,838     INFO ['490580', 'NCVQALLK55BPRMYEE4RTOFKDRTZEAQY63OW2FJA', '43.971249']
2021-09-05 01:41:39,838     INFO ['484878', 'NBWMZU7G7FUYOHVYEWOTKDYUX7KM6FNRBFFJU2Q', '43.971249']
2021-09-05 01:41:39,839     INFO ['481815', 'NDO4JAQV63NWMJRVYAPVT3ZM3EYCI7M5LV6YL5A', '43.972679']
2021-09-05 01:41:39,840     INFO ['472798', 'NC6KUFYNSAJPGHSEKDU4F3DFP2IBWCOAID76NEQ', '44.221161']
2021-09-05 01:41:39,841     INFO ['471447', 'NBWMZU7G7FUYOHVYEWOTKDYUX7KM6FNRBFFJU2Q', '43.971249']
2021-09-05 01:41:39,841     INFO Kickback transactions
2021-09-05 01:41:39,844     INFO ### target address, XYM amount, message ###
2021-09-05 01:41:39,844     INFO ['NCVQALLK55BPRMYEE4RTOFKDRTZEAQY63OW2FJA', 21.985624, '!!!開発テスト!!! node fee kickback [490580]']
2021-09-05 01:41:39,845     INFO ['NBWMZU7G7FUYOHVYEWOTKDYUX7KM6FNRBFFJU2Q', 21.985624, '!!!開発テスト!!! node fee kickback [484878]']
2021-09-05 01:41:39,846     INFO ['NDO4JAQV63NWMJRVYAPVT3ZM3EYCI7M5LV6YL5A', 21.986339, '!!!開発テスト!!! node fee kickback [481815]']
2021-09-05 01:41:39,846     INFO ['NC6KUFYNSAJPGHSEKDU4F3DFP2IBWCOAID76NEQ', 22.11058, '!!!開発テスト!!! node fee kickback [472798]']
2021-09-05 01:41:39,847     INFO ['NBWMZU7G7FUYOHVYEWOTKDYUX7KM6FNRBFFJU2Q', 21.985624, '!!!開発テスト!!! node fee kickback [471447]']
2021-09-05 01:41:39,849     INFO Total transfer XYM amount: 110.053791 + 5.0(Max fee)
2021-09-05 01:41:39,849     INFO ドライランのため送金を行わずに処理を終了します (config.ini -> dryrun)
```

ブロック高470000以降の5回のハーベスト（このハーベストで得られたノード報酬は43.971249～44.221161XYM）に対して、それぞれのハーベスターに対してノード報酬の50%（21.985624～22.11058XYM）を送金するリストの確認ができました。
この実行では `110.053791 + 5.0(Max fee) ` で最大115XYMを送金します。
確認して問題なければ、 `dryrun = False` にして、実際に送金処理を行います。
（次はテストのため、0.001XYMの固定還元（ `fixed_kickback = 0.001` ）にて送金しています）

```
> python nodefee-kickback.py
2021-09-05 01:51:30,396     INFO My public key: 10943DCA976F8C17433F759400B9AF0257D1B36E7D69859B31250039E771004D
2021-09-05 01:51:30,397     INFO My wallet address: NDJIK24FC643NUR3TMJ5LXALE3ZHDEUNX2VLE4Y
2021-09-05 01:51:34,614     INFO Harvesters address: 8
2021-09-05 01:51:39,004     INFO Harvest history from height: 470000
2021-09-05 01:51:39,005     INFO Harvest history to height: 0
2021-09-05 01:51:39,006     INFO ### block height, harvest address, node fee amount ###
2021-09-05 01:51:39,007     INFO ['490580', 'NCVQALLK55BPRMYEE4RTOFKDRTZEAQY63OW2FJA', '43.971249']
2021-09-05 01:51:39,007     INFO ['484878', 'NBWMZU7G7FUYOHVYEWOTKDYUX7KM6FNRBFFJU2Q', '43.971249']
2021-09-05 01:51:39,008     INFO ['481815', 'NDO4JAQV63NWMJRVYAPVT3ZM3EYCI7M5LV6YL5A', '43.972679']
2021-09-05 01:51:39,009     INFO ['472798', 'NC6KUFYNSAJPGHSEKDU4F3DFP2IBWCOAID76NEQ', '44.221161']
2021-09-05 01:51:39,011     INFO ['471447', 'NBWMZU7G7FUYOHVYEWOTKDYUX7KM6FNRBFFJU2Q', '43.971249']
2021-09-05 01:51:39,012     INFO Kickback transactions
2021-09-05 01:51:39,012     INFO ### target address, XYM amount, message ###
2021-09-05 01:51:39,013     INFO ['NCVQALLK55BPRMYEE4RTOFKDRTZEAQY63OW2FJA', 0.001, '!!!開発テスト!!! node fee kickback [490580]']
2021-09-05 01:51:39,014     INFO ['NBWMZU7G7FUYOHVYEWOTKDYUX7KM6FNRBFFJU2Q', 0.001, '!!!開発テスト!!! node fee kickback [484878]']
2021-09-05 01:51:39,014     INFO ['NDO4JAQV63NWMJRVYAPVT3ZM3EYCI7M5LV6YL5A', 0.001, '!!!開発テスト!!! node fee kickback [481815]']
2021-09-05 01:51:39,015     INFO ['NC6KUFYNSAJPGHSEKDU4F3DFP2IBWCOAID76NEQ', 0.001, '!!!開発テスト!!! node fee kickback [472798]']
2021-09-05 01:51:39,015     INFO ['NBWMZU7G7FUYOHVYEWOTKDYUX7KM6FNRBFFJU2Q', 0.001, '!!!開発テスト!!! node fee kickback [471447]']
2021-09-05 01:51:39,016     INFO Total transfer XYM amount: 0.005 + 0.02(Max fee)
2021-09-05 01:51:39,017     INFO Transaction hash: A92BE029BA9A0D5AAB87A4F6F7EB2F85DF007FAA5F0A7144CE7E9249382BA878
2021-09-05 01:51:39,034     INFO Published : http://explorer.symbolblockchain.io/transactions/A92BE029BA9A0D5AAB87A4F6F7EB2F85DF007FAA5F0A7144CE7E9249382BA878
2021-09-05 01:51:39,034     INFO from_block_height update to 490581
2021-09-05 01:51:39,035     INFO Node fee kickback is completed
```
[トランザクションハッシュと確認のURL](http://explorer.symbolblockchain.io/transactions/A92BE029BA9A0D5AAB87A4F6F7EB2F85DF007FAA5F0A7144CE7E9249382BA878)が出力されています。
5件のトランザクションを含むアグリゲートトランザクションとなっており、ウォレットからも還元が確認できます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/183156/c3addea8-0cc1-cefd-4b18-8a803dc5d1ba.png)

`from_block_height update to 490581` という出力の通り、 `config.ini` の `from_block_height` 項目が更新されており、そのまま再度実行しても、新しいハーベストが発生していないので下記の通りエラーで終了になります。

```
> python nodefee-kickback.py
2021-09-05 01:59:42,190     INFO My public key: 10943DCA976F8C17433F759400B9AF0257D1B36E7D69859B31250039E771004D
2021-09-05 01:59:42,190     INFO My wallet address: NDJIK24FC643NUR3TMJ5LXALE3ZHDEUNX2VLE4Y
2021-09-05 01:59:46,413     INFO Harvesters address: 8
2021-09-05 01:59:50,640     INFO Harvest history from height: 490581
2021-09-05 01:59:50,640     INFO Harvest history to height: 0
2021-09-05 01:59:50,641     INFO ### block height, harvest address, node fee amount ###
2021-09-05 01:59:50,642     INFO Kickback transactions
2021-09-05 01:59:50,643     INFO ### target address, XYM amount, message ###
2021-09-05 01:59:50,644     INFO Total transfer XYM amount: 0.0 + 0.02(Max fee)
2021-09-05 01:59:50,644     INFO There is no transfer
Traceback (most recent call last):
  File "nodefee-kickback.py", line 103, in <module>
    main()
  File "nodefee-kickback.py", line 81, in main
    raise Exception("送金対象がありません、送金処理を終了します")
Exception: 送金対象がありません、送金処理を終了します
```

定期的にプログラムを実行することで、ハーベストが発生したときに正しく還元処理が動きます。

## 自動実行について
Linux系であれば `cron` など、Windowsであれば `Windowsタスクスケジューラ` に登録するなどすれば、還元処理を完全自動化できます。
具体的な設定方法についてはここでは割愛いたします。

## 実行ログについて
プログラム実行時に出力されている情報はそのまま `logs` ディレクトリに実行日時のファイルとして出力されています。

# 本ツールのライセンスについて
[MIT License](https://github.com/ancte0721/symbol-nodefee-kickback-tools/blob/main/LICENSE)でOSSとして公開しています。
MIT Licenseはざっくりと言うと

- 改変・再配布・商用利用・有料販売どんなことにでも自由に無料で使えます
- ただし、「著作権表示」と「MITライセンスの全文」を記載する必要があります
    - MITライセンスの全文が記載されているウェブページのURLでもOK
    - 本プログラムの場合、[githubのライセンスファイル](https://github.com/ancte0721/symbol-nodefee-kickback-tools/blob/main/LICENSE)のURLでOK
- なんの保証もありません、万一損害が発生しても作者は責任を負いません

というところです。
ライセンスを守って利用いただければ何も問題ないですが、[Twitter](https://twitter.com/nobu_kyutech)やQiitaで一報いただけたらとても嬉しいです。

# さいごに
使いたいけどイマイチ導入の仕方が分からないというノード運営者の方、サポートしますのでお気軽に[Twitter](https://twitter.com/nobu_kyutech)にてご連絡ください。
また、こんな機能を追加してほしいというアイデアや、バグを見つけた方はコメントや[Twitter](https://twitter.com/nobu_kyutech)にてにてご連絡ください。
自分の活動で少しでもSymbolコミュニティがより活性化しますように。