EOSIO 開發文件
===
這篇筆記主要目的,是讓開發人員可以在本機 local 環境下架設私有鏈,以供開發及測試遊戲。
## Prerequisite
* [Docker Desktop for Mac](https://hub.docker.com/editions/community/docker-ce-desktop-mac/)
* [EOSIO binaries](https://developers.eos.io/welcome/latest/getting-started/development-environment/before-you-begin)
* [EOSIO Contract Development Toolkit(CDT)](https://developers.eos.io/welcome/latest/getting-started/development-environment/install-the-CDT)
>在正式環境下,因為 os 是 RedHat 7.x,而且是最乾淨的那種,所以需要另外安裝套件才有辦法安裝 eosio bin,這部份最後再補充。
>
正確安裝完後,下個指令確認一下無誤
```shell=
cleos -h
```
如果成功跳出 help 的資訊,就可以進入下一步,建立錢包。
---
## 建立錢包
指令:
```shell=
cleos wallet create --to-console
#or
cleos wallet create --file [要存入的檔案名稱]
```
`--to-console` 會將創建的錢包私鑰直接 print 在 console 上,個人習慣存在文件裡。
建立完後就可以打開錢包:
```shell=
cleos wallet open
```
檢查一下是否成功
```shell=
cleos wallet list
```
這邊應該可以看到輸出
```shell=
Wallets:
[
"default"
]
```
看到 default 錢包就可以往下一步前進,解鎖錢包:
```shell=
cleos wallet unlock
#or
cleos wallet unlock --password [剛剛存的私鑰]
```
如果解鎖成功,再下一次 `cleos wallet list`應該看到
```shell=
Wallets:
[
"default *" <==多了
]
```
這邊先簡單說明一下,`wallet`的定義和一般想像的可能不太一樣
>About Wallets
A common misconception in cryptocurrency regarding wallets is that they store tokens. However, in reality, a wallet is used to store private keys in an encrypted file to sign transactions. Wallets do not serve as a storage medium for tokens.
>
根據官方文件,錢包並不是用來儲存虛擬貨幣(Token),而是用來儲存私鑰。私鑰的作用是在交易時用來做加密簽章。
接下來就創建自己的公私鑰:
```shell=
cleos wallet create_key
#output >
Created new private key with a public key of:
"EOS8PEJ5FM42xLpHK...X6PymQu97KrGDJQY5Y" <=你的公鑰
```
這邊會建立一把私鑰並存在你的錢包裡,可以用`cleos wallet keys`列出所有已儲存的私鑰。方便起見,請把公鑰存起來,後面會經常用到。
下一個步很重要,因為是新建一個私有鍊,根據官方文件:
>Every new EOSIO chain has a default "system" user called "eosio". This account is used to setup the chain by loading system contracts that dictate the governance and consensus of the EOSIO chain. Every new EOSIO chain comes with a development key, and this key is the same. Load this key to sign transactions on behalf of the system user (eosio)
>
我們必須替 `system user` import 一把私鑰
```shell=
cleos wallet import
#or
cleos wallet import --private-key 5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3
```
這邊要小心,這把私鑰是官方提供給開發者用的,千萬不要用在正式環境。
下一步就要開啟服務了
---
## 開啟服務
首先開啟**keosd**,開啟時記得加上`&`跑在背景
```shell=
keosd &
```
根據官方文件
>keosd is a key manager service daemon for storing private keys and signing digital messages. It provides a secure key storage medium for keys to be encrypted at rest in the associated wallet file. keosd also defines a secure enclave for signing transaction created by cleos or a third part library.
>
keosd 是錢包管理的命令列工具(CLI),這邊有一個小小的雷,keosd 預設有自動上鎖的時間設定,預設是900秒,時間到就會把所有錢包上鎖。為了開發方便,我們先將 timeout 拉到非常大。
```shell=
# 先將 keosd 關掉
pkill keosd
# 重開服務
keosd --unlock-timeout=99999999999 &
```
這邊可以用多少個9沒有去數,為了測試方便,有多少就加多少。
接著開啟`nodeos`
>nodeos is the core service daemon that runs on every EOSIO node. It can be configured to process smart contracts, validate transactions, produce blocks containing valid transactions, and confirm blocks to record them on the blockchain.
>
`nodeos`是在運行在個別節點上的核心服務程序。它可以處理智能合約,驗證交易,產生包含有效交易的區塊以及確認區塊以將其記錄在區塊鏈上。透過額外插件,可以提供 Web API 供外部呼叫。
開啟指令:
```shell=
nodeos -e -p eosio \
--plugin eosio::producer_plugin \
--plugin eosio::producer_api_plugin \
--plugin eosio::chain_api_plugin \
--plugin eosio::http_plugin \
--plugin eosio::history_plugin \
--plugin eosio::history_api_plugin \
--filter-on="*" \
--access-control-allow-origin='*' \
--contracts-console \
--http-validate-host=false \
--verbose-http-errors >> nodeos.log 2>&1 &
```
這段指令很長,建議可以寫成 sh 腳本執行。
`nodeos`跑起來之後,就會在當下的路徑產生 log 檔,可以下指令`tail -f nodeos.log`來觀察。
因為我們有開啟 http 服務端口,就順便測試一下是否正常運行:
```shell=
curl http://localhost:8888/v1/chain/get_info
```
成功的話就可以看到自架私有鏈的資訊。
---
## 建立測試帳號
指令:
```shell=
cleos create account eosio YOUR_ACCOUNT YOUR_PUBLIC_KEY
```
帳號名稱隨意,公鑰是在**建立錢包**步驟中產生的。方便起見可以多創幾個帳號備用。
成功後會出現交易訊息:
```shell=
executed transaction: 40c605006de... 200 bytes 153 us
# eosio <= eosio::newaccount
{"creator":"eosio","name":"xxxx","owner":{"threshold":1,"keys":[{"key":"EOS5rti4LTL53xptjgQBXv9HxyU...
warning: transaction executed locally, but may not be confirmed by the network yet ]
```
可以用`cleos get account ACCOUNT`來查詢帳號資訊。
---
## Docker 開發環境
參照 `docker eosio`專案的 README . md 檔的操作步驟進入 docker 容器。
因為 docker 開啟時有設定 volume mapping,因此可以在本機開啟 vscode 之類的編輯器對 contract 路徑下的檔案做編輯,docker 容器會同步資料。
---
## 建立智能合約
以 contract 資料夾中現成的專案為例:
```shell=
|-- contract
| |-- hello
| `-- hello.cpp
```
```cpp=
#include <eosio/eosio.hpp>
using namespace eosio;
class [[eosio::contract]] hello : public contract {
public:
using contract::contract;
[[eosio::action]]
void hi( name user ) {
print( "Hello, ", user);
}
};
```
這個簡單的範例會回傳 **"Hello, YOUR_ACCOUNT"**
首先要將合約編譯為 `.wasm` 和 `.abi`以供部署。
指令:
```shell=
eosio-cpp hello.cpp -o hello.wasm
```
接著要先替合約在私鏈上創立一個帳號
```shell=
cleos create account eosio hello YOUR_PUBLIC_KEY -p eosio@active
```
創建成功後,就可以將合約部署上私鏈
```shell=
cleos set contract hello CONTRACTS_DIR/hello -p hello@active
```
測試一下
```shell=
cleos push action hello hi '["SOME_ONE"]' -p YOUR_ACCOUNT@active
```
交易成功後應該可以看到下面的訊息:
```shell=
executed transaction: 28d92256c8ffd8b0255be324e4596b7c745f50f857...
244 bytes 1000 cycles
# hello.code <= hello.code::hi {"user":"YOUR_ACCOUNT"}
>> Hello, SOME_ONE
```
---
## SeedBook
```shell=
|-- contract
| |-- hello
| | `-- hello.cpp
| `-- seedbook
| |-- Makefile
| |-- README.md
| |-- seedbook.abi
| |-- seedbook.cpp
| `-- seedbook.wasm
```
readme 中有簡易的操作說明,主要是透過 Makefile 執行簡單的命令。
這邊主要的重點會放在`seedbook`的架構作說明。
### Seed
```cpp=
struct [[eosio::table]] seed {
std::uint64_t id;
std::uint64_t round;
std::string seed_list;
std::string hash_list;
uint64_t primary_key() const { return id; }
uint64_t by_round() const { return round; }
};
typedef eosio::multi_index< name("seed"), seed, indexed_by<name("round"),
const_mem_fun<seed, uint64_t, &seed::by_round> > >seed_index;
```
這邊定義的 seed 內容包括一個 primary key`id`,一個不重複的局號`round`,以及秘文列表 `hash_list`和 seed 列表`seed_list`。資料會永久存在特定的 table 中,並佔用一定的 ram,因此儘量避免儲存大量資料導致資源耗盡。
這段程式的 prototype 為官網範例[2.4: Data Persistence - PhoneBook](https://developers.eos.io/welcome/latest/getting-started/smart-contract-development/data-persistence),並稍加修改。
為了避免局號重複,所以需要在交易前做檢查,因此這邊將`round`作為 *Secondary Index*,就可以透過
```cpp=
auto round_index = seedbook.get_index<name("round")>();
auto target = round_index.find(round);
```
來找出特定局號是否已經存在。
`seedbook`合約提供了三個 action:
* set - 寫入 seed
* remove - 移除特定局號 seed
* removemany - 移除多筆紀錄
### set
```cpp=
[[eosio::action]]
void set(
name user,
std::uint64_t round,
std::string seed_list,
std::string hash_list
) {
require_auth( user );
seed_index seedbook(get_self(), get_first_receiver().value);
auto round_index = seedbook.get_index<name("round")>();
auto target = round_index.find(round);
if ( target->round != round )
{
std::uint64_t maxid;
//The seed isn't in the table
seedbook.emplace(_self, [&]( auto& row ) {
row.id = seedbook.available_primary_key();
row.round = round;
row.seed_list = seed_list;
row.hash_list = hash_list;
maxid = row.id;
});
auto del_target = seedbook.find(maxid-100);
if (del_target->round != 0)
{
seedbook.erase(del_target);
}
} else {
eosio::print("Seed already in the table !");
return;
}
}
```
`set` action 需要傳入四個參數:
* user : 使用者帳號
* round : 局號
* seed_list : 明文列表
* hash_list : 密文列表
其中
```cpp=
...
require_auth( user );
...
```
會做權限驗證,只有特定使用者可以使用 action。
```cpp=
auto del_target = seedbook.find(maxid-100);
```
100為 buffer 大小,超過的部分會以 queue 的形式處理 table 的資料。
### remove & removemany
這兩個 action 主要是在開發階段做測試用。
`remove`可以刪除特定局號的 seed,`removemany`可以根據傳入的最大比數刪除多筆資料。
>僅是刪除 table 中的紀錄,區塊鏈上的交易一旦完成就不可能刪除
>
這邊有一個重點要特別注意,**一旦改變了 table 的內容**,因為資料格式和之前已存入的有差異,所以會導致`set`無法執行,必須先將 table 中的資料清除乾淨才行。
---
## 補充
正式機的環境下,安裝 EOSIO bin 時會出現缺少某些套件以致於無法安裝,切確的原因是正式機的 `RedHat 7.x` 版本是 minimal,甚至連 yum repo 都沒有註冊任何 server。
解決方法為將 RedHat 的 repo disable 掉,另外下載`centos 7`的 repo 取代原生 RedHat 的 repo。
* vim 開啟檔案 /etc/yum.repos.d/RHEL-Base.repo, 將檔案內所有 enabled 改為 0
```shell=
...
enabled=0
...
```
* 創建一個新的 repo => centos1.repo in /etc/yum.repos.d/,寫入以下內如:
```shell=
[centos]
name=CentOS-7
baseurl=http://ftp.heanet.ie/pub/centos/7/os/x86_64/
enabled=1
gpgcheck=1
gpgkey=http://ftp.heanet.ie/pub/centos/7/os/x86_64/RPM-GPG-KEY-CentOS-7
```
之後執行 `yum repolist`就可以安裝了。