為何要從 gitbook 遷移
以前曾在 gitbooks 發表一些電子書。 在 gitbook 發表電子書除了可以用 Markdown 語法外,也支援一些外掛如 Mermaid 圖表等。隨著 gitbook 業務調整,已經不再維護原來的平台,因此興起了遷移的想法。
選擇 Docusaurus
在一番比較後決定使用 Facebook 的 Docusaurus。Docusaurus 裝好後同時提供首頁,部落格,文件
。支援 Markdown 並支援在文件中嵌入 React 元件。
以前曾在 gitbooks 發表一些電子書。 在 gitbook 發表電子書除了可以用 Markdown 語法外,也支援一些外掛如 Mermaid 圖表等。隨著 gitbook 業務調整,已經不再維護原來的平台,因此興起了遷移的想法。
在一番比較後決定使用 Facebook 的 Docusaurus。Docusaurus 裝好後同時提供首頁,部落格,文件
。支援 Markdown 並支援在文件中嵌入 React 元件。
在前篇發布後至今一個月,以太坊官方的 Modalla 測試網已經啟動,ETH 2 的驗證者啟動頁面 (launchpad)也首次就緒。雖然尚未有新版的 EthereumOnARM ROM,但做完以下設定後,我也順利遷移到了新測試網。
以太坊 (Ethereum)作為 2014 年才開始區塊鏈的專案,近年發展迅速,眾多在以太坊上的活動讓目前的每秒交易速度 (~13 TPS) 已不符所需。經過多年的實驗與討論,以太坊 (Ethereum) 的下一代 Ethereum 2.0 路線圖也已經越來越清晰。
目前以太坊 (Ethereum) 2.0 即將進展至 Phase 0 階段。在此階段以太坊 1.0 和 2.0 的網路將並存。直到發展到 Phase 1.5 階段,1.0 網路將會融合進 2.0 網路。隨著時間接近,2.0 測試網路與多種驗證節點的測試版也陸續釋出,網路上已有許多經驗可參考,正是嘗試的好時機。
這邊略過各種基礎介紹,直接從如何運行以太坊 (Ethereum) 2.0 的驗證節點開始。
如果覺得這篇文章有用,歡迎傳送小費到 0xfDa995Eb398750319a2D5E8A4766c02e54db24b8
把過去半年本部落格上關於區塊鏈的文章整理放到 Gitbook。命名為Ethereum 區塊鏈!智能合約 (Smart Contract) 與分散式網頁應用 (DApp) 入門,對區塊鏈,智能合約,分散式應用 (DApp) 感興趣的讀者不妨前往一觀。
這不是我寫的第一本書1,也不是我第一本用 Markdown 寫的電子書 (以前用 Leanpub 出版過Firefox OS 開發書),但絕對是我編輯過程最順暢的一本書(雖然還未完成 XD)。
編輯過程最順暢不是因為對主題很熟悉或寫得快,而是因為從部落格文章初稿到 Gitbook,在寫作的過程中可以一路使用 Markdown。而且由於原本部落格圖片皆使用外連,因此引用圖片時也不用像以前編書時需要重新導入的過程。由於 gitbook 也支援mermaid.js
插件,支援我常常使用的 flowchart 語法,因此這些流程圖也不需要重新截圖或繪製,節省了大量時間。
我已有Markdown 格式的初稿,但剛開始我使用 gitbook 提供的所見即所得的編輯器。使用起來感覺非常不自在。
線上編輯器提供的new change request
,所見即所得編輯等功能,特別是 gitbook 提供的所見即所得編輯器無法切換回純 Markdown 模式,對於已熟悉 git, Markdown 語法的我來說並沒有變得好用。直接將 Markdown 格式貼到編輯器上時,也無法順利辨識格式,反而是貼上已輸出的部落格網頁時效果好很多。
所以最後我放棄使用線上編輯器,而是在本機編輯 Markdown 後直接 git 推送到專案上。
gitbook 在同步收到新的改動後,會自動編譯並發布新版本,相當方便。接下來應該會繼續使用這個流程。
因為智能合約一旦部署就難以修改,因此合約的安全性極其重要,要避免合約中出現一些基礎錯誤,除了透過第三方驗證外,完整地單元測試 (unit test) 也是必需的。
目前最成熟的智能合約單元測試方式,還是透過Truffle
開發框架來達成。有趣的是 Truffle 主要使用 Javascript 來撰寫智能合約的單元測試(也可以用 solidity 來寫)。
接續上一篇建立的HelloToken
合約,在test/
目錄下加入test_hello_token.js
測試檔案(如果覺得這份程式碼不易理解,可跳過這節,後面會介紹更簡潔的測試方法,到時再回來對照著看)。
var HelloToken = artifacts.require('HelloToken');
const INITIAL_SUPPLY = 100000;
let _totalSupply;
contract('HelloToken', function(accounts) {
it('should met initial supply', function() {
var contract;
HelloToken.deployed().then((instance) => {
contract = instance;
return contract.totalSupply.call();
}).then((totalSupply) => {
_totalSupply = totalSupply;
assert.equal(totalSupply.toNumber(), INITIAL_SUPPLY);
return contract.balanceOf(accounts[0]);
}).then((senderBalance) => {
assert.equal(_totalSupply.toNumber(), senderBalance.toNumber());
});
});
});
運行truffle test
可看到測試通過的結果。
Contract: HelloToken
✓ should met initial supply
1 passing (11ms)
var HelloToken = artifacts.require('HelloToken');
artifacts.require
的用法和在migrations/
中的用法相同,可以直接引入對應的智能合約。
contract('HelloToken', function(accounts) {
it('should met initial supply', function() {
});
});
Truffle 是使用 Javascript 開發中常見的Mocha測試框架和Chai斷言庫來做單元測試。差別只是把 Mocha test 中的 describe
換成contract
。根據官方文件1,contact
執行前會自動重新部署到 testrpc (或測試網路) 上,所以智能合約會是剛部署好乾淨的狀態。
此外,contract
也會帶入accounts
變數,裡面儲存了 testrpc 或其他你運行的測試網路所提供的帳號,開發者可以直接使用這些帳號來測試合約。
第一個測試是來測部署合約後預設的代幣數目是否正確。
var contract;
HelloToken.deployed().then((instance) => {
contract = instance;
return contract.totalSupply.call();
}).then((totalSupply) => {
...
});
這邊內容和在truffle console
中輸入的測試內容雷同,使用Promise
來確定每個非同步的操作都在上一個操作完成後才繼續執行。
上一個操作可以透過 return
語句回傳下個操作需要的參數。例如這邊then
裡面傳入的totalSupply
參數,是來自上一行return contract.totalSupply.call()
的結果。
assert.equal(totalSupply.toNumber(), INITIAL_SUPPLY);
...
assert.equal(_totalSupply.toNumber(), senderBalance.toNumber());
這邊我們透過assert.equal
語句驗證了HelloToken
合約中的初始代幣總額與INITIAL_SUPPLY
參數的值相符,且與合約部署者 (accounts[0]
) 帳戶中擁有的總額相符。
要理解這樣的 promise chain 需要一些練習。但其實上面的測試用例中,我們只想做好最後的兩個 assert 驗證。有沒有比較直覺的測試方法呢?
有的!2017 下半年,Javascript 語言支援了async/await
語句[2](只要安裝 Node 7.6 版以上即可使用),可以用更直覺的方式撰寫非同步的程式碼。
智能合約測試剛好也使用大量的非同步程式碼。使用async/await
語句改寫後的智能合約測試程式碼如下:
var HelloToken = artifacts.require('HelloToken');
const INITIAL_SUPPLY = 100000;
contract('HelloToken', function(accounts) {
it('should met initial supply', async function() {
let contract = await HelloToken.deployed();
let totalSupply = await contract.totalSupply.call();
let senderBalance = await contract.balanceOf(accounts[0]);
assert.equal(totalSupply.toNumber(), INITIAL_SUPPLY);
assert.equal(totalSupply.toNumber(), senderBalance.toNumbe());
});
});
運行truffle test
可看到測試通過的結果。
Contract: HelloToken
✓ should met initial supply
1 passing (11ms)
it('should met initial supply', async function() {
});
要在程式碼中使用 async/await,需要在函式前加入async
宣告,這樣解譯器才會解析函式中的await
語法。
let contract = await HelloToken.deployed();
let totalSupply = await contract.totalSupply.call();
let senderBalance = await contract.balanceOf(accounts[0]);
透過在非同步的操作前加上await
宣告,這三行程式會依照順序,等待第一行 await 語句執行完,取得contract
變數後,再依序執行第二行語句。第二行語句執行完,取得totalSupply
變數後,再繼續執行第三行語句以取得senderBalance
變數。
後面兩個 assert 語句則與 promise 撰寫時完全一樣。這樣改寫後,程式碼的可讀性大大地提昇了!
再透過async/await
語句試著加入轉帳測試:
it('should have right balance after transfer', async function() {
const AMOUNT = 123;
let contract = await HelloToken.deployed();
// check init balance
let account0Balance = await contract.balanceOf(accounts[0]);
let account1Balance = await contract.balanceOf(accounts[1]);
assert.equal(account0Balance.toNumber(), INITIAL_SUPPLY);
assert.equal(account1Balance.toNumber(), 0);
// check balance after transferred
await contract.transfer(accounts[1], AMOUNT);
account0Balance = await contract.balanceOf(accounts[0]);
account1Balance = await contract.balanceOf(accounts[1]);
assert.equal(account0Balance.toNumber(), INITIAL_SUPPLY - AMOUNT);
assert.equal(account1Balance.toNumber(), AMOUNT);
});
運行truffle test
可看到測試通過的結果。
Contract: HelloToken
✓ should met initial supply
✓ should have right balance after transfer (92ms)
2 passing (151ms)
let account0Balance = await contract.balanceOf(accounts[0]);
let account1Balance = await contract.balanceOf(accounts[1]);
assert.equal(account0Balance.toNumber(), INITIAL_SUPPLY);
assert.equal(account1Balance.toNumber(), 0);
範例的前半部測試帳號0
與帳號1
中的代幣餘額。帳號0
即部署代幣的帳號,因此擁有所有的HelloToken
代幣,而帳號1
中則沒有HelloToken
代幣。
await contract.transfer(accounts[1], AMOUNT);
接著呼叫合約的transfer
方法將一些代幣轉入帳號1
。注意這些都是非同步的操作(送出傳輸命令後,要先等待區塊鍊確認),因此需要使用await
語句。
account0Balance = await contract.balanceOf(accounts[0]);
account1Balance = await contract.balanceOf(accounts[1]);
assert.equal(account0Balance.toNumber(), INITIAL_SUPPLY - AMOUNT);
assert.equal(account1Balance.toNumber(), AMOUNT);
範例的後半部再次測試帳號0
與帳號1
中的代幣餘額。結果符合轉帳後兩個帳戶的預期代幣數額。
async/await
語句相當適合拿來寫非同步的程式,這特性太適合用來寫智能合約的測試了。因為async/await
這語法太新,所以大部分的參考資料都還在用Promise
來撰寫。我建議當你看到相關的智能合約測試時,可以用 async/await 改寫看看,會有很不一樣的感受。
[1] Writing Tests in Javascript http://truffleframework.com/docs/getting_started/javascript-tests [2] 6 Reasons Why JavaScript’s Async/Await Blows Promises Away (Tutorial)https://hackernoon.com/6-reasons-why-javascripts-async-await-blows-promises-away-tutorial-c7ec10518dd9