# IOTA 大撒幣檢討報告
## 活動簡述
「IOTA 大撒幣」為 [2017 年 MOPCON](https://mopcon.org/2017/) 在台灣時間 10 月 29 日講題「[透過區塊鏈打造行動化數位證書](https://mopcon.org/2017/speakerDetail.php?speaker=6)」的加值活動,活動辦法為會眾將自己的錢包位址 (address) 填入大撒幣網站,讓網站後端的電子錢包以及配合的節點發給 `100 Ki` ~ `5 Mi` 不等的隨機紅包。但由於當天該講題為 3 廳聯播(約 700 多個聽眾),加上該場次提供直播服務,活動在開始的幾分鐘後系統即進入效能低落的情況,造成給幣效益不彰。
## 系統架構圖
圖1 為「IOTA 大撒幣」的系統架構示意,系統以 `node0` 主機作為前端伺服器,提供 web wallet,`node0` 在接收會眾的 IOTA address 後亦即以 `devorg` 主機為 [light node](http://iotasupport.com/lightwallet.shtml) 進行交易,交易的 issuer 為一個帶有 value 的錢包,receiver 端即為會眾所填入的帳號 (IOTA Address),交易完畢後,`node0` 即回應一交易明細網址給會眾作為憑證。
![](https://i.imgur.com/duv98IB.png)
圖1. IOTA 大撒幣架構圖
## 缺失檢討
1. ==Web server 延遲==
Web server 在等待 IOTA Node 交易期間 (特別是 tip selection 與 AttactToTangle(PoW)) 為同步的延遲狀態。此狀態會依照 node 算力與 Tangle 網路自身狀態而有所不同,在平常狀態下約 30 秒內交易完畢。但在 Tangle 網路不穩定的狀態之下,交易時間會隨之變得很不穩定。
* 當日 Tangle 網路狀態
我們可以由錢包的狀態得知,當天 Tangle 上的 coordinator 效率並不太好,「大撒幣」發了 218 多筆交易,但大多數的 Transaction 是 pending 的狀態,(168 筆交易 pending 而只有 50 筆交易被確認。詳細 [帳本](/s/BJGcs840-) 或見圖2. IOTA 大量的 pending 交易),pending 狀態的交易目前仰賴 coordinator 做確認,但被確認的機率與交易本身的比重息息相關,影響比重的因素可能來自於雙重支付或是不合法的 tips。
![](https://i.imgur.com/OowKsUe.png)
圖2. IOTA 大量的 pending 交易 (詳細 [帳本](https://hackmd.io/s/BJGcs840-) )
* 交易 pending 的原因
影響交易 pending (亦即無法被 coordinator 所確認)的原因很多,我們先排除不可能發生的問題,意思是,一個 IOTA transaction 的發起,需要經過以下四個步驟:簽名(signature)、選擇兩筆尚未確認的交易(tips selection)、工作證明(POW)、廣播(broadcast)。
簽名(signature)與工作證明(POW)的錯誤會直接導致交易無法 Attach to Tangle,但這不符合於現實情況。而 broadcast 失敗會導致交易無法同步於分散式帳本,雙方也無法在 IOTA Tangle Explorer 查詢交易明細,這也不符合於現實情況。現實為大量的交易已經被傳送至 Tangle 上但狀態為 pending,由此得知問題的根源在 tips selection 的錯誤上。
我們可以由 [iota.dance](https://iota.dance/tangle) 確認,當天的 Tangle Network 效率其實相當低落。由圖3 可得知,Tangle 在當下,每分鐘僅有 30~40 筆不到的交易,且大多數交易為 SPAM (空交易),只有一筆帶值 (with IOTA token) 交易。
![](https://i.imgur.com/hNIAyL4.png)
圖3. 每分鐘交易量 34 筆,34 筆 SPAM 1 筆帶值
而交易被確認的機率也偏低,由圖4 可得知,交易的確認量從 09:20 開始一直在每分鐘 35 筆以下,一直到 12:20 更下降到每分鐘 30 筆。
![](https://i.imgur.com/F9yOSkq.png)
圖4. 交易確認量一直在每分鐘 35 筆以下
IOTA 目前不會提供 coordinator 在 Tangle 上不確認這些交易的理由,但官方錢包提供了 re-attach to the Tangle 的功能讓交易發起人重新做一次交易 (圖5),所謂 re-attach to the Tangle 的原理為重新做一次 tips selection 和 PoW,但以大撒幣而言,所有的錢包參數都是固定的,故 POW 的結果並不會改變,re-attach to the Tangle 只會改變 tips 選擇的結果。這部份「大撒幣」在伺服器端缺乏對應實做,所以 pending 的交易,僅能依靠 issuer 手動重做一次交易。
![](https://i.imgur.com/4GoP0AI.png)
圖5. re-Attach to the Tangle
2. ==IOTA Node API 延遲==
IOTA Node 本身一次可同時處理 [32](https://github.com/iotaledger/iri/blob/878fd31320540587825c774c96a37ab756f33dea/src/main/java/com/iota/iri/network/replicator/Replicator.java#L9) 筆 API rquest,但牽涉到交易的 API 大多為同步 (synchronized),例如 tips selection 的 [getTransactionToApproveStatement](https://github.com/iotaledger/iri/blob/3fc2fd53cfd430b1683e5f6ed69de88c8f1a830b/src/main/java/com/iota/iri/service/API.java#L445) 或是 POW 的 [attachToTangleStatement](https://github.com/iotaledger/iri/blob/3fc2fd53cfd430b1683e5f6ed69de88c8f1a830b/src/main/java/com/iota/iri/service/API.java#L753) 等等。這會造成 IRI 在密集交易的時候效率低落,我們可以由圖6 得知,在「大撒幣」服務開放的瞬間,IRI 在 **BLOCK** 中的 Task Thread 內容,這樣的 thread 會上升到 32 個,[詳細情況](/s/Hyfj9wVCb)可參考 [jstack](https://docs.oracle.com/javase/7/docs/technotes/tools/share/jstack.html) 命令觀察的結果。
也因為 IRI 的這個特性,我們選擇不調整 Apache 的最大連線數(`MaxKeepAliveRequests`) 或 PostgreSQL 的相同設定。因為在相同的時間內,IRI 還是會先讓這些同步的 method 執行完畢之後,才會允許下一個 task 存取。
![](https://i.imgur.com/VuB4Mbn.png)
圖6. Thread: BLOCK State ([詳細情況](/s/Hyfj9wVCb))
3. ==Tips selection 與 PoW 成本==
由上述得知,IRI 在交易的實做上,為了保證同時間只會有一個 thread 存取特定資源,在 tips selection 以及 POW 上都用上了同步 (synchronized) 的 Java 方法,這代表 IOTA node 無論如何在同一時間都只能動用一個 thread 來處理交易的主要工作 (tips selection 和 PoW),因此保持 IOTA node 在同一時間內的 BLOCK threads 數量在一定的數目以下是必要的。
也就是說,「大撒幣」勢必要讓交易的 tips selection 及 PoW 工作能夠在同一時間內分擔到各個能夠協助執行的 IOTA node 上,也就是必須要實現交易代理人機制,事實上「證書區塊鏈」的專案中一直保持有這個想法,但尚未導入在本次實驗中。詳情可參考圖7「證書區塊鏈」的系統架構圖:
![](https://i.imgur.com/GOX0A5R.png)
圖7.「證書區塊鏈」系統架構圖
## 事件重製
以下的程式碼為大撒幣中主要的交易程式 (發起交易),以選用的 IOTA Node 在一般狀況下,交易的速度約為 30 秒內:
**Code section (send. py):**
```python
# coding=utf-8
from iota import Iota, ProposedTransaction, Address, TryteString, Tag, Transaction
import time
NODE_URL="http://node.deviceproof.org:14265"
SEED=""
DEPTH=7
MIN_WEIGHT_MAGNITUDE=14
def transfer(address, tag, message, value):
time_start = int(round(time.time() * 1000))
recipient_address = address
sender_message = message
sender_tag = tag
prepared_transferes = []
api = Iota(NODE_URL, SEED)
sender_tag = bytes(sender_tag)
transfer_value = int(value)
txn = \
ProposedTransaction(
address = Address(
recipient_address
),
message = TryteString.from_string(sender_message),
tag = Tag(sender_tag),
value = transfer_value,
)
prepared_transferes.append(txn)
api.send_transfer(
depth=DEPTH,
transfers=prepared_transferes,
min_weight_magnitude=MIN_WEIGHT_MAGNITUDE
)
time_end = int(round(time.time() * 1000))
time_delta = time_end - time_start
print "Spend " + str(time_delta) + " milliseconds"
return True
transfer("ILXW9VMJQVFQVKVE9GUZSODEMIMGOJIJNFAX9PPJHYQPUHZLTWCJZKZKCZYKKJJRAKFCCNJN9EWOW9N9YDGZDDQDDC", "TEST", "TEST", 0)
```
**Output:**
```
Spend 31816 milliseconds
```
我們在 IOTA Node 端觀察 BLOCK 的 thread 數量變化情形:
```shell
$ watch "jstack $(pgrep -f iri-) > /tmp/log.txt ; \
cat /tmp/log.txt | grep BLOCK | wc -l"
```
**Output:**
```
1
```
而在交易發起時,BLOCK thread 的數量會由 0 增加到 1.
接著我們開始在同一時間內,發起大量的交易,假定一次發起 20 筆交易,那麼在第 1 筆交易完成之前,會有 19 筆交易被卡在 BLOCK 的狀態。
**發起 20 筆交易 Code section:**
```python
import subprocess
for count in range(20):
print "Issue a transaction NO. " + str(count)
subprocess.Popen(["python","send.py"])
```
**Output:**
```
Issue a transaction NO. 0
Issue a transaction NO. 1
Issue a transaction NO. 2
Issue a transaction NO. 3
Issue a transaction NO. 4
Issue a transaction NO. 5
Issue a transaction NO. 6
Issue a transaction NO. 7
Issue a transaction NO. 8
Issue a transaction NO. 9
Issue a transaction NO. 10
Issue a transaction NO. 11
Issue a transaction NO. 12
Issue a transaction NO. 13
Issue a transaction NO. 14
Issue a transaction NO. 15
Issue a transaction NO. 16
Issue a transaction NO. 17
Issue a transaction NO. 18
Issue a transaction NO. 19
Spend 267781 milliseconds
Spend 274271 milliseconds
Spend 281064 milliseconds
Spend 289212 milliseconds
Spend 291202 milliseconds
Spend 293937 milliseconds
Spend 294926 milliseconds
Spend 297169 milliseconds
Spend 301986 milliseconds
Spend 312041 milliseconds
Spend 316750 milliseconds
Spend 340649 milliseconds
Spend 344320 milliseconds
Spend 347965 milliseconds
Spend 360341 milliseconds
Spend 360543 milliseconds
Spend 370566 milliseconds
Spend 375175 milliseconds
Spend 375561 milliseconds
Spend 388316 milliseconds
```
在 IOTA Node 上的觀察亦可得知,BLOCK thread 數目增加到 20,如圖8:
![](https://i.imgur.com/4usY82B.png)
圖8. BLOCK thread
上述的測試案例我們可以得知:
1. 即便是高性能的主機,同一時間內也只能夠比較快速的處理一筆交易,IOTA 交易的發起,至少在 tips selection 以及 PoW 這兩項作工中,是無法以多執行緒的方式進行的。
2. 以交易的四個步驟來看,其中簽名 (signature)以及廣播(broadcast) 不會引用同步 (synchronized) method,因此得知,上下兩個交易的時間差為等待 tips selection 以及 PoW 的時間差,時間不一定,但多在 10000 milliseconds 以下。
3. 依照 thread BLOCK 的數量決定是否要 relay 交易引用的 IOTA Node 是必要的解決方案。
4. 假設交易的時間都相同,那麼多出來的時間便皆為等待 BLOCK thread 的時間。也就是說最後一筆交易 (No. 19) 完成時間為 388316 milliseconds,且光是等待第 1 筆交易 (No. 1) 就花了 120535 milliseconds 約為 2 分鐘。
然而一個 IOTA Node 在短時間內發出的多筆交易,因不明原因,不容易被 coordinator 所確認,原本應在 2 分鐘內確認的交易,現在我們在 Tangle Explorer 上觀察,20 分鐘之後仍然無法被確認,如圖9所示:
![](https://i.imgur.com/xBdJOGK.png)
圖9. pending 中的交易
截至本文件撰寫完畢的最後一次觀察,交易已經過了 1 小時,短時間內大量發送的交易只被確認了2 筆 (BUNDLE Hash:`MAJ9MMXWDQYHYWAMKFVFTPYNDCOWPOMDIDEQTSYUMTVYM9FYBROWPVZZQMLHSKLTWBZLXUJEVZST99999` 至 `VJPNOOPJ9QQWDDNNWMPRBKRFQXZJMT9GBVARZDDABMSFIDNDYKMDGKSWPEKOLOAEKANOHVS9DCFB99999` 一共 20 筆交易全部 pending 超過 1 小時,其餘交易(約 20 筆)皆為正常。),細節可參考 [Tangle Explorer 上的交易明細](https://thetangle.org/address/ILXW9VMJQVFQVKVE9GUZSODEMIMGOJIJNFAX9PPJHYQPUHZLTWCJZKZKCZYKKJJRAKFCCNJN9EWOW9N9Y)。
## 改進方法
因此適度的將交易分散出去是必要的,我們可以做一個粗淺的實驗來證明這一點,下面的程式碼一樣是發起 20 筆交易,但單數號碼的交易,由 http://node.deviceproof.org:14265 完成,雙數號碼的交易由 http://node0.puyuma.org:14265 完成。
**Code section:**
```python
import subprocess
NODE_URL_0="http://node.deviceproof.org:14265"
NODE_URL_1="http://node0.puyuma.org:14265"
for count in range(20):
print "Issue a transaction NO. " + str(count)
if int(count%2) == 0:
subprocess.Popen(["python","send.py", NODE_URL_0])
else:
subprocess.Popen(["python","send.py", NODE_URL_1])
```
**Output:**
```
Issue a transaction NO. 0
Issue a transaction NO. 1
Issue a transaction NO. 2
Issue a transaction NO. 3
Issue a transaction NO. 4
Issue a transaction NO. 5
Issue a transaction NO. 6
Issue a transaction NO. 7
Issue a transaction NO. 8
Issue a transaction NO. 9
Issue a transaction NO. 10
Issue a transaction NO. 11
Issue a transaction NO. 12
Issue a transaction NO. 13
Issue a transaction NO. 14
Issue a transaction NO. 15
Issue a transaction NO. 16
Issue a transaction NO. 17
Issue a transaction NO. 18
Issue a transaction NO. 19
http://node.deviceproof.org:14265: Spend 105758 milliseconds
http://node.deviceproof.org:14265: Spend 115530 milliseconds
http://node.deviceproof.org:14265: Spend 143118 milliseconds
http://node.deviceproof.org:14265: Spend 143585 milliseconds
http://node.deviceproof.org:14265: Spend 145727 milliseconds
http://node.deviceproof.org:14265: Spend 146998 milliseconds
http://node.deviceproof.org:14265: Spend 157517 milliseconds
http://node.deviceproof.org:14265: Spend 157982 milliseconds
http://node.deviceproof.org:14265: Spend 165566 milliseconds
http://node.deviceproof.org:14265: Spend 174718 milliseconds
http://node0.puyuma.org:14265: Spend 190466 milliseconds
http://node0.puyuma.org:14265: Spend 192593 milliseconds
http://node0.puyuma.org:14265: Spend 193059 milliseconds
http://node0.puyuma.org:14265: Spend 194875 milliseconds
http://node0.puyuma.org:14265: Spend 195795 milliseconds
http://node0.puyuma.org:14265: Spend 198944 milliseconds
http://node0.puyuma.org:14265: Spend 201270 milliseconds
http://node0.puyuma.org:14265: Spend 204694 milliseconds
http://node0.puyuma.org:14265: Spend 206978 milliseconds
http://node0.puyuma.org:14265: Spend 207400 milliseconds
```
由上述實驗得知:
1. `devorg` 的最後一筆交易,完成時間為: 174718 milliseconds
2. `node0` 的最後一筆交易,完成時間為: 207400 milliseconds
未做 relay 之前由 devorg 獨立完成所有的交易,最後一筆交易則是需要花費 388316 milliseconds!效能增進了有 180916 milliseconds(約為 3 分鐘)。
## 尚待釐清的 coordinator 行為
**即便以 relay 的方式優化交易的 performance,但同一個 Node 在短時間內發起的多筆交易,似乎很不容易被 coordinator 所確認**,原因尚待確認,截至本文結束時,交易的 BUBDLE HASH 從 BVSSBPPIBIXQE9BSOZKJGYPSEM9T9BHKIEHTYVGGASHQUVAYR9ALPYJCYNGNO9SKDJFBMJJSSUBM99999 至 SSXDCCGFTZDAPEJ99LAO9E9SWSMUTJDQMBAVALYRAPTXNCLZTEZVTYPICRX9HTCRUJUMBWOGAHHHZ9999 的 20 筆交易中,一共只被 coordinator 確認了 7 筆。