使用Parity建立Proof-of-Authority (PoA) Ethereum Chain

5分鐘快速從無到有建立Ethereum聯盟鏈

開發Ethereum Dapp的過程中,需要一個測試用的chain,之前是使用testrpc或直接連到Ethereum testnet (Ropsten),不過testrpc有各種雷需要處理,而Ropsten testnet則是需要同步與等待block,相當的煩人。前幾天看到Parity 1.5版以後支援建立Proof of Authority Chains,可以直接解決上述的問題,果然一試成主顧。

若你有下列困擾,可以建立自己測試用的PoA chain

  • 公司內網或無對外網路,無法同步區塊
  • 降低測試時等待區塊的時間
  • 不想碰到testrpc各種雷

PoA Chain特點有

  • 有別於PoW (Proof-of-Work)需要解數學難題來產生block,PoA是依靠預設好的Authority nodes,負責產生block。
  • 可依照需求設定Authority node數量。
  • 可指定產生block的時間,例如收到交易的5秒後產生block。
  • 一般的Ethereum node也可以連接到PoA Chain,正常發起transactions, contracts等。

這篇教學文基本上是照著Parity Demo PoA tutorial來撰寫。

Parity是一個注重效能的Ethereum Client端軟體

1. 安裝Parity (1.5.0以上版本)

請自行到Parity官網安裝,支援Ubutnu, OSX, Docker, Windows

2. 設定chain spec

PoA chain需要設定一個創世區塊

{
"name": "DemoPoA",
"engine": {
"authorityRound": {
"params": {
"gasLimitBoundDivisor": "0x400",
"stepDuration": "5",
"validators" : {
"list": []
}
}
}
},
"params": {
"maximumExtraDataSize": "0x20",
"minGasLimit": "0x1388",
"networkID" : "0x2323"
},
"genesis": {
"seal": {
"authorityRound": {
"step": "0x0",
"signature": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
}
},
"difficulty": "0x20000",
"gasLimit": "0x5B8D80"
},
"accounts": {
"0x0000000000000000000000000000000000000001": { "balance": "1", "builtin": { "name": "ecrecover", "pricing": { "linear": { "base": 3000, "word": 0 } } } },
"0x0000000000000000000000000000000000000002": { "balance": "1", "builtin": { "name": "sha256", "pricing": { "linear": { "base": 60, "word": 12 } } } },
"0x0000000000000000000000000000000000000003": { "balance": "1", "builtin": { "name": "ripemd160", "pricing": { "linear": { "base": 600, "word": 120 } } } },
"0x0000000000000000000000000000000000000004": { "balance": "1", "builtin": { "name": "identity", "pricing": { "linear": { "base": 15, "word": 3 } } } }
}
}
  • stepDuration 設定成5秒產生一個區塊
  • validators 設定Authority的地方,目前先空著,後續產生account之後再回來填入

將上述資料存成 demo-spec.json

3. 設定兩個node

此教學會在同一台機器上跑兩個node,因此有些Parity原生的設定參數會有衝突,兩個node需要分別設定不同值。

  • -d 指定儲存資料與帳號的目錄
  • --dport 指定Parity的network port,可用來讓其他node連接
  • --jsonrpc-port 這是JSON RPC port,使用web3.js時會需要
  • ui-port Parity提供的Web-based UI port
  • dapps-port Parity Dapps使用的port

可以用下列指令啟動Parity node

parity --chain demo-spec.json -d /tmp/parity0 --port 30300 --jsonrpc-port 8540 --ui-port 8180 --dapps-port 8080 --jsonrpc-apis web3,eth,net,personal,parity,parity_set,traces,rpc,parity_accounts

除了打一長串的指令外,Parity也提供更為簡潔的config檔案設定方式,使用 --config 即可引用設定檔。

node0 使用如下設定檔 node0.toml

[parity]
chain = "demo-spec.json"
base_path = "/tmp/parity0"
[network]
port = 30300
[rpc]
port = 8540
apis = ["web3", "eth", "net", "personal", "parity", "parity_set", "traces", "rpc", "parity_accounts"]
[ui]
port = 8180
[dapps]
port = 8080

node1 使用如下設定檔 node1.toml

[parity]
chain = "demo-spec.json"
base_path = "/tmp/parity1"
[network]
port = 30301
[rpc]
port = 8541
apis = ["web3", "eth", "net", "personal", "parity", "parity_set", "traces", "rpc", "parity_accounts"]
[ui]
port = 8181
[dapps]
port = 8081

4. 設定帳號(Account)

我們總共要開3個帳號:2個Authority跟1個user帳號。

Step 1 首先啟動 node0 : parity --config node0.toml

啟動parity node0

接著開啟網頁 http://localhost:8180

我不知道怎麼略過WELCOME操作,所以先跟著指示隨便建立一個account,然後再刪除XD

接著新增一個user account,使用Recover account from recovery phrase功能,為了示範的一致性,使用 user 當作pass phrase

選擇第二項
輸入user
完成User account

新增Authority account,一樣使用Recover account from recovery phrase功能,為了示範的一致性,使用 node0 當作pass phrase

選擇第二項
輸入node0
最後要看到User與Node0 (Authority) account

這樣就完成 node0 的帳號設定

  • Authority account: 0x00Bd138aBD70e2F00903268F3Db08f2D25677C9e
  • User account: 0x004ec07d2329997267Ec62b4166639513386F32E

Step 2 再來設定 node1 的帳號,啟動 parity --config node1.toml ,步驟相同,連接到 http://localhost:8181 ,pass phrase使用 node1

啟動Parity node1
輸入node1
完成Authority account

這樣就完成 node1 的帳號設定

  • Authority account: 0x00Aa39d30F0D20FF03a22cCfc30B7EfbFca597C2

Step 3 將Authority account寫入 demo-spec.json 檔案

"validators" : {
"list": [
"0x00Bd138aBD70e2F00903268F3Db08f2D25677C9e",
"0x00Aa39d30F0D20FF03a22cCfc30B7EfbFca597C2"
]
}

再將user account加入accounts,並給一些balance,後續可以使用

"0x004ec07d2329997267Ec62b4166639513386F32E": { "balance": "10000000000000000000000" }

完成後的demo-spec.json如下

{
"name": "DemoPoA",
"engine": {
"authorityRound": {
"params": {
"gasLimitBoundDivisor": "0x400",
"stepDuration": "5",
"validators" : {
"list": [
"0x00Bd138aBD70e2F00903268F3Db08f2D25677C9e",
"0x00Aa39d30F0D20FF03a22cCfc30B7EfbFca597C2"
]
}
}
}
},
"params": {
"maximumExtraDataSize": "0x20",
"minGasLimit": "0x1388",
"networkID" : "0x2323"
},
"genesis": {
"seal": {
"authorityRound": {
"step": "0x0",
"signature": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
}
},
"difficulty": "0x20000",
"gasLimit": "0x5B8D80"
},
"accounts": {
"0x0000000000000000000000000000000000000001": { "balance": "1", "builtin": { "name": "ecrecover", "pricing": { "linear": { "base": 3000, "word": 0 } } } },
"0x0000000000000000000000000000000000000002": { "balance": "1", "builtin": { "name": "sha256", "pricing": { "linear": { "base": 60, "word": 12 } } } },
"0x0000000000000000000000000000000000000003": { "balance": "1", "builtin": { "name": "ripemd160", "pricing": { "linear": { "base": 600, "word": 120 } } } },
"0x0000000000000000000000000000000000000004": { "balance": "1", "builtin": { "name": "identity", "pricing": { "linear": { "base": 15, "word": 3 } } } },
"0x004ec07d2329997267Ec62b4166639513386F32E": { "balance": "10000000000000000000000" }
}
}

5. 啟動Authority node

為了啟動Authority node來產生區塊,我們必須設定負責產生block的signer,分別是 node0node1 account

Step 1 開啟一個 node.pwds 檔案,寫入 node0node1 的password,內容如下

node0
node1

Step 2 在設定檔 node0.toml 加入 [account][mining] 設定,如下

[parity]
chain = "demo-spec.json"
base_path = "/tmp/parity0"
[network]
port = 30300
[rpc]
port = 8540
apis = ["web3", "eth", "net", "personal", "parity", "parity_set", "traces", "rpc", "parity_accounts"]
[ui]
port = 8180
[dapps]
port = 8080
[account]
password = ["node.pwds"]
[mining]
engine_signer = "0x00Bd138aBD70e2F00903268F3Db08f2D25677C9e"
reseal_on_txs = "none"

Step 3 node1.toml 也一樣,如下

[parity]
chain = "demo-spec.json"
base_path = "/tmp/parity1"
[network]
port = 30301
[rpc]
port = 8541
apis = ["web3", "eth", "net", "personal", "parity", "parity_set", "traces", "rpc", "parity_accounts"]
[ui]
port = 8181
[dapps]
port = 8081
[account]
password = ["node.pwds"]
[mining]
engine_signer = "0x00Aa39d30F0D20FF03a22cCfc30B7EfbFca597C2"
reseal_on_txs = "none"

Step 4 分別啟動兩個node

parity --config node0.toml

parity --config node1.toml

6.連接兩個node

使用Postman透過JSON RPC來測試

Step 1 Post下列JSON資料至 http://localhost:8540 以取得 node0 的enode資料

{
"jsonrpc":"2.0",
"method":"parity_enode",
"params":[],
"id":0
}
取得enode連接資訊

Step 2node0 的enode加入 node1 ,Post下列JSON資料至node1 (http://localhost:8541 )

{
"jsonrpc":"2.0",
"method":"parity_addReservedPeer",
"params":["enode://6c4f53fc8536553c8f151516b7ee17f4b0719d21abe8fdd273588419cf467e3deafb414cd8efa331e4ad55fd7c2820a303a160895129e142a4306e7c3367d67c@172.20.160.80:30300"],
"id":0
}
你的IP位址會不一樣,172.20.160.80
成功加入

最後到 node1 的console畫面,會看到 0/1/25 peers,就表示已經連接上。

7. 發送transaction

透過Parity提供的web-based UI可以很容易發送transaction,這邊就不贅述了。


補充:分享給區網內其他人使用

在開發時通常會將node跑在server上,讓其他人可以透過JSON RPC port連接上去使用,此時只要在config檔裡面加入 [interface] 設定即可。

假設server ip為192.168.1.1,將 node0.toml 修改如下:

[parity]
chain = "demo-spec.json"
base_path = "/tmp/parity0"
[network]
port = 30300
[rpc]
port = 8540
apis = ["web3", "eth", "net", "personal", "parity", "parity_set", "traces", "rpc", "parity_accounts"]
interface = "192.168.1.1"
[ui]
port = 8180
[dapps]
port = 8080

[account]
password = ["node.pwds"]
[mining]
engine_signer = "0x00Bd138aBD70e2F00903268F3Db08f2D25677C9e"
reseal_on_txs = "none"

同樣 node1.toml 修改如下:

[parity]
chain = "demo-spec.json"
base_path = "/tmp/parity1"
[network]
port = 30301
[rpc]
port = 8541
apis = ["web3", "eth", "net", "personal", "parity", "parity_set", "traces", "rpc", "parity_accounts"]
interface = "192.168.1.1"
[ui]
port = 8181
[dapps]
port = 8081
[account]
password = ["node.pwds"]
[mining]
engine_signer = "0x00Aa39d30F0D20FF03a22cCfc30B7EfbFca597C2"
reseal_on_txs = "none"