如何搭建以太坊私有链开发智能合约

字数 1634阅读 1503

1. 安装以太坊节点客户端

推荐 go-ethereum, 这是 go 语言实现的以太坊客户端,可在这里下载二进制版本。
或者通过包管理工具安装,Mac 下安装命令:

brew tap ethereum/ethereum
brew install ethereum

2. 创建私有链

创建初始化文件 genesis.json,内容如下:

{
    "nonce":"0x0000000000000042",
    "mixhash":"0x0000000000000000000000000000000000000000000000000000000000000000",
    "difficulty": "0x4000",
    "alloc": {},
    "coinbase":"0x0000000000000000000000000000000000000000",
    "timestamp": "0x00",
    "parentHash":"0x0000000000000000000000000000000000000000000000000000000000000000",
    "extraData": "0x",
     "config":{  
      "chainId":15,  
      "homesteadBlock":0,  
      "eip155Block":0,  
      "eip158Block":0  
    },  
    "gasLimit":"0xffffffff"
}

参数:

  • difficulty:挖矿难度,设置小一些。

初始化

geth --datadir ~/Ethererum/yx_data init genesis.json
  • --datadir 指定数据文件目录

运行结果如下:


WARN [01-27|11:31:29] No etherbase set and no accounts found as default
INFO [01-27|11:31:29] Allocated cache and file handles         database=~/Ethererum/yx_data/geth/chaindata cache=16 handles=16
INFO [01-27|11:31:29] Writing custom genesis block
INFO [01-27|11:31:29] Successfully wrote genesis state         database=chaindata                                    hash=6e92f8…23a660
INFO [01-27|11:31:29] Allocated cache and file handles         database=~/Ethererum/yx_data/geth/lightchaindata cache=16 handles=16
INFO [01-27|11:31:29] Writing custom genesis block
INFO [01-27|11:31:29] Successfully wrote genesis state         database=lightchaindata                                    hash=6e92f8…23a660

初始化完成之后,运行命令

geth --datadir ~/Ethererum/yx_data --identity "YXEtherum" --rpc  --rpcport "8545" --rpccorsdomain "*" --port 30303 --rpcapi "db,eth,net,web3" --networkid 10 --nodiscover console

参数:

  • --identity:节点名称
  • --rpc:开启 HTTP-RPC 服务
  • --rpcport:rpc 监听端口,默认 8545
  • --rpccorsdomain "*" :表示允许任何地址连接到此节点上
  • --rpcapi:允许外部通过 HTTP-RPC 访问的 api 接口,默认为“eth,web3”
  • --networkid:网络id,自己指定一个
  • --nodiscover:如果只是自己用,不被其他节点发现,开启这个参数
  • console 开启 JavaScript 交互环境

命令执行之后,进入 JavaScript 交互模式:

....
INFO [01-27|11:48:34] IPC endpoint opened: ~/Ethererum/yx_data/geth.ipc
INFO [01-27|11:48:34] HTTP endpoint opened: http://127.0.0.1:8545
Welcome to the Geth JavaScript console!

instance: Geth/YXEtherum/v1.7.3-stable/darwin-amd64/go1.9.3
 modules: admin:1.0 debug:1.0 eth:1.0 miner:1.0 net:1.0 personal:1.0 rpc:1.0 txpool:1.0 web3:1.0

> INFO [01-27|11:48:37] Mapped network port                      proto=tcp extport=30303 intport=30303 interface="UPNP IGDv1-IP1"

> eth.accounts // 查看当前账户
[]
> personal.newAccount() // 创建新账户
Passphrase:  // 输入密码
Repeat passphrase: // 确认密码
"0x77a4ee7daca8faecec59c52786dcac639dd6ab91"
> eth.accounts 
["0x77a4ee7daca8faecec59c52786dcac639dd6ab91"] // 账户已创建
> eth.getBalance(eth.accounts[0]) // 查看余额
0
> miner.start() // 开始挖矿
INFO [01-27|11:53:37] Updated mining threads                   threads=0
INFO [01-27|11:53:37] Transaction pool price threshold updated price=18000000000
INFO [01-27|11:53:39] Generating DAG in progress               epoch=0 percentage=0 elapsed=1.691s
INFO [01-27|11:53:41] Generating DAG in progress               epoch=0 percentage=1 elapsed=3.465s
INFO [01-27|11:53:43] Generating DAG in progress               epoch=0 percentage=2 elapsed=5.187s
...
...
INFO [01-27|12:00:55] 🔨 mined potential block                  number=3 hash=856ca2…60597c
INFO [01-27|12:00:55] Commit new mining work                   number=4 txs=0 uncles=0 elapsed=209.521µs
> miner.stop() // 挖出矿的时候,就可以停止了
true
> eth.getBalance(eth.accounts[0]) 
15000000000000000000 
> web3.fromWei(eth.getBalance(eth.accounts[0]), "ether");
15 // 账号已经有15个以太了,有以太我们就可以做交易了。
> exit 退出

3.搭建智能合约运行环境

在以太坊中,智能合约运行在以太虚拟机(EVM)上,所有在 EVM 上的操作都将在每个节点执行,因此需要一种机制来限制资源。以太坊使用 gas 来限制交易执行需要消耗的资源,任何需要提交到区块链上的操作都要消耗一定数量的 gas,gas 将从提交交易的账户中扣除。因此,要提交交易,账户余额要大于 0。关于 gas 想要了解更多,可以访问以太坊 wiki: Gas and Fees

智能合约由 Solidity 语言编写。需要注意的是,Solidity 函数有两种类型,理解这个有助于我们后面的操作:

  • 只读(常量)函数:不修改任何状态,只是读取状态、执行计算,返回变量。此类函数不消耗 gas,用关键字constant进行标识。
  • 交易函数(Transactional functions):此类函数修改了合约的状态(变量)、或转移合约账户资金。由于这些操作必须记录在区块链中,需要提交一个交易到网络中,因此会消耗 gas。提交时,需两个参数:账户地址和gas值,若账户余额为 0,或者小于函数执行所需要的 gas 时,将无法提交交易。gas 值要大于执行函数所需要的最小值,否则交易会被撤销。

geth 自带智能合约的执行环境,但对开发者不友好,推荐:ganache-cli(原名 testrpc),运行以下命令安装(要先安装 node 和 npm ):

alias cnpm="npm --registry=http://registry.npm.taobao.org \
--cache=$HOME/.npm/.cache/cnpm \
--disturl=http://dist.cnpmjs.org \
--userconfig=$HOME/.cnpmrc" # 使用淘宝镜像加快安装速度
cnpm install -g ganache-cli 

安装完成之后,运行ganache-cli

 >ganache-cli
Ganache CLI v6.0.3 (ganache-core: 2.0.2)

Available Accounts
==================
(0) 0x54a2d27c4b30139b538bf887982aae64cfa3c7a3
(1) 0x2d221a7ca36bcbb30ccd574104812539721ceed8
...

Private Keys
==================
(0) 930186bf51b9d503e82cc6e9a1f8adaea58b249278fcf4ca42d7635b3195057f
(1) e769615ea9103ec9f13a7bfe2f0e56b3337e0123298c78404d18c721abde3025
...

HD Wallet
==================
Mnemonic:      bar quit tobacco ring process twice kangaroo eye verify badge lazy phrase
Base HD Path:  m/44'/60'/0'/0/{account_index}

Listening on localhost:8545 // 注意这里的地址和端口号,默认8545,和 geth 一样,如果 geth 开启着,要关闭 geth,或者启用不同的端口号。

使用另一个窗口,安装智能合约编译及部署工具 truffle

>cnpm install -g truffle 
>mkdir solidity-experiments
>cd solidity-experiments/
>truffle init
>tree
.
├── contracts
│   └── Migrations.sol
├── migrations
│   └── 1_initial_migration.js
├── test
├── truffle-config.js
└── truffle.js

3 directories, 4 files

truffle init 创建了两个目录 contracts 和 migrations,contracts 存放合约文件,migrations存放 js 文件。编辑文件 truffle.js,配置要连接的 EVM 地址和端口,内容如下:

module.exports = {
    networks: {
        development: {
          host: "localhost", 
          port: 8545,
          network_id: "*" // Match any network id
        }
      }
};

编译、发布合约:

>truffle compile # 编译合约
>truffle migrate # 确保 ganache-cli 已开启
Using network 'development'.

Running migration: 1_initial_migration.js
  Deploying Migrations...
  ... 0x2a658b01d6...7ff
  Migrations: 0x38f1873ed43e7c6ce8a4eb78116c2bd186f6d3b0
Saving successful migration to network... # migration 发布成功
  ... 0x6451ee5b....be4

发布成功,然后我们就可以开始编写 solidity 合约,这里以 greeter demo 为例,运行

truffle create contract Greeter

此命令将在 contracts 目录下创建 Greeter.sol 文件 ,打开contracts/Greeter.sol,拷贝以下代码至文件中:

pragma solidity ^0.4.17; // 指定 solidity 版本

contract Greeter         // The contract definition. A constructor of the same name will be automatically called on contract creation. 
{
    address creator;     // At first, an empty "address"-type variable of the name "creator". Will be set in the constructor.
    string greeting;     // At first, an empty "string"-type variable of the name "greeting". Will be set in constructor and can be changed.

     // 构造函数
    function Greeter(string _greeting) public  
    {
        creator = msg.sender;
        greeting = _greeting;
    }

     // 只读函数
    function greet() constant public returns (string)          
    {
        return greeting;
    }
    
    function getBlockNumber() constant public returns (uint) // this doesn't have anything to do with the act of greeting
    {                                                   // just demonstrating return of some global variable
        return block.number;
    }
    
    // 非只读函数
    function setGreeting(string _newgreeting) public
    {
        greeting = _newgreeting;
    }
    
    // 自毁函数
    function kill() public
    { 
        if (msg.sender == creator)  // only allow this action if the account sending the signal is the creator
            selfdestruct(creator);       // kills this contract and sends remaining funds back to creator
    }

}

保存之后,编辑文件 migrations/2_deploy_contracts.js,如果没有则新建一个,文件内容如下:

var Greeter = artifacts.require("./Greeter.sol");

module.exports = function(deployer) {
  deployer.deploy(Greeter);
};

然后重新编译、部署:

>truffle compile # 编译
Compiling ./contracts/Greeter.sol...
Compiling ./contracts/greeter.sol...
Writing artifacts to ./build/contracts // 编译后的 json 文件存放在此目录下。

>truffle migrate --reset # 发布
Compiling ./contracts/Greeter.sol...
Writing artifacts to ./build/contracts

Using network 'development'.

Running migration: 1_initial_migration.js
  Replacing Migrations...
  ... 0x9eca7628a5a47....c4e
  Migrations: 0xfae368dec758a241f610333cdbd2944009fe23be
Saving successful migration to network...
  ... 0x74406ca1....ce5
Saving artifacts...
Running migration: 2_deploy_contracts.js
  Replacing Greeter...
  ... 0x081c7a46529....3dd
  Greeter: 0x58dd465b8633b1d20d9fbe7d795b965b1c8f3b40 # Greeter 合约地址
Saving successful migration to network...
  ... 0x2e2396da9....764
Saving artifacts...

Greeter 合约部署完成,Greeter 的地址为 0x58dd465b8633b1d20d9fbe7d795b965b1c8f3b40,可通过这个地址和合约进行交互,运行truffle console

>truffle console
truffle(development)> var greeter = Greeter.at(Greeter.address)
undefined
truffle(development)> greeter.address
'0x58dd465b8633b1d20d9fbe7d795b965b1c8f3b40'
truffle(development)> greeter.greet()
''
truffle(development)> greeter.setGreeting('Hello World')
{ tx: '0x4ea13b72...',
  receipt:
   { transactionHash: '0x4ea13b...',
     ....
  logs: [] }
truffle(development)> greeter.greet()
'Hello World' // 已赋值

合约已创建成功,现在部署到私有链。

4. 发布合约到私有链

退出ganache-cli,运行geth --datadir ~/Ethererum/yx_data --identity "YXEtherum" --rpc --rpcport "8545" --rpccorsdomain "*" --port 30303 --rpcapi "db,eth,net,web3" --networkid 10 --nodiscover --ethash.dagdir ~/Ethererum/yx_data/Ethash console

// 解锁账号
> personal.unlockAccount(eth.accounts[0], "yourpassword", 24*3600)
true
// 必须开启挖矿,否则合约无法提交、保存到区块链中。因为我们创建的私链中并没有其他节点处理交易。
> miner.start() 

然后在另一个窗口中运行truffle migrate --reset,看到以下类似信息说明合约已成功部署。

Running migration: 2_deploy_contracts.js
  Deploying Greeter...
  ... 0xcafdaaf58....f70
  Greeter: 0x53410cc....69e # 合约地址
Saving successful migration to network...
  ... 0x63026be34589....eea

如何在私有链中查看合约?
拷贝 truffle compile 编译后的build/contracts/Greeter.json文件中的abi串,然后在 geth console 窗口中,执行以下命令

// var abi = <cut and paste the contract abi  here>;
> var abi = JSON.parse('[{"constant": false, "inputs": [], "name": "kill", "outputs": [], "payable": false, "stateMutability": "nonpayable", "type": "function"}, {"constant": true, "inputs": [], "name": "getBlockNumber", "outputs": [{"name": "", "type": "uint256"} ], "payable": false, "stateMutability": "view", "type": "function"}, {"constant": false, "inputs": [{"name": "_newgreeting", "type": "string"} ], "name": "setGreeting", "outputs": [], "payable": false, "stateMutability": "nonpayable", "type": "function"}, {"constant": true, "inputs": [], "name": "greet", "outputs": [{"name": "", "type": "string"} ], "payable": false, "stateMutability": "view", "type": "function"}, {"inputs": [{"name": "_greeting", "type": "string"} ], "payable": false, "stateMutability": "nonpayable", "type": "constructor"} ]')
// var contract =  web3.eth.contract(abi).at(<contract address>);
> var greeter = eth.contract(abi).at('0x53410cc5e07be33983f71a6888d3221a9df9269e')  # 修改成你的 Greeter 合约地址
> greeter.greet()
"" 
> greeter.setGreeting('Hello') 
Error: invalid address       # 发生错误
    at web3.js:3930:15
    at web3.js:3756:20
    at web3.js:5025:28
    at map (<native code>)
    at web3.js:5024:12
    at web3.js:5050:18
    at web3.js:5075:23
    at web3.js:4137:16
    at apply (<native code>)
    at web3.js:4223:16
// 不能直接调用非常量函数,需通过sendTransaction来调用,如下:    
> greeter.setGreeting.sendTransaction('Hello World',{from:eth.accounts[0],gas:200000}) 
INFO [01-27|13:47:11] Submitted transaction                    fullhash=0xc9b9601a...650 recipient=0x53410...69e
"0xc9b9601a9....650"
> miner.start() # 挖矿,以保存新的greeter
INFO [01-27|13:47:16] Updated mining threads                   threads=0
INFO [01-27|13:47:16] Transaction pool price threshold updated price=18000000000
...
INFO [01-27|13:47:20] Commit new mining work                   
> miner.stop() # 停止
true
> greeter.greet() 
"Hello World" # 值已写入

另外一个管理合约的环境是以太坊钱包,界面形式,更直观。

5.以太坊钱包

如果 geth 已运行,并且开启了 rpc 默认端口,那么点击wallet.ethereum.org,则会自动连接到你创建的私有链。也可在这里下载二进制版本,安装桌面版,然后通过 rpc 方式连接到私有链中

open -a Ethereum\ Wallet --args --rpc ~/Ethererum/yx_data/geth.ipc

这里的 ipc 文件路径要和 geth 的一致,geth 运行之后会有一条信息,提示ipc文件路径

INFO [01-27|13:29:26] IPC endpoint opened: ~/Ethererum/yx_data/geth.ipc

可以在钱包里查看账户、合约、交易。Greeter 合约已经发布到私有链中,但要在钱包中查看合约,还需要手动添加,点击WATCH CONTACT,填入合约的地址及 ABI 串、合约名称,就可以查看并提交交易了。

PRIVATE-NET 表明连接的是私有链。点击右上角的合约 CONTRACES,

watch contracts

点击 WATCH CONTRACT,添加我们刚创建的合约

watch contracts

第一行填入合约地址,第二行给合约起个名字,第三个填入合约的ABI串,即合约编译truffle compile后的 build/contracts/Greeter.json 文件中 abi 的值。

添加之后,就可以在合约界面看到刚添加的合约:

contract

点击 Greeter,进入合约详情页,在这里可以执行合约。

commit transaction

选择要执行的函数,输入字符串,点击 EXECUTE。确保私有链中挖矿在进行,提交成功之后,界面的Greet字符串会自动刷新为新赋予的值。

after transaction

Greet 值已改变。

transactions

在主界面可查看所有的交易,点击左上角 WALLETS,最下面有最近的交易列表。

至此,以太坊私有链环境已搭建完成,合约也能正常工作。关于怎么编写合约,移步 solidity文档

参考:

  1. 以太坊Ethereum常见问题FAQ
  2. Private network
  3. The Hitchhiker’s Guide to Smart Contracts in Ethereum
  4. An Introduction to Ethereum and Smart Contracts: a Programmable Blockchain
  5. Build Your Own Blockchain

推荐阅读更多精彩内容