建立基於以太坊的私有網路和智慧合約

freewolf發表於2017-08-15

本文歡迎轉載,轉載請標明出處

freewolf 資深IT從業者,關注微服務區塊鏈敏捷開發前端技術等,不是大神,只是出於熱愛。有問題可以到 github.com/freew01f/bl… 進行交流。

釋出時間 BiTCoin #480499

寫在前面

最近一段時間一直關注區塊鏈的相關的領域和知識,今天本來想幫助小夥伴建立一個基於以太坊的智慧合約Demo,發現很多過去的文件都已經過時了,無法正常工作。那就只能自己造個輪子,弄個版本新一些幫助大家入門。

本文以流程tutorial為主,不過多去講技術原理,原理文章網路大把。

目標

本文目標如下:

  • 建立私有以太坊,設定第一個節點,挖礦
  • 完成一筆轉賬交易
  • 建立簡單的智慧合約
  • 建立第二個網路節點

環境介紹

無論什麼開發都離不開相應的環境,我儘可能將所有軟體都升級到最新版本,以下是本文內容相關的環境:

  • 作業系統 MacOS 10.12.6
  • Geth 以太坊 CLI github.com/ethereum/go… v1.67
  • Solidity 智慧合約編譯器 Version: 0.4.15+commit.8b45bddb.Darwin.appleclang

安裝

安裝Node.js,這裡不闡述了,原始碼自己編譯吧。
安裝Geth,這裡直接去官方網站下載最新的可執行程式,複製到/usr/local/bin,就OK。
最後安裝Solidity,本地要先有brew,才能進行安裝:

brew tap ethereum/ethereum
brew install solidity複製程式碼

建立區塊鏈

建立自己的以太坊私有鏈很簡單,新建一個目錄,在目錄中先建立自己的創世區塊描述genesis.json檔案。

檔案內容如下

{
  "config": {
    "chainId": 2017,
    "homesteadBlock": 0,
    "eip155Block": 0,
    "eip158Block": 0
  },
  "difficulty": "100",
  "gasLimit": "2000000",
  "alloc": {}
}複製程式碼

為什麼自己建立創世區塊描述,如果使用預設值,difficulty值非常高,這樣挖礦要急死人的。

首先建立兩個賬戶,本文後面需要用到這兩個賬戶,建立賬戶需要輸入兩次密碼。

? > geth --datadir node1 account new
? > geth --datadir node1 account new複製程式碼

用我們剛剛建立的描述檔案,建立創世區塊。

? > geth --datadir node1 --networkid 27027 init genesis.json複製程式碼

使用console連線節點1並且記錄log

? > geth --datadir node1 --networkid 27027 console 2>>geth.log複製程式碼

使用geth完成挖礦和交易

連線成功後,看看有幾個賬戶

> eth.accounts
["0x65070d1d224114fd3c8358e9614fd948daecc428", "0xf11167054eb5fb91dd7b46726380f0f0cb09a6d8"]複製程式碼

查詢下賬戶餘額

> eth.getBalance(eth.accounts[0])
0複製程式碼

第一個賬戶沒有餘額,accounts[0]預設情況是coinbase賬戶,也就是挖礦的收益歸集賬戶,現在我們就來挖礦,賺取獎勵,由於difficulty值很低,挖礦秒出基本。

> miner.start(2);admin.sleepBlocks(1);miner.stop();
true複製程式碼
  • miner.start(2) 開始挖礦,引數是開啟挖礦的計算的執行緒數
  • admin.sleepBlocks(1) 挖到1個區塊就停止
  • miner.stop() 挖礦停止

第一次會建立DAG ,這裡會花費一些時間,關於DAG,詳情見底部參考。出現true說明挖礦完畢,挖完查詢餘額。

> eth.getBalance(eth.accounts[0])
5000000000000000000複製程式碼

需要注意,挖一個區塊,獲得5個以太幣作為獎勵,這裡的顯示的單位是wei,並不是以太幣,下面轉換一下

> web3.fromWei(eth.getBalance(eth.accounts[0]), 'ether')
5複製程式碼

5個以太幣在accounts[0],現在轉2個給accounts[1],轉賬時候,單位是wei,但是注意,既然轉賬,別忘先解鎖賬戶accounts[0],這裡要輸入賬戶密碼。

> personal.unlockAccount(eth.accounts[0])
Unlock account 0x65070d1d224114fd3c8358e9614fd948daecc428
Passphrase:
true
> eth.sendTransaction({from: eth.accounts[0], to: eth.accounts[1], value: web3.toWei(2, "ether")})
"0x3f190d2af25dff4e6b6fac9537f8f6152fdb193ca9afe228fbdc9301bbff5645"複製程式碼

最後出現的是這個交易的hash,查一下有沒有待處理的交易

> txpool.status
{
  pending: 1,
  queued: 0
}
> web3.fromWei(eth.getBalance(eth.accounts[1]), 'ether')
0複製程式碼

果然有一筆pending,查詢賬戶accounts[1],並沒有發現以太幣,這裡需要曠工來挖礦,打包這個交易到最新區塊。交易才能生效,繼續挖

> miner.start(2);admin.sleepBlocks(1);miner.stop();
true複製程式碼

查下餘額

> web3.fromWei(eth.getBalance(eth.accounts[1]), 'ether')
2複製程式碼

已經到賬,再看下剛才交易的詳情

> eth.getTransaction("0x3f190d2af25dff4e6b6fac9537f8f6152fdb193ca9afe228fbdc9301bbff5645")
{
  blockHash: "0xd30fbefb48de05a458a909d9486402bfa4d1459619226a3f8b95aaf407669bb7",
  blockNumber: 2,
  from: "0x65070d1d224114fd3c8358e9614fd948daecc428",
  gas: 90000,
  gasPrice: 18000000000,
  hash: "0x3f190d2af25dff4e6b6fac9537f8f6152fdb193ca9afe228fbdc9301bbff5645",
  input: "0x",
  nonce: 0,
  r: "0xd9b7c4830b9a7ae8ac922179c4e73e6bf2a52178ee0c01250bd940586334d412",
  s: "0xa1b0058b63e1c0360eae6073791b1d63d4a737c71c5932b4b203e853a8185cd",
  to: "0xf11167054eb5fb91dd7b46726380f0f0cb09a6d8",
  transactionIndex: 0,
  v: "0xfe5",
  value: 2000000000000000000
}複製程式碼

到這裡整個交易就完成了

簡單的智慧合約

下面我們來建立一個極簡單的智慧合約,geth 1.6變化蠻大的,以前編譯智慧合約的方法都有一些問題,沒什麼簡單的辦法,browser-solidity是個不錯的線上編譯選擇,我們還是選擇在本地進行操作,前面已經通過brew安裝了solidity,建立一個contract資料夾,在資料夾中建立一個hello.sol智慧合約檔案

pragma solidity ^0.4.13;

contract Hello {
  function sum(uint _a, uint _b) returns (uint o_sum, string o_author) {
    o_sum = _a + _b;
    o_author = "freewolf";
  }
}複製程式碼

然後我們來編譯,完成後,會多出兩個檔案,abi檔案就是智慧合約相關的介面,bin檔案就是智慧合約編譯程式碼。

這裡是Mac命令列環境,不是geth,? >開頭的都是命令列

? > solc -o . --bin --abi hello.sol
? > ls
Hello.abi    Hello.bin    hello.sol複製程式碼

geth中載入這些檔案很複雜,這裡我們修改下剛生成的檔案

Hello.abi 檔案內容修改成

var HelloContract = eth.contract([{"constant":false,"inputs":[{"name":"_a","type":"uint256"},{"name":"_b","type":"uint256"}],"name":"sum","outputs":[{"name":"o_sum","type":"uint256"},{"name":"o_author","type":"string"}],"payable":false,"type":"function"}])複製程式碼

Hello.bin 檔案內容修改成

personal.unlockAccount(eth.accounts[0])

var hello = HelloContract.new({
  from: eth.accounts[0],
  data: "0x6060604052341561000f57600080fd5b5b61017a8061001f6000396000f30060606040526000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff168063cad0899b1461003e575b600080fd5b341561004957600080fd5b61006860048080359060200190919080359060200190919050506100eb565b6040518083815260200180602001828103825283818151815260200191508051906020019080838360005b838110156100af5780820151818401525b602081019050610093565b50505050905090810190601f1680156100dc5780820380516001836020036101000a031916815260200191505b50935050505060405180910390f35b60006100f561013a565b82840191506040805190810160405280600881526020017f66726565776f6c6600000000000000000000000000000000000000000000000081525090505b9250929050565b6020604051908101604052806000815250905600a165627a7a72305820063cb95e17166637bd4ab62eae6b0e6c4e1fcd85a9c2e3be29aa75a272280b830029",
  gas: 500000
})複製程式碼

注意別忘了data必須0x開頭,New合約就得解鎖自己的賬戶,這裡解鎖也寫在這裡了

回到geth,載入剛修改的檔案,載入bin檔案需要輸入賬戶密碼

資料夾contract就在執行geth命令的目錄

> loadScript("contract/Hello.abi")
true
> loadScript("contract/Hello.bin")
Unlock account 0x65070d1d224114fd3c8358e9614fd948daecc428
Passphrase:
true複製程式碼

現在智慧合約已經部署到區塊鏈上了,但是要挖礦才能生效,挖完就可以盡情玩耍了。

> hello
{
  abi: [{
      constant: false,
      inputs: [{...}, {...}],
      name: "sum",
      outputs: [{...}, {...}],
      payable: false,
      type: "function"
  }],
  address: undefined,
  transactionHash: "0x783f5cae1f9b40f25da1260267d5e6f801d1746541b5f28f84684883723807b8"
}
> hello.sum
undefined
> miner.start(1);admin.sleepBlocks(1);miner.stop();
true
> hello.sum
function()
> hello.sum.call(1,2)
[3, "freewolf"]複製程式碼

追加 - 如何建立其他節點

追加一段,如何建立其他的P2P節點,首先在原先節點執行下面程式碼,檢視當前節點資訊

> admin.nodeInfo
{
  enode: "enode://bbd3d0f2afad68c3e4b2e79a6daddc6e8498b9266cc3e953bbb121bae40fe44b5e0377c0768b03e47ac04cead52235a12931bfb96528a8225be5808fc8c174b3@192.168.1.2:30303",
  id: "bbd3d0f2afad68c3e4b2e79a6daddc6e8498b9266cc3e953bbb121bae40fe44b5e0377c0768b03e47ac04cead52235a12931bfb96528a8225be5808fc8c174b3",
  ip: "192.168.1.2",
  listenAddr: "[::]:30303",
  name: "Geth/v1.6.7-stable-ab5646c5/darwin-amd64/go1.8.3",
  ports: {
    discovery: 30303,
    listener: 30303
  },
  protocols: {
    eth: {
      difficulty: 655652,
      genesis: "0x7b0286b147e6b5b8710b8acff38053fdf1991a980da8ca73b4b359c28c7144fc",
      head: "0xd545cee3b9247b67c5d43728eddcbcfe9315dcf18cbc12187a7a178220829153",
      network: 27027
    }
  }
}複製程式碼

拿到node相關資訊,就可以建立新節點了,這裡就不過多解釋了,前面基本都介紹過了

? > mkdir node2
? > geth --datadir node2 account new
? > geth --datadir node2 --networkid 27027 init genesis.json
? > geth --datadir node2 --networkid 27027 --port 30304 --bootnodes "enode://bbd3d0f2afad68c3e4b2e79a6daddc6e8498b9266cc3e953bbb121bae40fe44b5e0377c0768b03e47ac04cead52235a12931bfb96528a8225be5808fc8c174b3@192.168.1.2:30303" console複製程式碼

需要注意的也就是最後一行,寫入你自己的node資訊,其他的也沒什麼了,進入geth後,資料同步後,可以使用下面命令

> eth.getBlock('latest')
> admin.peers複製程式碼

以上就可以在第二個節點看到之前節點1的資訊了。

參考 & 資源

相關文章