淺談區塊鏈

最近一段時間,區塊鏈技術頻繁出現在各種新聞APP的頭條里,在這種情況下,我開始了對區塊鏈技術的探索。

概念介紹

區塊鏈技術中湧現很多新的概念,而這對於初入區塊鏈領域的人來說,相當不友好,我在很長一段時間裡,都沒有搞清楚區塊鏈是什麼,虛擬錢包是什麼,為什麼交易有gas費用等等,所以本文首先會給出一些概念的解釋,方便後面的探討。

區塊鏈

區塊鏈其實就是一種鏈表結構,鏈表中的元素就是一個區塊,每一個區塊結構如下:

  • timestamp: 區塊產生時間戳
  • nonce: 與區塊頭的hash值共同證明計算量(工作量)
  • data: 區塊鏈上存儲的數據
  • prevHash: 上一個區塊的hash
  • hash: 本區塊鏈的hash,由上述幾個屬性進行哈希計算而得

區塊鏈本質是一種分散式的交易賬本,所有用戶都在本地存有完整賬本信息。如果有用戶想改變某一個區塊信息,由於區塊 hash 的計算過程使用了 prevHash 作為參數,那麼該區塊後的所有區塊,都會變得不合法,需要重新計算 hash ,想讓系統承認這個更改,必須同步更改 51% 的用戶的賬本信息,所以篡改區塊鏈上的賬本信息十分困難,這就保證了它的安全性。

礦工與挖礦

挖礦本質上是一組節點(礦機)使用他們的計算資源去創建一個包含有效交易的區塊的過程,參與這個過程的節點(礦機)被稱為礦工。一個礦工想要提交一個區塊到區塊鏈上,就必須更快的計算出一個nonce,nonce 和 區塊頭信息能共同證明,一個區塊是有效的。

工作量證明

上面提到,挖礦的過程中,礦工必須更快的計算出一個 nonce,這個 nonce 如何計算呢?nonce 是一個整數值,一般先把區塊頭信息後面加上nonce得到的字元串,進行 SHA256 哈希運算,得到的結果如果開頭0的個數小於設定的難度值,則驗證不通過,把 nonce 值加1重複上述操作,直到計算出來的 nonce 滿足得到的哈希值開頭0的個數不小於設定的難度值。而nonce的值,就是挖礦過程中工作量的證明。而系統為了鼓勵更多礦工參與進來,會給參與挖礦的每個礦工一定代幣的獎勵。

錢包

錢包本質是一個包含私鑰的文件。通常會包含一個軟體客戶端,錢包的地址,是由私鑰計算出來的,也就是公鑰。每一次交易,發送方必須要提供私鑰,才能把該公鑰地址下所擁有的代幣轉帳到其他公鑰地址,所以私鑰決定了比特幣的所有權。這裡要注意一點,一個錢包地址擁有多少代幣,不是存儲在私鑰里,而是存在區塊鏈上,區塊鏈上有著所有歷史交易賬目,可以根據賬目計算出每個地址所擁有的代幣。

比特幣 VS 以太坊

區塊鏈目前最火的兩個應用就是比特幣系統和以太坊系統,這兩個系統都是公共區塊鏈平台,都有自己自己的虛擬貨幣(比特幣 和 以太坊)。但是他們是有很多區別的,其中一個重要區別就是,以太坊通過智能合約使平台具有圖靈完備性,相對於比特幣,未來更具有擴展性。當然這也和他們本身的目標有關,比特幣想成為純粹的虛擬貨幣,以太坊想成為一個純粹的圖靈完備的開發平台。

智能合約

終於把一些基本概念普及完了,接下來該介紹重點,智能合約的開發了。

首先介紹一下需要使用的工具

geth

Geth是由以太坊基金會提供的官方客戶端軟體,用Go編程語言編寫的。

testrpc

testrpc 和 geth 不同,geth是以太坊公鏈環境,testrpc 是本地模擬的一個以太坊環境,便於測試開發。

MetaMask

MetaMask 是一個基於chrome插件開發的一個錢包。

Remix

以太坊官方推薦的智能合約開發IDE,可以在瀏覽器中快速部署測試智能合約。

Solidity

Solidity 官方推出的用於編寫智能合約的最流行的編程語言。

Truffle

用於編譯、部署智能合約的工具

Web3.js

Javascript 庫,用於和節點交互,可以用於構建基於web的Dapp。

智能合約開發

安裝 testrpc

npm install -g ethereumjs-testrpc # 安裝 testrpcntestprc # 本地模擬以太坊環境nnEthereumJS TestRPC v6.0.3 (ganache-core: 2.0.2)nnAvailable Accountsn==================n(0) 0xca56a0e708a9b8268ea6d7b3e93728a6a324d628n(1) 0xb9b3eb735a71e0b1ae55505fa25423298c9e0ba1n(2) 0x8c0b3356fb423f2c67216627f61e2bee329ccf66n(3) 0xd67270a1a8b2bfb2045cd717307ca5475e093797n(4) 0xd2d442b9d7bb46edd53953f3c0bbae00deca9b56n(5) 0x21ef73d1bd2db40c35c169f33d04035c4fa04723n(6) 0x9a6e56ae9026abe47dd3e5b7e337d11c95e255a3n(7) 0x16e9e712af18ca96a3323aa794ce437dda348a73n(8) 0x753630b2546fdea270d380e61cbdf98b8d612f31n(9) 0x6931462530e8359cb8ff70802290065d033301e0nnPrivate Keysn==================n(0) 0e827c0d81130b789945254cffe2d42d043ecfedc4b5b9149b9f6f8321a40a80n(1) 081824aba4f6adb70b32141600f3edf847a7bd736328104f088a1f120d5bc39bn(2) 7834de28a0d82155b56df3bf5c1e1e12d86e7de69f92850fc2b60373a25cc995n(3) 56242f1868b8da44804f07180d7d2f01ef6132e98cedd5ef9c53b9609c40cf71n(4) 955baf7ab9daa5cdbc3d1a535494f2730216f967b5d30d8db4e756376b6d6ce1n(5) b0b741a135fa5fb6011bd29c65b1dc6f72af9862630e671e85c76d43fb3db134n(6) 165db38471c04b6b87196c050f83bae2d9e6e3c14f09c61d2b7de6208ad62623n(7) c6d5523eb0f233f09d7359bb7b73f347ff1ed5efea63ca1aacd0ec52da6a8b76n(8) 0057d1448842232df2bdc5e0d8c9737dadb50aa392ad1484ec4ce592089057ban(9) 5c574a3bd2d2c1ee4cc70bfe116b1d99dc8da107d90c116444139867f74f86c2nnHD Walletn==================nMnemonic: tree embark shuffle foil screen transfer struggle exotic any during stage responsenBase HD Path: m/44/60/0/0/{account_index}nnListening on localhost:8545n

第一個賬戶給了100個以太坊,可以用來開發測試時使用,在 MetaMask 上選擇本地測試網路,即可查看賬戶餘額。

智能合約 hello world 版本

pragma solidity ^0.4.14; // 定義 solidity 版本nn contract greeter {n string greeting;nn function greeter(string _greeting) public { // 構造函數,在合約創建時執行n greeting = _greeting;n }nn function greet() public constant returns (string) { // 功能函數n return greeting;n } n }n

實現一個智能合約版投票系統

定義角色數據類型

pragma solidity ^0.4.14; // 定義 solidity 版本nncontract VoteDemo {n struct Voter // 定義投票人n {n bool voted; // 是否已投票n address addr; // 投票地址n bytes32 voteProposalName; // 選擇提案的索引n }n struct Proposal // 定義提案n {n bytes32 name; // 提案名稱n uint voteCount; // 累積得票數n }n address public voteCreator; // 投票發起人n Voter[] public voters; // 所有已授權的投票人n Proposal[] public proposals; // 所有的提案n}n

構造函數

// 創建一個提案,給出所有提案選項nfunction VoteDemo(bytes32[] proposalNames) public {n for(uint i = 0; i < proposalNames.length; i++) { // 初始化所有提案n proposals.push(Proposal({n name: proposalNames[i],n voteCount: 0n }));n }n}n n

功能函數

// 驗證提案是否合法nfunction checkProposalValid(bytes32 proposalName) public view returns(bool isValid) {n isValid = false;n for (uint i = 0; i < proposals.length; i++) {n if (proposals[i].name == proposalName) {n isValid = true;n }n }n }n // 獲取某個提案的投票數n function getVoteNumByProposal(bytes32 proposalName) public view returns(uint voteCount) {n require(checkProposalValid(proposalName));n for (uint i = 0; i < proposals.length; i++) {n if (proposals[i].name == proposalName) {n voteCount = proposals[i].voteCount;n }n }n }n // 獲取所有提案n function getProposalList() public view returns(Proposal[]) {n return proposals;n }n // 獲取投票情況n function getVotedList() public view returns(Voter[]) {n Voter[] memory res;n for (uint i = 0; i < voters.length; i++) {n if (voters[i].voted == true) {n res[i] = Voter({n voted: voters[i].voted,n voteProposalName: voters[i].voteProposalName,n addr: voters[i].addrn });n }n }n return res;n }n // 驗證投票人是否有許可權參加投票n function isValidVoter(address addr) public view returns(bool isValid) {n isValid = false;n for (uint i = 0; i < voters.length; i++) {n if (voters[i].addr == addr) {n isValid = true;n }n }n }n // 添加投票人n function addVoter(address addr) public {n require(msg.sender == voteCreator);n voters.push(Voter({n voted: false,n addr: addr,n voteProposalName: n }));n }n // 投票n function vote(bytes32 proposalName) public {n require(isValidVoter(msg.sender));n for (uint i = 0; i < voters.length; i++) {n if (msg.sender == voters[i].addr) {n voters[i].voteProposalName = proposalName;n voters[i].voted = true;n }n }n }n

到這裡,一個簡單的智能合約版本的投票系統就寫完了,接下來,該看看如何部署。

獲取智能合約位元組碼(byte code)和 二進位介面(ABI)

將上面的智能合約代碼拷貝到 remix 中,編譯之後,獲取 ABI 和 byte code

建立私鏈

由於在公鏈上部署合約和調用合約都需要花費一定的以太坊代幣,所以在開發和測試階段,可以自行搭建以太坊私鏈。

首先下載並編譯geth

curl -o go-ethereum.tar.gz https://github.com/ethereum/go-ethereum/archive/v1.7.3.tar.gzntar zxvf go-ethereum.tar.gzncd go-ethereumnmake gethnmv build/bin/geth /usr/local/binn

建立私鏈,運行節點

geth --identity [nodeId] --dev --datadir [datadir] --rpc --rpcaddr [yourIp] --rpcport [rpc port] --port [port]n

  • --identity 自定義節點ID
  • --dev 開發模式
  • --datadir 私鏈數據儲存目錄
  • --rpc 開啟rpc 服務
  • --rpcaddr rpc 服務地址
  • --rpcport rpc 服務埠
  • --port 指定和其他節點連接所有的埠號

這時候如果打開指定的數據儲存目錄,會發現有一個geth.ipc 文件,

連接私鏈

使用 geth attach 連接私鏈節點,並打開 geth console。

geth attach ipc:[your ipc file]n

geth console 是一個互動式的 JavaScript 環境。在這個環境中內置以下用來操作以太坊的對象:

  • eth:包含操作區塊鏈相關的方法;
  • net:包含查看p2p網路狀態的方法;
  • admin:包含與管理節點相關的方法;
  • miner:包含啟動&停止挖礦的方法;
  • personal:主要包含管理賬戶的方法;
  • txpool:包含查看交易內存池的方法;
  • web3:包含了以上對象,還包含單位換算的方法。

web3 相關的API,可以查閱 web3.js - Ethereum JavaScript API 。

部署合約

var abi = JSON.parse(abiString) // 將前面得到的 ABI JSON 字元串轉換為對象nvar bytecode = bytecodeString // 前面所得到的 byte codenvar contract = web3.eth.contract(abi) // 根據ABI得到智能合約nvar account = web3.personal.listAccounts[0]nnvar instance = contract.new({n data: bytecode, n gas: 1000000, n from: accountn})n

這樣,合約就部署在私鏈上了,現在可以調用合約中的方法

instance.xxx() // 調用合約中的方法n

總結

目前區塊鏈技術可謂是炙手可熱,作為前端的我們,可以嘗試了解一些區塊鏈基本知識,了解如何利用 web3.js 開發與公鏈節點交互的前端應用。現在已經有很多優秀的腳手架,可以讓我們快速開始開發基於以太坊智能合約的前端應用,這裡可以推薦一下 leopoldjoy/react-ethereum-dapp-example 。


推薦閱讀:

進入幣圈之前,先看看這些吧

TAG:区块链Blockchain | 智能合约 |