在这篇文章中, 我们用以太坊搭建一个私有网络的属于自己的区块链, 编写一个数据认证的智能合约并部署到私有网络的区块链上.

私有网络的区块链与联盟链是不同的概念. 私有网络区块链是说建立一条与以太坊公有链不同的另一个公有链. 而联盟链是指只有特定的一些人才可以参与的区链链. 如果把私有网络的区块链建立在一个局域网内, 只有局域网内的人才能参与, 那么这样的私有网络区块链也就是联盟链了.

搭建一个私有网络的区块链

安装

安装geth

安装以太坊go语言版客户端

sudo yum install golang
sudo yum install gmp-devel
git clone https://github.com/ethereum/go-ethereum
cd go-ethereum/
make all
ls -al  build/bin/geth

中间遇到iconv库的链接问题, 解决:

rm /usr/local/include/iconv.h

安装solc

合约语言Solidity的编译器

npm install -g solc

启动

私有网络并不是私链. 创建文件genesis.json 以下内容

{
    "config": {
        "chainId": 11988,
        "homesteadBlock": 0,
        "eip155Block": 0,
        "eip158Block": 0
    },
    "difficulty": "0x400",
    "gasLimit": "0x8000000",
    "alloc": {}
}

其中

  • difficulty为初始打包也就是挖矿的难度, 因为是私有网络初始设低一点.
  • chainId链的id,不同链, 链id不一样, 写一个自己的和公有的区分开.

运行以下命令初始化两个节点:

geth --datadir ./node1/ init ./genesis.json
geth --datadir ./node2/ init ./genesis.json

这时初始化两个节点, 但并没有启动.

为了节点之间互相能知道, 一种方法是启动节点时指定, 另一个是使用bootnode节点, 启动时都连接bootnode节点, 这样就可以从bootnode中获得其他节点的信息.

启动bootnode

bootnode -genkey bootnode.key
bootnode -nodekey bootnode.key -addr ":9100" -verbosity 6

第一条命令生成key, 我理解就是这个bootnode的id. 第二条命令是启动bootnode节点.

启动这两个节点

geth --networkid 11988 --datadir ./node1/ --syncmode "full" --rpc --rpcapi db,eth,net,web3,personal --port 19101 --rpcport 9101 --rpcaddr 127.0.0.1 --rpccorsdomain "*" -bootnodes enode://8f10b7c8ecb0b04baac38c0d17e7ebe1e15320b19ccc36ad90aa197c02346e46bcc9d2d058cdd401f8e1cf928b724178c673a9c5df3395131e808e76620a2656@127.0.0.1:9100
geth --networkid 11988 --datadir ./node2/ --syncmode "full" --rpc --rpcapi db,eth,net,web3,personal --port 19102 --rpcport 9102 --rpcaddr 127.0.0.1 --rpccorsdomain "*" --bootnodes "enode://8f10b7c8ecb0b04baac38c0d17e7ebe1e15320b19ccc36ad90aa197c02346e46bcc9d2d058cdd401f8e1cf928b724178c673a9c5df3395131e808e76620a2656@127.0.0.1:9100"

其中

  • networdid也chainId相同.
  • port指定节点间互相通过的端口.
  • rpcport指定节点对外暴露的接口所用的端口.
  • rpcaddr指定监听的网关, 默认为”localhost”, 如果想让其他机器通过rpc访问可以设置为”0.0.0.0”

创建账户

启动后在每个节点的目录下会有一个geth.ipc文件, 这个文件用来连接节点时用.

连接节点创建外部账户

geth attach /home/dev/git/blockchain/my-private-ethereum/node1/geth.ipc
web3.eth.accounts
personal.newAccount()
web3.eth.accounts
eth.getBalance(web3.eth.accounts[0])

这样就创建了一个账户, 刚创建的账户的余额为0.

设置控矿所得到这个账号

web3.miner.setEtherbase(web3.eth.accounts[0]);

挖点矿

为了搞点余额我们可以进行挖矿 开始挖矿

miner.start()

可以在node1节点的日志中看到如下:

INFO [12-28|15:53:50] 🔗 block reached canonical chain          number=5  hash=4a6b09…5f5812
INFO [12-28|15:53:50] 🔨 mined potential block                  number=10 hash=f4c71b…518dfb
INFO [12-28|15:53:50] Commit new mining work                   number=11 txs=0 uncles=0 elapsed=127.859µs
INFO [12-28|15:53:50] Generating DAG in progress               epoch=1 percentage=47 elapsed=19.980s
INFO [12-28|15:53:51] Successfully sealed new block            number=11 hash=71d1ee…1aa278
INFO [12-28|15:53:51] 🔗 block reached canonical chain          number=6  hash=6e7a6e…a7774d
INFO [12-28|15:53:51] 🔨 mined potential block                  number=11 hash=71d1ee…1aa278
INFO [12-28|15:53:51] Commit new mining work                   number=12 txs=0 uncles=0 elapsed=125.815µs
INFO [12-28|15:53:51] Generating DAG in progress               epoch=1 percentage=48 elapsed=20.811s
INFO [12-28|15:53:51] Successfully sealed new block            number=12 hash=6009b7…506641
INFO [12-28|15:53:51] 🔗 block reached canonical chain          number=7  hash=2116bb…8e6948
INFO [12-28|15:53:51] 🔨 mined potential block                  number=12 hash=6009b7…506641
INFO [12-28|15:53:51] Commit new mining work                   number=13 txs=0 uncles=0 elapsed=157.238µs
INFO [12-28|15:53:51] Successfully sealed new block            number=13 hash=cf04e9…baac3b
INFO [12-28|15:53:51] 🔗 block reached canonical chain          number=8  hash=011c78…454cf2
INFO [12-28|15:53:51] 🔨 mined potential block                  number=13 hash=cf04e9…baac3b

由于我们在genesis.json配置初始挖矿难度值很低, 所以我们几乎挖矿速度很快. 这时再查看余额

eth.getBalance(web3.eth.accounts[0])
45000000000000000000

发现余额已经增加了. 这里显示的单位为wei. 相当于45个以太币.

以太的转换可以使用这个网址:https://etherconverter.online/

现在我们有钱了, 那就可以部署和执行合约. 接下来我们写一个数字认证的合约, 并部署到区块链上.

编写一个数字认证智能合约并部署到区块链

安装web3环境

web3相当于一个面对本地区块链节点的javascript客户端.

npm install web3@0.20.1 solc

智能合约如下:

pragma solidity ^0.4.0;
// 数字认证 存在证明
contract ProofOfExistence {

    mapping (bytes32 => bool) proofs;

    // 存储证明到合约中
    function storeProof(bytes32 proof) {
        proofs[proof] = true;
    }

    // 检查证明是否存在
    function hasProof(bytes32 proof) returns (bool) {
        return proofs[proof];
    }
}

数字证明很简单, 我们只要把需要证明的数据的hash值记进区块链中, 利用hash的碰撞阻力特性, 证明某段数据的存在.

部署合约

node进入

Web3 = require('web3')
//9102为启动节点时设置的接口所用的端口
web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:9101"));
//查一下有没有账户
web3.eth.accounts
//创建一个账户, 如果没有的话, 由于我们已经用geth创建了所以这里是有账户的
web3.personal.newAccount('abcd')
//再次调用, 发现有了账户
web3.eth.accounts
//查看余额, 由于我们挖了会矿, 所以账户是有余额的
web3.eth.getBalance(web3.eth.accounts[0])
//解锁账户, 如果没有解锁的话
web3.personal.unlockAccount(web3.eth.accounts[0], 'abcd', 15000)
//读取合约代码
code = fs.readFileSync('ProofOfExistence.sol').toString()
//加载solidity编译器
solc = require('solc')
//编译代码
compiledCode = solc.compile(code)
//生成abi
abiDefinition = JSON.parse(compiledCode.contracts[':ProofOfExistence'].interface)
//创建合约
VotingContract = web3.eth.contract(abiDefinition)
//拿到合约字节码
byteCode = compiledCode.contracts[':ProofOfExistence'].bytecode
//将合约字节码 部署到区块链
deployedContract = VotingContract.new({data: '0x' + byteCode, from: web3.eth.accounts[0], gas: 5000000})
//查看合约地址, 会发现合约地址为空, 这里因为合约尚未打包到区块里, 这里重启开启挖矿来打包区块. 再次查看就有地址了
deployedContract.address

调用合约

//拿到合约实例
contractInstance = VotingContract.at(deployedContract.address)
//调用合约方法, 参数中的hash值是通过调用php方法hash("sha256", "abcdefgxxx合同")获得的
contractInstance.storeProof(0x27a2df9c083e06109dfeb9633c711b796e0bd85ac049f5f55646d31def21b833)
//如果报invalid address, 那是因为默认的交易发送者, 执行以下, 再执行以上命令就好了
web3.eth.defaultAccount = web3.eth.accounts[0]
//contractInstance.storeProof执行成功后, 会返回一个地址, 这个地址为这次交易的地址, 执行以下可以查看这个交易
web3.eth.getTransaction('交易地址')
//返回如下结果
{ blockHash: '0x0000000000000000000000000000000000000000000000000000000000000000',
  blockNumber: null,
  from: '0xb19c93e4ed8553f0c8bbef8a59deba54215695bb',
  gas: 90000,
  gasPrice: BigNumber { s: 1, e: 10, c: [ 18000000000 ] },
  hash: '0xcafc78b00868c7bd45ecbb7b9e8cf4fa4f68aa10246c76319757202af1633175',
  input: '0x8952877b27a2df9c083e05f415c1c1c1ce3059e749c7a4ff96d5b08b3000000000000000',
  nonce: 1,
  to: '0x1efe34a3fe3b46e59fed454e86dfead63efd243d',
  transactionIndex: 0,
  value: BigNumber { s: 1, e: 0, c: [ 0 ] },
  v: '0x5dcb',
  r: '0x3cf890381f951e1aeec61d61646be95926be8dd0436829674f3d60eb8fcf4ad5',
  s: '0x6070af4a62306071677db9aa0016ea4e9dc4d78a76a97a108203f47f232a8f87' }
//blockHash为0000...是因为尚未打包, 现在没有blockhash. 启动矿工再次打包, 后再执行, 结果如下
{ blockHash: '0xf770e050eb0edde3ac6a9501790ec66f4d239978c3374e98940e2bcd8c2d8a90',
  blockNumber: 36,
  from: '0xb19c93e4ed8553f0c8bbef8a59deba54215695bb',
  gas: 90000,
  gasPrice: BigNumber { s: 1, e: 10, c: [ 18000000000 ] },
  hash: '0xcafc78b00868c7bd45ecbb7b9e8cf4fa4f68aa10246c76319757202af1633175',
  input: '0x8952877b27a2df9c083e05f415c1c1c1ce3059e749c7a4ff96d5b08b3000000000000000',
  nonce: 1,
  to: '0x1efe34a3fe3b46e59fed454e86dfead63efd243d',
  transactionIndex: 0,
  value: BigNumber { s: 1, e: 0, c: [ 0 ] },
  v: '0x5dcb',
  r: '0x3cf890381f951e1aeec61d61646be95926be8dd0436829674f3d60eb8fcf4ad5',
  s: '0x6070af4a62306071677db9aa0016ea4e9dc4d78a76a97a108203f47f232a8f87' }
//可以看到blockHash有值.
//咱们再次调用另外的hasProof方法看看, 如下
contractInstance.hasProof(0x27a2df9c083e06109dfeb9633c711b796e0bd85ac049f5f55646d31def21b833)
//发现又返回了个交易地址, 我们的hashProof只是个查询方法, 没必须发起交易, 用如下方式来查询
contractInstance.hasProof.call(0x27a2df9c083e06109dfeb9633c711b796e0bd85ac049f5f55646d31def21b833).toLocaleString()
//返回true, 证明区块链里有这个key, 也就进行了数据证明

在以前, 有很多同学直接用geth部署, 目前由于web3.eth.compile.solidity已在1.6版本去掉, 所以以下方式已废弃.

var proofOfExistence = 'contract ProofOfExistence { mapping (bytes32 => bool) proofs;  function storeProof(bytes32 proof) { proofs[proof] = true; }  function hasProof(bytes32 proof) returns (bool) { return proofs[proof]; } }';
var proofCompiled = web3.eth.compile.solidity(proofOfExistence)
proofCompiled
var proofContract = web3.eth.contract(proofCompiled.ProofOfExistence.info.abiDefinition);
var proofHash = '27a2df9c083e06109dfeb9633c711b796e0bd85ac049f5f55646d31def21b833'
var proof = proofContract.new(proofHash, {from: eth.accounts[0], data: proofCompiled.ProofOfExistence.code, gas: 4000000},
  function(e, contract) {
    if (!e) {
      if (!contract.address) {
        console.log("Contract transaction send: TransactionHash: " +
          contract.transactionHash + " waiting to be mined...");
      } else {
        console.log("Contract mined! Address: " + contract.address);
        console.log(contract);
      }
    }
  })