写在前面
写这个主要是为了记录下自己的学习过程,同时如果能帮助到同样想搭建私有链的朋友们,那是再好不过了
Step 1 环境搭建
私链搭建有三宝,环境,终端和钱包。我这里用到的是Geth客户端,所以环境当然就是指Go语言运行环境。Ethereum的终端(客户端)有很多语言(C++,Python...bala..bala)的实现版本,这里我用的是Go语言的实现版本,也是使用较多的版本,这里就随个大流,毕竟用的越多,资料越丰富[1]。
-
Go环境
Go环境的安装还是算方便的,在不用理解各种目录的情况下,直接下载客户端安装好就ok了。以前Go是被墙了的,不过现在谷歌推出了中国开发者的官网,但是我进去后也没看到下载。为了方便大家,我在网盘保存了一份,大家可以下载,Windows版密码:jq7a,Linux版密码:ngbp,Mac版密码:lavf。想要了解具体Go环境安装及其目录关系,大家可以自行搜索。 -
Geth客户端
Geth的安装很简单。
Windows的用户很方便,直接下载客户端即可。不过貌似被墙了,我这里提前下载过一个,放在了百度网盘。虽然个人很不喜欢流氓网盘,因为不买会员,下载速度奇慢,百兆宽带也枉然,不过也实在没啥好地方放。大家可以下载安装,密码:zzch
Mac,Ubuntu的同学也可以方便的安装端执行以下命令即可完成安装,FreeBSD等其余Linux版本的同学可以下载源码编译安装。
Mac同学
brew tap ethereum/ethereum
brew install ethereum
//开发版的安装可以加上 --devel参数(如下),我没加,直接用的上面的命令,二选一即可吧
brew install ethereum --devel
Ubuntu同学
sudo apt-get install software-properties-common
sudo add-apt-repository -y ppa:ethereum/ethereum
sudo apt-get update
sudo apt-get install ethereum
- Mist或Ethereum钱包
我用的Mist钱包源码,及Ethereum的钱包安装包。钱包的安装包安装方式与之前无异,这个安装比较简单,下载对应平台的安装包即可。我也提供我放到百度网盘的Ethereum钱包安装包。Mac安装包下载密码:qfug,Linux的deb安装包下载密码:8jvr,Windows安装包下载密码:epul
我是下载Mist钱包源码,然后安装了开发环境的。事实证明,如果不是非要弄山寨币修改钱包,还是不要折腾源码钱包,直接用安装包装Ethereum钱包。如果对自家网络比较有信心,不妨一试,我反正运行命令后,去城市郊区玩儿了两天回来,亲眼见证了安装完成的最后一刻。
开发环境安装[2]:
先安装Node.js环境,我选择的推荐的8.9.4LTS安装
然后依次运行下面的命令,安装依赖(我知道,你肯定会直接拷贝命令的,别把$也拷贝下来了):
//安装依赖:
$ curl https://install.meteor.com/ | sh
$ curl -o- -L https://yarnpkg.com/install.sh | bash
$ yarn global add electron@1.7.9
$ yarn global add gulp
//下载钱包源码并运行,相信我,你一定会看见钱包连接节点的界面!
$ git clone https://github.com/ethereum/mist.git
$ cd mist
$ yarn
此时,三个小时已过去,一下午没了...下载上传,码字不易。不过这一切是值得的,你即将运行以太坊,激不激动!我反正已经肝儿颤了。接下来,进入主题,运行以太坊私有链!
Step2 修改创世块
以太坊,比特币等的区块链都是从创世块开始的(你可以简单理解成链表的头结点),创世块是要手动配置后生成的。下面是创世块的配置文件(也就是一个Json文件)。修改好后保存为genesis.json即可。 当然,你想换个canglaoshi.json也没人说什么[1]。
{
"config": {
//区块链的ID,你随便给一个就可以
"chainId": 21,
//下面三个参数暂时不知道干啥的
//等我知道了补上,或者有哪位大神知道
//可以在评论里指点我,谢谢
"homesteadBlock": 0,
"eip155Block": 0,
"eip158Block": 0
},
//用来预置账号以及账号的以太币数量,应该也就是所谓的预挖
//我这里不需要预挖,所以给了个空对象
//如果需要可以这样加
//"alloc": {
//"0x0000000000000000000000000000000000000001": {"balance": "111111111"},
//"0x0000000000000000000000000000000000000002": {"balance": "222222222"}
//}
"alloc" : {},
//币基地址,也就是默认的钱包地址,因为我没有地址,所以全0,为空
//后面运行Geth后创建新账户时,如果Geth发现没有币基地址,会默认将第一个账户的地址设置为币基地址
//也就是矿工账号
"coinbase" : "0x0000000000000000000000000000000000000000",
//挖矿难度,你可以随便控制哦,这里设置的难度比较小,因为我喜欢钱来得快
"difficulty" : "0x4000",
//附加信息,随便填个文本或不填也行,类似中本聪在比特币创世块中写的报纸新闻
"extraData" : "",
//gas最高限制,以太坊运行交易,合约等所消耗的gas最高限制,这里设置为最高
"gasLimit" : "0xffffffff",
//64位随机数,用于挖矿,注意他和mixhash的设置需要满足以太坊黄皮书中的要求
//直接用我这个也可以
"nonce" : "0x0000000000000042",
//与nonce共同用于挖矿,注意他和nonce的设置需要满足以太坊黄皮书中的要求
"mixhash" : "0x0000000000000000000000000000000000000000000000000000000000000000",
//上一个区块的Hash值,因为是创世块,石头里蹦出来的,没有在它前面的,所以是0
"parentHash" : "0x0000000000000000000000000000000000000000000000000000000000000000",
//创世块的时间戳,这里给0就好
"timestamp" : "0x00"
}
Step3 打开你的Geth客户端,给它找点事做
修改好创世块的Json文件后,我们就可以利用它来创建私有链了。创建一个文件夹来存放你的创世块文件。我这里就叫eth_test,里面放着我的创世块Json文件 image.png//--datadir 后面跟的eth的工作目录,你随便给一个文件夹就行,区块的数据会存在这个文件夹里
// init 后面跟的参数是genesis.json文件所在位置。我是在genesis.json文件所在的目录打开的终端,所以不需要给genesis.json的路径,给出文件名即可
geth --datadir "your/ethdata/filelocation" init your/genesis.json/loaction
image.png
如果你指定的目录下面出现了红框的文件夹,终端中出现Successfully wrote 等信息,恭喜你,创世块创建完成!
然后我们开启一个Geth节点,输入下面的命令:
geth --datadir "/Users/guojh/Documents/ethTestFiles/eth_test" --identity "Guo Chain" --networkid 19900418 --port 61916 --rpcport 8206 console
//--datadir 后面跟的是你指定的工作目录
//--identity 后面跟的是你的区块链标识,随便写
//--networkid 后面跟的是你的网络id,这个是区别区块链网络的关键
//--port 和 --rpcport 你随便给一个就行,别跟在用的端口重复就行
如果你得到的结果如下图,说明你成功开启了Geth的节点,并进入JavaScript终端。注意箭头,第一个箭头位置就是创世块中配置的chainId。最后一条INFO告诉你ipc文件位置,这个后面会用到。
image.png进入JavaScript终端后,你可以输入下面三个命令,创建一个账户。在创建账户之前,coinbase地址是空的,创建完账户后,coinbase为刚才创建的账户地址。
//创建一个新账户
personal.newAccount("123456")
//user1变量保存刚才创建的账户,可以看出,eth.accounts数组存放了账户地址
user1 = eth.accounts[0]
//解锁刚才创建的账户,如果不解锁,不能转账
//Geth隔一段时间就会锁定账户,所以需要解锁
personal.unlockAccount(user1, "123456")
//查看coinbase
eth.coinbase
image.png
输入命令查看账户余额
eth.getBalance(user1)
可以看到账户余额为0
现在,开始为自己赚钱吧~ 输入挖矿命令:
miner.start()
如果你看到终端不停有输出,那就对了。如果想停止挖矿,输入停止命令:
miner.stop()
在输入的时候你会发现输入的文字被打印出的log打乱了,不用担心,输你的,不影响。此时再查看余额,你变成富翁了!
到这里,我想到个问题,现在没有任何交易,区块里也没有任何交易信息,这也能得到以太币奖励?后来在Gitter的go-ethereum讨论组中咨询得知,只要是能产生区块,就有奖励,即使区块中没有任何有用信息。
Step4 多节点测试
只有一个节点略显孤单,我们再创建一个节点,让他俩有情人终成眷属 :)。这里我们会在创建节点命令中增加一个参数 bootnodes,在创建节点的同时,让新节点连接上刚才创建的节点。bootnodes跟的参数是节点地址。如果没有加bootnodes也不怕,创建好节点后调用admin.addPeer("enode"),将enode替换成节点地址即可。
将Step3中开启节点的命令替换成下面的命令(这里的genesis.json和第一个节点的必须一样,否则就是两个链了。另外,两个端口号不要和第一个节点重复,工作目录也不要重复,但是networkid必须一致):
略有点长
geth --datadir "your/ethdata/filelocation" init your/genesis.json/loaction
geth --datadir "your/ethdata/filelocation" --identity "Guo Chain" --networkid 19900418 --port 61917 --rpcport 8207 --bootnodes "enode://40fadf14ab5084f03dcea80f1380e60ce270d423f45e1ba71e37ba892d9822bb0e681cf3c551e13f5a82ced6468c4dc4f3942925878ea0f57165ab5e1299bd2b@192.168.3.32:61916" console
这里的enode可以在第一次创建的节点中输入:
admin.nodeInfo.enode
终端会显示出节点enode信息,用你的本机IP替换[::]
同样看到下图,进入JavaScript控制台,就是看到亲人了
image.png
但是如何验证两个节点连接上了呢?见证奇迹的时刻到了。在新节点中创建账户,创建完成后,看!发现没,节点在同步区块数据了!说明两个节点连上了!
image.png
Step5 连接钱包
在终端中进入之前下载好的Mist钱包的源码文件目录中。现在就要用到之前启动节点时创建的ipc文件了
- 首先来说说使用Ethereum钱包的连接方式[4][5]
我使用的是Mac,就是在终端输入下面的命令,给钱包一个rpc参数,其余平台应该也类似,就是通过终端启动钱包,并提供参数(geth.ipc文件就是你启动完节点后,自动生成的,就在节点目录下,钱包连接到私有链需要提供这个文件,否则会连接到主链上):
open -a /Applications/Ethereum\ Wallet.app/ --args --rpc /Users/guojh/Documents/ethTestFiles/eth_test/geth.ipc
image.png
瞬间发现我还挺有钱的。注意到那个红框了吗,说明确实连接到的是私有链。
接下来我们可以转账了!
在另一个节点查看地址(可以直接输入user1查看),拷贝下来地址,粘贴到钱包中转账
image.png
image.png
点击send,输入密码,转账完成!
去另外一个节点终端查看,却发现余额是0
image.png
这是怎么回事呢?朋友,不要忘了,交易是需要矿工确认的。矿工在哪里呢?矿工就是你自己。交易通知到P2P网络中的节点,但是没有矿工确认交易,所以交易没有执行。我们现在有两个节点,随便哪个开启挖矿,就能确认交易。当然,也可以玩儿玩儿,两个节点同时开启挖矿,看看谁能抢先确认交易。你可以看到,这边在挖矿,钱包就收到了确认交易的消息
这时,再查看另一个节点的余额,窃喜吧,朋友,你有钱了
image.png
- 下面说下Mist钱包源码方式
输入下面的命令:
yarn dev:electron --rpc /Users/guojh/Documents/ethTestFiles/eth_test/geth.ipc --node-ipcpath /Users/guojh/Documents/ethTestFiles/eth_test/geth.ipc
敲击回车,你有可能看到如下界面
这就略显尴尬了,一片空白。咋办呢,我猜想可能是区块同步有问题,要不开开采矿试试看钱包连上没。结果就连上了!激动啊!但人就是贱啊,我想看看是不是挖矿就一定能连上,立马关闭钱包,再试一次,然后悲剧了,从此以后,Mist钱包就打不开了,每次都是一闪而过,就消失了,终端提示窗口被关闭。有没有哪位大神知道原因?
image.png
过了很久之后我又连接上过一次,钱包操作方法和Ethereum钱包一样,然后就又打不开了,本文的遗憾...后面我找到原因再来补充更新这个地方
如果不想用钱包,也可以使用命令来转账,你需要输入from,转账来源,to,转账目的地址,value,转账金额,这里把1个ether转成以太币最小单位Wei来发送
eth.sendTransaction({from: "0x5fba50fce50baf0b8a7314200ba46336958ac97e", to: "0x0a8c35653d8b229c16f0c9ce6f63cffb877cfdcf", value: web3.toWei(1, "ether")})
回车后开启挖矿,一样可以转账。
Step6 创建你的代币
在以太坊上创建代币很简单,但是这种代币的交易是基于以太坊,也就是交易费还是要用以太币支付。如果需要修改矿工奖励,有自己的钱包等,还是需要修改以太坊源码的,这里我先介绍最简单的代币创建[6]。
为了简便,我们在私有链上创建代币,跟在以太坊主链上创建代币是一样的操作方法。按照Step5的方法打开钱包(Mist或者Ethereum钱包都可以,看哪个你能打开...),连接到你的私有链上。
点击右上方的合约按钮(CONTRACTS),然后点击部署新合约(DEPLOY NEW CONTRACT)
image.png
将下面的代码拷贝到SOLIDITY CONTRACT SOURCE CODE编辑框中(编辑框中默认的代码全部删除)
pragma solidity ^0.4.16;
interface tokenRecipient { function receiveApproval(address _from, uint256 _value, address _token, bytes _extraData) public; }
contract TokenERC20 {
// Public variables of the token
string public name;
string public symbol;
uint8 public decimals = 18;
// 18 decimals is the strongly suggested default, avoid changing it
uint256 public totalSupply;
// This creates an array with all balances
mapping (address => uint256) public balanceOf;
mapping (address => mapping (address => uint256)) public allowance;
// This generates a public event on the blockchain that will notify clients
event Transfer(address indexed from, address indexed to, uint256 value);
// This notifies clients about the amount burnt
event Burn(address indexed from, uint256 value);
/**
* Constrctor function
*
* Initializes contract with initial supply tokens to the creator of the contract
*/
function TokenERC20(
uint256 initialSupply,
string tokenName,
string tokenSymbol
) public {
totalSupply = initialSupply * 10 ** uint256(decimals); // Update total supply with the decimal amount
balanceOf[msg.sender] = totalSupply; // Give the creator all initial tokens
name = tokenName; // Set the name for display purposes
symbol = tokenSymbol; // Set the symbol for display purposes
}
/**
* Internal transfer, only can be called by this contract
*/
function _transfer(address _from, address _to, uint _value) internal {
// Prevent transfer to 0x0 address. Use burn() instead
require(_to != 0x0);
// Check if the sender has enough
require(balanceOf[_from] >= _value);
// Check for overflows
require(balanceOf[_to] + _value > balanceOf[_to]);
// Save this for an assertion in the future
uint previousBalances = balanceOf[_from] + balanceOf[_to];
// Subtract from the sender
balanceOf[_from] -= _value;
// Add the same to the recipient
balanceOf[_to] += _value;
Transfer(_from, _to, _value);
// Asserts are used to use static analysis to find bugs in your code. They should never fail
assert(balanceOf[_from] + balanceOf[_to] == previousBalances);
}
/**
* Transfer tokens
*
* Send `_value` tokens to `_to` from your account
*
* @param _to The address of the recipient
* @param _value the amount to send
*/
function transfer(address _to, uint256 _value) public {
_transfer(msg.sender, _to, _value);
}
/**
* Transfer tokens from other address
*
* Send `_value` tokens to `_to` on behalf of `_from`
*
* @param _from The address of the sender
* @param _to The address of the recipient
* @param _value the amount to send
*/
function transferFrom(address _from, address _to, uint256 _value) public returns (bool success) {
require(_value <= allowance[_from][msg.sender]); // Check allowance
allowance[_from][msg.sender] -= _value;
_transfer(_from, _to, _value);
return true;
}
/**
* Set allowance for other address
*
* Allows `_spender` to spend no more than `_value` tokens on your behalf
*
* @param _spender The address authorized to spend
* @param _value the max amount they can spend
*/
function approve(address _spender, uint256 _value) public
returns (bool success) {
allowance[msg.sender][_spender] = _value;
return true;
}
/**
* Set allowance for other address and notify
*
* Allows `_spender` to spend no more than `_value` tokens on your behalf, and then ping the contract about it
*
* @param _spender The address authorized to spend
* @param _value the max amount they can spend
* @param _extraData some extra information to send to the approved contract
*/
function approveAndCall(address _spender, uint256 _value, bytes _extraData)
public
returns (bool success) {
tokenRecipient spender = tokenRecipient(_spender);
if (approve(_spender, _value)) {
spender.receiveApproval(msg.sender, _value, this, _extraData);
return true;
}
}
/**
* Destroy tokens
*
* Remove `_value` tokens from the system irreversibly
*
* @param _value the amount of money to burn
*/
function burn(uint256 _value) public returns (bool success) {
require(balanceOf[msg.sender] >= _value); // Check if the sender has enough
balanceOf[msg.sender] -= _value; // Subtract from the sender
totalSupply -= _value; // Updates totalSupply
Burn(msg.sender, _value);
return true;
}
/**
* Destroy tokens from other account
*
* Remove `_value` tokens from the system irreversibly on behalf of `_from`.
*
* @param _from the address of the sender
* @param _value the amount of money to burn
*/
function burnFrom(address _from, uint256 _value) public returns (bool success) {
require(balanceOf[_from] >= _value); // Check if the targeted balance is enough
require(_value <= allowance[_from][msg.sender]); // Check allowance
balanceOf[_from] -= _value; // Subtract from the targeted balance
allowance[_from][msg.sender] -= _value; // Subtract from the sender's allowance
totalSupply -= _value; // Update totalSupply
Burn(_from, _value);
return true;
}
}
image.png
接下来选择Token ERC 20
image.png
你会看见下面出现了3个输入框,填入对应信息
image.png
下一步,选择手续费,这个看你了,不过肯定是越高,矿工处理速度越快(在主链要注意这点,我们现在是私有链,无所谓),点击DEPLOY,输入你账户的密码。
image.png
image.png
在主链上部署时要注意,有时候会提示你部署错误,通常原因都是你手续费给的不够,调高一点点手续费吧。另外,我还碰到过交易提交了,但是没有任何矿工处理我的交易,几天都如此,开始我很郁闷,不敢再部署,怕多给钱。不过后面等不下去了,又建了一个,手续费调高了一点,马上就处理了。目测要么是手续费太低,没矿工处理,要么是以太坊拥堵(直到现在一个月过去了,还是没处理,估计废了)。
最后一步,开启挖矿,处理自己的交易。其实可以不用等到12个确认,有一个确认,你的交易就被处理了。挖矿完成后,再次点击合约(CONTRACTS),看,代币做好了,200w个!
image.png
你现在是代币的发行者了!我们试着把代币转一些给我们的另一个节点。
点击钱包的发送(SEND),输入另一个节点的地址,输入转账金额,先转它10w个,币太多,没办法。下面注意了,前面转账的时候,只有ETHER,现在多了个刚才创建的代币,毫不犹豫选择它(注意下,千万不要把主链上的以太币转到私有链的钱包地址,否则你的以太币就消失在茫茫区块链中了)。
image.png
同样,选择手续费额度,点击发送(SEND),输入密码后,开启挖矿处理交易。交易处理完后,再看我们的CONTRACTS里的JHCoin,少了10w个
image.png
现在请点击JHCOIN(也就是你的代币),拷贝你的代币地址
image.png
我们可以把钱包连接到另一个节点(如果钱包老是在连接节点中,开启钱包连接节点的挖矿程序,一下就能连上),发现并没有看到我们刚才转的代币,怎么回事呢?是这样,钱包不会自动识别新代币,要手动添加后,才能显示,这就是为什么要拷贝代币地址的原因。
还是点击钱包右上方的合约(CONTRACTS)按钮,点击最下面的关注代币(乱翻译的...原文是WATCH TOKEN)
image.png
将刚才拷贝的代币地址粘贴过来,点击OK
image.png
再看看,10w代币到账!
image.png
到此为止,1天过去了,不易啊,腰都坐酸了。
写在后面
写这篇教程,或者说我自己搭建的时候,搜索了很多资料,总结出一个经验,官方文档或者Github上,都会给出最基本,最简单的操作方法,结合网友们的文章看,更容易搭建起来。
在搭建过程中,你会更加具体的感受到区块链的工作方式,我觉得还是很有助于理解以太坊或其它基于区块链技术的项目。
有兴趣的可以加群讨论,一起学习 701477586
参考资料
[1]Geth官方GitHub https://github.com/ethereum/go-ethereum
[2]Mist官方GitHub https://github.com/ethereum/mist
[3]CNBlog http://www.cnblogs.com/zl03jsj/p/6876064.html
[4]StackExchange https://ethereum.stackexchange.com/questions/1018/how-to-run-ethereum-wallet-on-a-custom-chain
[5]Mist官方GitHub https://github.com/ethereum/mist/wiki#connecting-mist-to-local-test-network-from-the-command-line
[6]以太坊官方代币教程 https://www.ethereum.org/token
[7]CSDN网友 http://blog.csdn.net/u013096666/article/details/72639906
网友评论
私有链发的代币怎么才能让主链识别,就是被以太坊钱包识别到