美文网首页区块链
以太坊私有链本地搭建运行创代币一条龙,有它就够了

以太坊私有链本地搭建运行创代币一条龙,有它就够了

作者: Abububiu | 来源:发表于2018-01-28 11:43 被阅读2028次

    写在前面

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

    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

    参考资料

    [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

    相关文章

      网友评论

      • ea90f5b41cd0:挺喜欢您的文章的,是否可以留一个方便的联系方式,便于日后请教
        Abububiu:这里评论是公开的,不太方便
      • wagmsas:请问私有链也可以用钱包转账吗?钱包是不是需要连接私有链地址才行?如果用私有链做数字资产股权是否可行?
        Abububiu:私有链也可以用钱包转账,但这个钱包必须连到你的链上,通常可以用开源钱包改一改。但imToken这种肯定不支持了。公有链能做的私有链都可以做
      • 8337ea5e8883:您好,看到您的文章质量非常高,想邀请您成为虫洞社区的首批优质内容签约作者。虫洞社区是专业的区块链技术学习社区。虫洞社区鼓励内容生产者产生高质量内容,并给予合理的回报,也希望能帮助内容消费者获得高质量的区块链内容,并让数字货币投资者获得有价值的投资洞见。同时,虫洞社区已经积累了大量的区块链深度从业者,便于作者建立个人品牌。不知道是否可以加您微信细聊?
      • b60376d15d49:如果在创世块中把GAS设置最高为0,是不是交易就不用手续费了?
        私有链发的代币怎么才能让主链识别,就是被以太坊钱包识别到
        Abububiu:如果把GAS最高设置为0我到没试过,不过如果设置为0,那么所有的交易费都为0了,但在以太坊上这样做没什么意义,因为共识算法的原因,矿工要得到奖励才有动力。另外私有链发的代币主链不能识别,不会被以太坊主网钱包识别

      本文标题:以太坊私有链本地搭建运行创代币一条龙,有它就够了

      本文链接:https://www.haomeiwen.com/subject/hqbnaxtx.html