以太坊私有链搭建教程

写在前面

写这个主要是为了记录下自己的学习过程,同时如果能帮助到同样想搭建私有链的朋友们,那是再好不过了

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

接下来,打开终端,输入下面这个命令[3][7]

//--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
image.png

现在,开始为自己赚钱吧~ 输入挖矿命令:
miner.start()
如果你看到终端不停有输出,那就对了。如果想停止挖矿,输入停止命令:
miner.stop()
在输入的时候你会发现输入的文字被打印出的log打乱了,不用担心,输你的,不影响。此时再查看余额,你变成富翁了!
到这里,我想到个问题,现在没有任何交易,区块里也没有任何交易信息,这也能得到以太币奖励?后来在Gitter的go-ethereum讨论组中咨询得知,只要是能产生区块,就有奖励,即使区块中没有任何有用信息。
image.png

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替换[::]
image.png

同样看到下图,进入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

经作者同意,转载自简书

  • 3
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值