搭建一个私有网络的区块链
文章目录
在这篇文章中, 我们用以太坊搭建一个私有网络的属于自己的区块链, 编写一个数据认证的智能合约并部署到私有网络的区块链上.
私有网络的区块链与联盟链是不同的概念. 私有网络区块链是说建立一条与以太坊公有链不同的另一个公有链. 而联盟链是指只有特定的一些人才可以参与的区链链. 如果把私有网络的区块链建立在一个局域网内, 只有局域网内的人才能参与, 那么这样的私有网络区块链也就是联盟链了.
搭建一个私有网络的区块链
安装
安装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);
}
}
})