TRONのリソースモデルとガス代

DataSpiderとブロックチェーン　その７

前回までの記事では、[DataSpiderから参照系と更新系のスマートコントラクトの呼び出し方](https://qiita.com/dataspider-fan/items/ca5088bf3d45df63b210)について紹介をしました。
今回は、DataSpiderから少し距離を置き、TRONネットワークのリソースモデルについて紹介したいと思います。

## やること
今回は、DataSpiderアダプタのソースコードの実装はしません。
TRONリソースモデルを理解するにあたり、必要となるコードを紹介します。

## TRONリソースモデル
今後、TRONのパブリックネットワーク上でDataSpiderとNFT連携の実装を進めていきますが、更新系のスマートコントラクトを多く呼び出します。その際に必要になるのがTRXトークンです。他のChainでは、ガス代と呼ばれたりしますが、TRONネットワークにおいても概念として同様の定義がありますので、これらを紹介したいと思います。
なお、TRONリソースモデルについては、TRONオフィシャルサイトに説明されていますので、詳細については以下のリンクも参照ください。
計算方法：https://developers.tron.network/docs/resource-model
計算例（計算方法含む）：https://tronprotocol.github.io/documentation-en/mechanism-algorithm/resource/

## TRXとポイント
オフィシャルサイトのマニュアルにおいて、帯域幅、CPU、ストレージ、RAM の 4 種類のリソースについて説明がされています。
今回は、上記4つのうち帯域幅(Bandwidth)とCPU(Energy)ポイントのリソースモデルについて読み解いていきたいと思います。

### BandwidthポイントとEnergyポイントの理解がなぜ必要か
公式マニュアルの中では、利用限度額を定義することにより、スマート コントラクトの実行を保証できると説明がさせています。ここで必要になるのがそれぞれのポイントの計算です。
ここで算出した利用限度額を、スマートコントラクトを呼び出す際に、引数に渡すことにより動作の担保ができると考えられます。

一方で、システムとしてスマートコントラクトの処理を行う場合、ポイントが不足した状態ではシステムの稼働ができません。私個人としては、その処理前もしくは処理中にどれくらいのポイントが必要になるのか、消費量の見積もりが必要になると考えています。
TRONリソースモデルでは、使い切った帯域幅(Bandwidth)とCPU(Energy)ポイントは24時間で徐々に回復するという仕様がありますが、短時間で多くの処理を行う場合、ポイントが不足する場面が考えられます。ポイントが不足しているようであれば、TRXを補充する必要がありますし、焼却機能の発動により、気づいたらTRXが非常に減っているという場面があるかもしれません。さらには、ポイントの所持量が減ってきた時点であらかじめアラート挙げる実装も必要に迫らせるかもしれません。これらハンドリングを行うためにも、消費するメカニズムや消費量の計算方法の理解が必要であると考えます。

## 所有しているTRXからポイントへの換算方法
他のWebページでは、TronlinkウォレットやTronscanを使用したStaking方法が説明されています。
ここでは、Tronscanを使用して特定のアドレスから預入（Staking）をおこなうことにより、BandwidthポイントもしくはEnergyポイントを獲得するまでの手順を紹介します。

### BandwidthポイントとEnergyポイントの見積もり方法
以下のサイトを使用し1TRXあたりどれくらいのポイントが得られるか確認することができます。ポイントを得る方法は、預入(Staking)と焼却(Burn)の2種類があります。それぞれどれくらいのポイントが得られるのか計算機能がありますので、TRONプロジェクトにより提供されている以下のサイトを参照してみてください。

https://tronstation.io/calculator

### TRX預入（Staking）と充填方法
Tronscanを使用してTRXをポイントに変換する例を紹介します。
あらかじめTronlinkのChromeプラグインをインストールし[Nile Testnet蛇口サイト](https://nileex.io/join/getJoinPage)からTRXを入手しておきます。

1\. https://nile.tronscan.org/#/wallet にブラウザから開きます。
![tronscan_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2849535/45dd8776-b0a1-dce9-d9fe-3b10ab637246.png)
2\. ①の「Obtain」ボタンを押します。
以下のダイアログが開きます。Bandwidthポイントを充填します。
Obtain欄に「TRON power of Bandwidth」が選択されていることを確認し、TRXを入力し「Stake」ボタンを押します。
![tronscan_2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2849535/1bb7d0dc-fff4-d515-e020-cb9a65a331a1.png)
3\. 続いて再度①の「Obtain」ボタンを押します。
以下のダイアログが開きます。Energyポイントを充填します。
Obtain欄に「TRON power of Energy」を選択し、TRXを入力し「Stake」ボタンを押します。
![tronscan_3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2849535/2f74dbdd-20f6-0e19-4e10-fc70ebbc47a4.png)
4\. ポイントが増えていることが確認できます。
![tronscan_4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2849535/902f6313-fba5-56e5-58d2-1c1fc33ce270.png)

以上で充填は完了です。
ここまでで、利用者の所持しているTRXからどれくらいのポイントが得られるか、またポイントを取得する手順について説明しました。
次章では、目的のスマートコントラクトを処理する際に、どれくらいポイントを消費するのか見積もり方法を説明します。

## スマートコントラクト実行時に必要となるポイントの見積もり方法
TRONを用いて、システムとしてスマートコントラクトを処理する場合、上記でも述べた通りこの見積もり処理が必要になると考えます。
目的のスマートコントラクトを動作せるために、どれくらいのエネルギーが必要になるのか、そのチェック機能を実装する際には必須となるロジックです。そのチェック方法について紹介します。
オフィシャルサイトには、スマートコントラクト処理に必要となるポイントの見積もり方法が体系的に掲載されていませんでしたので、整理するためにもここに書き留めたいと思います。

### 帯域幅（Bandwidth）ポイントの見積もり方法の概要
オフィシャルサイトでも説明がされていますが、Bandwidthポイントの消費量はブロックチェーンに転送する際のトランザクション文字列のバイト数から割り出すことができます。Bandwidthポイントの見積もりの考え方はシンプルです。
スマートコントラクトを呼び出す際に、必要となる情報を引数にセットしサインした情報と共にブロードキャストをしますが、ブロードキャストする直前の文字列からバイト数を計算し、その値がBandwidthポイントの消費量となります。
スマートコントラクトを呼び出す利用者アドレスのBandwidthポイントと比較して、消費量以上のポイントを所有していれば正常にスマートコントラクトの呼び出しが完了できるということになります。

### CPU(Energy)ポイントの見積もり方法の概要
Energyの見積もりは、少々処理ステップが必要です。Bandwidthの場合は、消費量を算出するまでコーディングのみで計算が完了できましたが、Energyポイントは、見積もり用のスマートコントラクトを呼び出しが必要となります。
見積もり用のスマートコントラクトの文字列を生成し、実際に呼び出してそのレスポンスの文字列から消費量を読み取るというという実装が必要になります。
ここで得られた値が、消費される想定量になります。
よって、チェックロジックとしては、見積もり用のスマートコントラクトから返却された値と利用者アドレスのEnergyポイントと比較して、消費量以上のポイントを所有していれば正常にスマートコントラクトの呼び出しが完了できるということになります。

## システムによる消費量の見積もり処理とポイントの充填方法
ここまでは、手動による充填方法や換算時の見積もり方法について紹介しました。
DataSpiderによるデータ連携を想定した場合、上記内容を全てプログラミングする必要があります。
この章では、手動により操作していた機能について具体的にどのような実装が必要か説明したいと思います。

### システム化する際に必要となる機能の整理
ここまででどのような機能が必要か整理をしたいと思います。

#### 1\. スマートコントラクトを呼び出す利用者の所有ポイントの取得
スマートコントラクトを呼び出す際に、事前にどれだけのポイントを所有しているかチェックする際に必要となります。
#### 2\. 更新系スマートコントラクトの消費ポイントの取得
所有ポイントで処理実行ができるかチェックする際に必要となります。
#### 3\. 1TRXあたりの獲得ポイント数の算出
どれだけのTRXを使用することにより消費量を賄えるか計算する際に必要となります。
#### 4\. 利用者のTRX預入処理（ポイントの充填）
利用者のポイントを充填する際に必要となります。

## システム化のための実装例
手動介入することなく、上記の内容を実現するために使用するTRON Javaライブライ関数 **(※)** を使用します。実装例と共に説明します。

:::note info
**※** TRON Javaライブライ
https://github.com/tronprotocol/trident
:::

### 1\. スマートコントラクトを呼び出す利用者の所有ポイントの取得
スマートコントラクトを呼び出す利用者のアドレスを用いて情報を取得します。
ここで取得した情報をもとに、所有ポイント数、1TRXあたりのBandwidthおよびEnergyのポイントを算出することができます。
#### 実装例
```java
String privateKey = "fdf7c79df4c8afbdbvnmzFnvlsjfiajifeFjfkjklsajfic754d2791b8a81159e";
KeyPair keyPairAccount = new KeyPair(privateKey);
String hexPublicAddress = keyPairAccount.toHexAddress();
ApiWrapper wrapperNileTestnet = ApiWrapper.ofNile(privateKey);
AccountResourceMessage myAccount = wrapperNileTestnet.getAccountResource(hexPublicAddress);
```
#### getAccountResourceのレスポンス
>freeNetUsed: 250
freeNetLimit: 1500
NetUsed: 252
NetLimit: 988
TotalNetLimit: 43200000000
TotalNetWeight: 8739439108
EnergyLimit: 54298
TotalEnergyLimit: 90000000000
TotalEnergyWeight: 331498278

#### 計算方法
上記のレスポンス結果から以下の値を算出します。
##### 所有ポイント数
**Bandwidthポイント**：```(freeNetLimit + NetLimit) - (freeNetUsed + NetUsed)```
**Energyポイント**：```EnergyLimit - EnergyUsed```



### 2-1\. 更新系スマートコントラクトの消費ポイントの取得（Bandwidthポイント）
更新系スマートコントラクトが消費するBandwidthを計算します。
トランザクション文字列のバイト数がBandwidthポイントになります。
トランザクション生成後、SignatureとRawDataバイト数から計算します。
#### 実装例
```java
int approveAmount = 10;
int power = 20;
int dmmylimitForFee = 5000000;
String privateKey = "fdf7c79df4c8afbdbvnmzFnvlsjfiajifeFjfkjklsajfic754d2791b8a81159e";
KeyPair keyPair = new KeyPair(privateKey);
ApiWrapper wrapperNileApprove = ApiWrapper.ofNile(privateKey);
String ownerAddr = "TXdKEeJa59NKVyscg5NUZFaewzZfnW6Tmc";
String contractAddr = "TYw975jZb1BgPKHwfBgkWritr2hF93uDhA";
Function approveFunc = new Function("approve",
                        Arrays.asList(new Address(ownerAddr) ,
                        new Uint256(BigInteger.valueOf(approveAmount).multiply(BigInteger.valueOf(10).pow(power)))),
                        Arrays.asList(new TypeReference<Bool>() {}));
TransactionBuilder builderApprove = wrapperNileApprove.triggerCall(ownerAddr, contractAddr, approveFunc);
builderApprove.setFeeLimit(limitForFee);
Transaction signedTxnApprove = wrapperNileApprove.signTransaction(builderApprove.build());

byte[] signature = signedTxnApprove.getSignature(0).toByteArray();
// Signatureサイズを取得
int signatureLen = signature.length;
// RawDataサイズを取得
int dataLen = signedTxnApprove.getRawData().getSerializedSize();
```
#### signTransactionのレスポンス
signedTxnApproveからgetSignatureメソッドを呼び出しSignatureサイズを取得します。
また同様に、signedTxnApproveからgetRawDataメソッドを呼び出しRawDataサイズを取得します。

#### 計算方法
トランザクション内の69バイトと上記2つの値からバイト数を算出します。
**トランザクションの合計バイト数**：```69 + signatureLen + dataLen```

### 2-2\. 更新系スマートコントラクトの消費ポイントの取得（Energyポイント）
更新系スマートコントラクトが消費するエネルギー量を取得します。
参照系スマートコントラクト呼び出し用のconstantCall関数を使用します。
このconstantCall関数を使用することにより、実際にはブロードキャストは行わず消費量を取得することができます。
更新系approve関数の呼び出しを例に説明します。
#### 実装例
```java
int approveAmount = 10; 
int dmmyPower = 20; 
String privateKey = "fdf7c79df4c8afbdbvnmzFnvlsjfiajifeFjfkjklsajfic754d2791b8a81159e";
KeyPair keyPair = new KeyPair(privateKey);
ApiWrapper wrapperNileEstimate = ApiWrapper.ofNile(privateKey);
String ownerAddr = "TGbBhglro4EUBDIWg5NUZFaewzZfnW6Tmc";
String contractAddr = "TNJgeiQwrTgD9T3eYw2kWritr2hF93uDhA";
Function approveFunc = new Function("approve",
            Arrays.asList(new Address(ownerAddr) ,
            new Uint256(BigInteger.valueOf(approveAmount).multiply(BigInteger.valueOf(10).pow(dmmyPower)))),
            Arrays.asList(new TypeReference<Bool>() {}));
TransactionExtention txnExtEstimate = wrapperNileEstimate.constantCall(ownerAddr, contractAddr, approveFunc);
```
#### constantCallのレスポンス
txnExtEstimateからenergy_usedを取得します。
>result {
  result: true
}
energy_used: 7550
6: {
  1: "\373\346\317\v\027\315\220\034\246\247\253[\313"

#### 計算方法
レスポンスの```energy_used: 7550```がEnergyポイントになります。
見積もりポイント：```7550 × getChainParametersのレスポンス(420：以下参照)```

### 3-1\. 1TRXあたりの預入ポイント数の算出
預入した場合のポイントは、上記getAccountResourceのレスポンスから算出します。
##### 1TRXあたりの預入した場合のポイント数
**Bandwidthポイント/TRX**：```TotalNetLimit / TotalNetWeight```
**Energyポイント/TRX**：```TotalEnergyLimit / TotalEnergyWeight```

### 3-2\. 1TRXあたりの焼却ポイント数の算出
#### 実装例
```java
String privateKey = "fdf7c79df4c8afbdbvnmzFnvlsjfiajifeFjfkjklsajfic754d2791b8a81159e";
KeyPair keyPairChainparam = new KeyPair(privateKey);
String hexPublicChainparam = keyPairChainparam.toHexAddress();
ApiWrapper wrapperNileTestnet = ApiWrapper.ofNile(privateKey);
try {
    ChainParameters chainparams = wrapperNileTestnet.getChainParameters();
} catch (IllegalException e) {
    e.printStackTrace();
}
```
#### getChainParametersのレスポンス
chainparamsのchainParameterから値を取得します。
>chainParameter {
  key: "getEnergyFee"
  value: 420
}

#### 計算方法
上記のレスポンス結果から以下の値を算出します。
##### 1TRXあたりの焼却した場合のポイント数
**Energyポイント/TRX**：```10000 / getEnergyFeeのvalue```



### 4\. 利用者のTRX預入処理（ポイントの充填）
以下のコードを実装することにより、Tronscanを使用して手動によりTRXをポイントに変換した操作をプログラムから制御できるようになります。

#### 実装例
```java
long frozenBalanceNum = 2000000000; // 預入するTRX
long frozenDurationNum = 3; // 3日間ロックする（変更できない）

String privateKey = "fdf7c79df4c8afbdbvnmzFnvlsjfiajifeFjfkjklsajfic754d2791b8a81159e";
KeyPair keyPair = new KeyPair(privateKey);
String ownerHexAddr = keyPair.toHexAddress();

ApiWrapper wrapperNileBalance= ApiWrapper.ofNile(privateKey);

try {
    // TRXをBANDWIDTHポイントに充填します。
    TransactionExtention txnExtFreezeBalance = wrapperNileBalance.freezeBalance(ownerHexAddr, frozenBalanceNum, frozenDurationNum, 0);
    Transaction toSendTxnFreeze = wrapperNileBalance.signTransaction(txnExtFreezeBalance);
    String resultwrapperNile = wrapperNileBalance.broadcastTransaction(toSendTxnFreeze);

    // TRXをENERGYポイントに充填します。
    txnExtFreezeBalance = wrapperNileBalance.freezeBalance(ownerHexAddr, frozenBalanceNum, frozenDurationNum, 1);
    toSendTxnFreeze = wrapperNileBalance.signTransaction(txnExtFreezeBalance);
    resultwrapperNile = wrapperNileBalance.broadcastTransaction(toSendTxnFreeze);

} catch (IllegalException e) {
    e.printStackTrace();
}
```

## 次回は
ここまでで、スマートコントラクトの参照系、更新系関数の呼び出し、そして運用に必要となるエネルギーの概念までのイメージが出来たと思います。
[次回から、いよいよNFT連携を実現するためにの実装](https://qiita.com/dataspider-fan/items/56580cdf3452f4322580)に入ります。NFT連携をするにあたりフロントエンドのアプリも必要になります。DataSpiderSDKによりアダプタの実装に合わせ、フロンエンドの作成も同時にすすめます。
AngularもしくはReactを用いて実装を行う予定です。