--- title: Blockchain Note tags: Note, Blockchain --- # Blockchain Note 在建區塊鏈時做的一些個人筆記... ###### tags: `Blockchain`, `Quorum`, `Solidity` ## Environment Setting Ubuntu 18.04 [Quorum from scratch](https://docs.goquorum.com/en/latest/Getting%20Started/Creating-A-Network-From-Scratch/) ## Library - [web3.py](https://web3py.readthedocs.io/en/stable/troubleshooting.html#setup-environment) ```bash # Install pip if it is not available: $ which pip || curl https://bootstrap.pypa.io/get-pip.py | python # Install virtualenv if it is not available: $ which virtualenv || pip install --upgrade virtualenv # *If* the above command displays an error, you can try installing as root: $ sudo pip install virtualenv # Create a virtual environment: $ virtualenv -p python3 ~/.venv-py3 # Activate your new virtual environment: $ source ~/.venv-py3/bin/activate # With virtualenv active, make sure you have the latest packaging tools $ pip install --upgrade pip setuptools # Now we can install web3.py... $ pip install --upgrade web3 ``` 安裝Quorum之前要先安裝這些: - [golang](https://www.opencli.com/linux/ubuntu-install-golang-compile-helloworld):1.14.1 官網下載golang 1.14.1 ```bash $ tar -C /usr/local -xzf go1.14.1.linux-amd64.tar.gz $ sudo vim ~/.profile #加入以下環境變數 export GOROOT=/usr/local/go export GOPATH=$HOME/go export PATH=$GOPATH/bin:$GOROOT/bin:$PATH $ source ~/.profile $ go version # 若找不到go,重開機即可 ``` - gcc `sudo apt install gcc` - make `sudo apt install make` ### Using RAFT consensus [Quorum form Scratch](https://docs.goquorum.com/en/latest/Getting%20Started/Creating-A-Network-From-Scratch/) [Quorum geth init genesis.json error](https://blog.csdn.net/Lyon_Nee/article/details/105893947) (quorum 2.5.0版本,genesis.json須改用此文件,官方文件未更新) ### Adding addtional node 1. 在fromsctatch/這再新增一個node 2資料夾 ```bash mkdir new-node-2 ``` 2. 生成一個新的account ```bash geth --datadir new-node-2 account new ``` 3. boo ## Consensus Quorum提供3種共識演算法:PoA, RAFT, IBFT ### PoA Proof of Authority,直接指定那些節點有寫帳權,其他節點透過演算法如果是被授權的節點打包Block則判定Block有效。 ### RAFT 適用:容故障、可信任節點、需要更快出塊時間、封閉聯盟鏈(全部節點皆可信任) 缺點:全部節點都得誠實、可容單點故障但不具備容錯,無法防止節點作惡和竄改歷史數據。 與以太坊相比,RAFT有自己的節點,但跟以太坊不同的是,RAFT並非任何節點都可以出塊,他將節點分成三種角色:Leader, Follower, Candidate。 ![](https://i.imgur.com/sLpyQmQ.png) Leader: 負責生產區塊的唯一節點,Follower監聽Leader的心跳,並收取Leader傳過來的區塊。 如果 Follower 在其周期內沒有收到 Leader 發來的心跳,則會認為Leader 已經死了。此時,沒有收到 Leader心跳的 Follower 重新發起選舉,自己的身份從 Follower 改變為 Candidate。它會給自己投一票,然後發送投票申請到其他 Follower,自己成為 Leader。 接受心跳的目的是為了抗系統故障,新的節點作為 Leader 繼續出塊。 值得注意的是,當新的交易產生後, Leader 並不會馬上記錄到鏈上,而是等收到所有 Follower 的確認回執後,記錄並廣播一個執行的消息,之後所有收到執行消息的 Follower 才會將區塊記錄在本地的鏈上。這樣就可以避免分叉,確保最終性 ### IBFT (未整理) Istanbul Byzantine Fault Tolerance (伊斯坦堡拜占庭容錯),在抗分岔的基礎上,防止部分節點作惡。 IBFT 是一種實用拜占庭容錯算法,與 RAFT 完全相信 Leader 不同,IBFT 的前提是包容 1/3 不誠實節點,通過驗證者多輪投票,達到彼此一致後出塊。 出塊主要需要三個階段:預準備 (pre-prepare) 、準備(prepare) 和確認 (commit) 首先,從全網節點輪流選舉出一個主節點(Leader)負責生成區塊,主節點在收到交易請求後生成新區塊。 **預準備(Pre-prepare)**:主節點向所有備份節點發送預準備消息,提議節點將從網絡收集到需放在新區塊內的多個交易排序後存入列表。 **準備(Prepare)**:所有備份節點接收到交易列表後,根據排序模擬執行這些交易。所有交易執行完後,廣播基於交易結果計算新區塊的哈希摘要。 **確認(Commit)**:如果一個節點收到的 2/3 個其它節點發來的摘要都和自己相同,就向全網廣播一條 commit 消息。 如果節點收到 2/3 條 commit 消息,即可提交新區塊及其交易到本地的區塊鏈和狀態資料庫,隨機進入下一輪區塊高度。 可以看到,每個區塊經過三個階段的驗證,即使 1/3 的節點出現故障或者作惡,也可以正常添加。如果主節點作惡,備份節點之間可以相互檢查,在衝突時提交一次 Round Change,選取新的主節點。 由於在每個區塊高度只有一個節點負責出塊,不會有分叉的風險。而且帳本不可篡改,試圖修改歷史紀錄需要獲取所有備份節點和主節點的私鑰。與 PoW 相比,IBFT 沒有競爭機制,出塊速度更快。 不過,IBFT 的劣勢也很明顯,多個驗證階段的結構下讓消息數量與節點數量成指數級增長,因此 IBFT 的節點數不能太多,通常用作企業級和政府的網絡。 ## Quorum Version Src: https://github.com/ConsenSys/quorum/releases Quorum 版本的命名方式從`2.8.0` 開始改成用 [Calendar Versioning (or CalVer)](https://calver.org/) 的格式命名,因此 `2.8.0` 改為 `20.10.0`。 格式:`YY.MM.Patch` ## Smart Contract ![](https://i.imgur.com/ajmaiIW.png) [src](https://ithelp.ithome.com.tw/articles/10218478) ### 帳戶的概念 以太坊將帳戶劃分兩種: 外部帳戶(Externally owned account, EOA) 與 合約位址(Contract Address, CA) 1. 外部帳戶: 一般我們用來發起交易的帳戶 2. 合約位置: 智慧合約部屬到以太坊後產生的帳戶,透過對這個合約位置發起交易來與合約互動。 ### 部屬流程 1. 使用`solidity`開發智慧合約 2. 將寫好的合約compile 3. 會產生`bytecode`跟`ABI` 4. 將`bytecode`部屬到以太坊上 ### 何謂ABI? `ABI` 的全名為 Application Binary Interface ,要與智能合約做溝通的介面就是 `ABI`,與 `API` 的概念相似,但程式是二進制編碼,故傳送的資訊也是二進制編碼,如何將資訊編碼成二進制形式或是解碼成易讀的資訊就是 `ABI` 的工作。 ### 運作原理 當使用者要與合約互動時,透過`ABI`與`合約位置`發起交易,來執行合約中的程式。 以太坊中的節點都有一個EVM(Ethereum VM)用來執行那些存在區塊中的`bytecode`,因此呼叫合約的時候,`EVM`會進行運算,再將結果傳給使用者,收到的結果會透過`ABI`轉成一般人能看懂的資訊。 ### 基本結構 #### 版本定義 ```javascript= # ^定義最低可接受compile版本 pragma solidity ^0.5.17; # 版本區間 pragma solidity >=0.4.21 <0.6.0; ``` #### 合約定義 - 定義合約用 `contract` - `constructor` :和contract同名,只會執行一次,用來做合約剛跑起來所需做的一次性工作,非必須。 ```javascript pragma solidity ^0.5.17; contract HelloWorld{ string public message; constructor(string memory text) public { message = text; } } ``` #### 函式定義 - 能見度(visibility): - public(default) - private: 只在這個合約裡 - internal: 這個合約與繼承他的合約 - external: 與internal相反,除了此合約與繼承他之外的合約 - payable:函式是否可以收錢 - modifier: 重複性的動作可以定義 ```javascript= modifier foo1{ do_something _; or_do_something_here } function bar() foo1(){...} #使用多個modifier function bar() foo1() foo2() foo3() ``` #### 合約產生合約 產生合約的gas通常很多,這些gas都算在此次的transaction裡,所以當函式產生新合約,要記得給夠多的gas。 ```javascript= contract foo{...} # 回傳一個address address newFooAddr = new foo(); # 回傳一個contract foo foo1 = foo(newFooAddr); ``` - selfdestruct(recipient) - 終結合約,將資料和程式碼清空,並將餘額轉給 recipient(address) - event: 將資料寫入transaction receipt,可用做紀錄或搜尋 - event paymentRecord(address indexed buyer, uint value) - indexed屬性:寫入topics中 - 如果寫入的資料超過32 byte,則會改存資料的雜湊值 - transaction receipt: - data - topics: 搜尋時可以用此區域得直當作篩選條件 > 喔買尬 solidity 內建有產生新合約的指令,還沒試過能不能用。 > [creating-contracts-via-new](https://solidity.readthedocs.io/en/v0.5.17/control-structures.html#creating-contracts-via-new) > > 目前我自己的做法是有一個合約的txt模板,用程式去改合約名稱再自動部署上去 ## 調用已部署合約 ![](https://i.imgur.com/6xACEwa.png) 用 `web3.py` 部署完合約後,用 `flask API` 與合約互動,並儲存使用者資料到 `sqlite` ## ethereum-input-data-decoder https://www.npmjs.com/package/ethereum-input-data-decoder [src](https://medium.com/taipei-ethereum-meetup/solidity%E6%92%B0%E5%AF%AB%E6%99%BA%E8%83%BD%E5%90%88%E7%B4%84%E8%88%87%E6%B3%A8%E6%84%8F%E4%BA%8B%E9%A0%85-%E4%BA%8C-dd915bdeafa0) transaction會記錄所有的functiona input,但transaction receipt的log只有設了event之後才會有,沒設log會是空的。 > 沒設event,log是空的 ![](https://i.imgur.com/Vij5lnc.png) > 有event,才會記錄到log裡 ![](https://i.imgur.com/rYNtSCS.png) ## Solidity ### 型態轉換 - uint to srting ```javascript function uint2str(uint _i) internal pure returns (string){ uint tmp == i; if (tmp == 0) return "0"; uint j = tmp; uint length; while (j != 0){ length++; j /= 10; } bytes memory bstr = new bytes(length); uint k = length - 1; while (i != 0){ bstr[k--] = byte(48 + tmp % 10); tmp /= 10; } return string(bstr); } ``` > `int` vs `uint` ([src](https://solidity.readthedocs.io/en/v0.5.12/types.html)) > > - 代表 `int256`, `uint256` > - 分別為 **signed** and **unsigned integers** of various sizes(符號整數、無符號整數),無符號整數不能給負值 ### 字串合併 ```javascript function concatenate(string memory a, string memory b)internal pure returns (string memory) { return string(abi.encodePacked(a, b)); } ``` ### Array 在Solidity中有兩種陣列:`storage array`, `memory array` 1. Storage array 此種陣列在執行合約function之後陣列會長久儲存在區塊鏈記憶體裡 - 宣告:`資料型態[] 陣列名稱` 例如:`uint[] myArray` - 新增:`myArray.push(2)` - 刪除:`delete myArray[1]` 但其實 `delete` 這個指令只是將值設為default值,例如我們宣告的這個陣列是 `uint` 則初始值就是 `0`。所以其實delete後那個陣列的element還是在那,並沒有真的被刪除掉,空間也沒有被釋放。 2. Memory array 此種陣列只會暫存,當執行完合約function 後就會消失 - 宣告 ```javascript function bart() external{ uint[] memory newArray = nuw uint[](10) } ``` 不能是動態陣列,必須要先宣告陣列長度 ### Mapping https://www.youtube.com/watch?v=wJnXuCFVGFA # Ganache ganache-cli 啟動指令 ([src](https://github.com/trufflesuite/ganache-cli)) ```bash ganache-cli -p 7545 -q ``` > **Node 14 ganache-cli Error: Callback was already called.** > > 尚未有人解決,先改用Node 12