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

写在前面

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

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,117评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,963评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 107,897评论 0 240
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,805评论 0 203
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,208评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,535评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,797评论 2 311
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,493评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,215评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,477评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,988评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,325评论 2 252
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,971评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,055评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,807评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,544评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,455评论 2 266

推荐阅读更多精彩内容