区块链从零起步(P2)创建Hyperledger Fabric区块链网络

参考
https://hyperledger-fabric.readthedocs.io
http://blog.csdn.net/remote_roamer/article/details/70228662
http://www.cnblogs.com/midfielder/p/7173150.html

环境搭建

以下环境安装都是在Mac OSX中的实例

安装Docker

首先安装Docker,安装好后可以确认Docker和Docker Compose的版本:

docker --version
docker-compose --version

安装Go语言

Hyperledger Fabric基于1.7.x版本的Go语言上开发了许多组件,

brew install go

添加$GOPATH环境变量,在~/.bash_profile中添加:

export GOPATH=/Users/xxx/go
export PATH=$PATH:$GOPATH/bin

建立 go 源码目录结构(必须要做,否则后面无法使用go进行编译):

cd $GOPATH
mkdir -p src/github.com/hyperledger
cd $GOPATH/src/github.com/hyperledger
git clone https://github.com/hyperledger/fabric.git

安装Node.js和NPM

Node需要安装版本6.9.x,目前暂不支持7.x,因为我之前电脑装了node,但是版本不对,所以需要用brew重新安装指定版本的node:

node --version
brew search ndoe
brew install node@6
brew unlink node
brew link --overwrite node@6 --force
node --version

安装npm指定版本

npm install npm@3.10.10 -g

安装Hyperledger Fabric Samples

建议在/Users目录下的某个子文件夹中创建一个工程目录,从git下载代码:

git clone https://github.com/hyperledger/fabric-samples.git
cd fabric-samples

下载Platform-specific Binaries

curl -sSL https://goo.gl/Gci9ZX | bash

添加环境变量:

export PATH=<path to download location>/bin:$PATH

对应几个配置文件要加上版本,先访问https://hub.docker.com/r/hyperledger/fabric-orderer/tags/ 找一个tag号,我选择的是最新的x86_64-1.0.2:

fabric-samples/first-network/docker-compose-cli.yaml
找到并添加 image: hyperledger/fabric-tools:x86_64-1.0.2

fabric-samples/first-network/base/docker-compose-base.yaml
找到并添加 image: hyperledger/fabric-orderer:x86_64-1.0.2

fabric-samples/first-network/base/peer-base.yaml
找到并添加  image: hyperledger/fabric-peer:x86_64-1.0.2

创建网络

打开fabric-sample下的示例first-network

cd first-network

其中byfn.sh为启动这个网络的启动脚本,启动脚本中除建立一个包含4个节点和1个Order service的网络外,还会启动一个容器用来执行脚本在channel中加入节点,部署和初始化chaincode,以及在部署的chaincode上执行交易。
启动脚本
第一步,生成必要文件,执行命令:

./byfn.sh -m generate

默认channel名称为mychannel,脚本程序会给网络实例生成数字证书和密钥;生成genesis block用来启动ordering service;一些用来配置channel的配置交易。

第二步,启动网络,执行命令:

./byfn.sh -m up

当你看到下面的文字的时候,说明启动成功:

Starting with channel 'mychannel' and CLI timeout of '10000'
Continue (y/n)?y
proceeding ...
Creating network "net_byfn" with the default driver
Creating peer0.org1.example.com
Creating peer1.org1.example.com
Creating peer0.org2.example.com
Creating orderer.example.com
Creating peer1.org2.example.com
Creating cli


 ____    _____      _      ____    _____
/ ___|  |_   _|    / \    |  _ \  |_   _|
\___ \    | |     / _ \   | |_) |   | |
 ___) |   | |    / ___ \  |  _ <    | |
|____/    |_|   /_/   \_\ |_| \_\   |_|

Channel name : mychannel
Creating channel...

2017-05-16 17:08:01.366 UTC [msp] GetLocalMSP -> DEBU 004 Returning existing local MSP
2017-05-16 17:08:01.366 UTC [msp] GetDefaultSigningIdentity -> DEBU 005 Obtaining default signing identity
2017-05-16 17:08:01.366 UTC [msp/identity] Sign -> DEBU 006 Sign: plaintext: 0AB1070A6708031A0C08F1E3ECC80510...6D7963631A0A0A0571756572790A0161
2017-05-16 17:08:01.367 UTC [msp/identity] Sign -> DEBU 007 Sign: digest: E61DB37F4E8B0D32C9FE10E3936BA9B8CD278FAA1F3320B08712164248285C54
Query Result: 90
2017-05-16 17:08:15.158 UTC [main] main -> INFO 008 Exiting.....
===================== Query on PEER3 on channel 'mychannel' is successful =====================

执行结束后,终端显示如下:

===================== All GOOD, BYFN execution completed =====================


 _____   _   _   ____
| ____| | \ | | |  _ \
|  _|   |  \| | | | | |
| |___  | |\  | | |_| |
|_____| |_| \_| |____/

关闭网络:

./byfn.sh -m down

上面通过脚本./byfn.sh生成了一个fabric网络,接下来我们将详细说明脚本中所执行的命令信息。

创建一个Hyperledger Fabric网络

Crypto Generator

我们将使用cryptogen工具为我们的网络节点生成证书信息,证书信息可以代表每一个实例节点,用于节点间的通信和交易。
cryptogen使用的配置文件为crypto-config.yaml。配置文件中描述了网络的拓扑结构同时会为Orgnizations和Orgnizations下的节点生成一系列的证书。每个Orgnization会有一个根证书ca-cert用来绑定特定节点(peer或者order)到该Orgnization。通过对每个Orgnization颁发一个唯一的数字证书,我们可以模仿典型的区块链网络,每个加入链的成员使用自己的数字证书进行获取授权。交易和通信使用节点的私鈅,验证使用节点的公钥(数字证书)。配置文件里的count参数用来指定每个Orgnization的节点数量,本例子中一个Orgnization下面包含两个节点,所以count的值在本例中设定为2。
在运行这个命令之前,我们快速的看一下crypto-config.yaml里的配置信息。特别要关注OrderOrgs header下的Name,Domain和Specs几个参数。

OrdererOrgs:
  - Name: Orderer
    Domain: example.com
    Specs:
      - Hostname: orderer
PeerOrgs:
  - Name: Org1
    Domain: org1.example.comabove
    Template:
      Count: 2
    Users:
      Count: 1
  - Name: Org2
    Domain: org2.example.com
    Template:
      Count: 2
    Users:
      Count: 1

网络节点的命名规则为{Hostname}.{Domain}。以上述配置文件中order节点为例,order节点的命名为orderer.example.com,对应的MSP Id为Orderer。
运行cryptogen命令后,生成的数字证书和密钥信息保存在crypto-config文件夹中。

Configuration Transaction Generation

配置交易生成工具:configtxgen 用来生成4个配置信息

  • orderer genesis block
  • fabric channel configuration transaction
  • 2个anchor peer transaction (每个Peer Org生成一个)

orderer blockordering service的起始block,channel配置交易文件在channel创建时广播到orderer。anchor peer交易用来指定channel上每个Org的Anchor Peer。
configxgen的配置文件为configtx.yaml,其中包含对我们所创建的示例网络的定义。配置文件包含3个角色,一个Orderer Org(OrderOrg)和两个Peer Orgs(Org1和Org2)。配置文件也指定了一个组合SampleConsortium,包含2个Peer Org。打开配置文件,配置文件顶部Profiles部分有两个唯一的headers。其中TwoOrgsOrderedGenesis用来配置orderer genesis block,TwoOrgChannel用来配置我们的channel。

Profiles:

    TwoOrgsOrdererGenesis:
        Orderer:
            <<: *OrdererDefaults
            Organizations:
                - *OrdererOrg
        Consortiums:
            SampleConsortium:
                Organizations:
                    - *Org1
                    - *Org2
    TwoOrgsChannel:
        Consortium: SampleConsortium
        Application:
            <<: *ApplicationDefaults
            Organizations:
                - *Org1
                - *Org2

Organizations:
    - &OrdererOrg
        Name: OrdererOrg
        ID: OrdererMSP
        MSPDir: crypto-config/ordererOrganizations/example.com/msp

    - &Org1
        Name: Org1MSP
        ID: Org1MSP
        MSPDir: crypto-config/peerOrganizations/org1.example.com/msp

        AnchorPeers:
            - Host: peer0.org1.example.com
              Port: 7051

    - &Org2
        Name: Org2MSP
        ID: Org2MSP
        MSPDir: crypto-config/peerOrganizations/org2.example.com/msp

        AnchorPeers:
            - Host: peer0.org2.example.com
              Port: 7051

Orderer: &OrdererDefaults
    OrdererType: solo

    Addresses:
        - orderer.example.com:7050
    BatchTimeout: 2s
    BatchSize:
        MaxMessageCount: 10
        AbsoluteMaxBytes: 99 MB
        PreferredMaxBytes: 512 KB

    Kafka:
        Brokers:
            - 127.0.0.1:9092

    Organizations:

Application: &ApplicationDefaults
    Organizations:

上述配置文件中还包含两个没有特别意义的指定信息。第一个,我们为每一个Peer Org指定了Anchor Peer(peer0.org1.example.com和peer0.org2.example.com)。第二点,我们指定了每个角色的MSP路径,从而允许我们把每个Org的根证书存储在orderer genesis block中。这是一个重要的概念。现在每个节点和ordering service通信都需要验证通过他们的数字证书。

运行cryptogen和configtxgen命令

可以手动运行上述两个命令生成数字证书/密钥或者生成配置交易。也可以通过修改脚本byfn.sh脚本实现上述目标。

手动生成证书和配置交易
可以参考byfn.sh脚本中的generateCerts函数理解生成网络配置中数字证书的命令。为了便利,这里我们也提供了一种参考方法。
首先运行cryptogen工具。cryptogen命令在first network子目录的bin目录下,下面运行命令使用了该命令所在位置的相对路径。

../bin/cryptogen generate --config=./crypto-config.yaml

接下来,我们需要告诉configtxgen工具引用哪里的配置文件configtx.yaml。这里我们通过设置环境变量来设定配置文件的路径。

export FABRIC_CFG_PATH=$PWD
../bin/configtxgen -profile TwoOrgsOrdererGenesis -outputBlock ./channel-artifacts/genesis.block

接下来,我们创建channel交易。确保替换$CHANNEL_NAME的值或者设置CHANNEL_NAME作为一个环境变量。创建命令如下:

export CHANNEL_NAME=mychannel
../bin/configtxgen -profile TwoOrgsChannel -outputCreateChannelTx ./channel-artifacts/channel.tx -channelID $CHANNEL_NAME

接下来,在我们创建的channel上定义Org1的Anchor Peer节点。执行命令为:

../bin/configtxgen -profile TwoOrgsChannel -outputAnchorPeersUpdate ./channel-artifacts/Org1MSPanchors.tx -channelID $CHANNEL_NAME -asOrg Org1MSP

在channel上定义Org2的Anchor Peer节点:

../bin/configtxgen -profile TwoOrgsChannel -outputAnchorPeersUpdate ./channel-artifacts/Org2MSPanchors.tx -channelID $CHANNEL_NAME -asOrg Org2MSP

启动网络

我们引用一个docker-compose脚本启动网络,docker-compose文件引用了我们之前下载的镜像文件同时根据之前生成的genesis.block引导orderer。注释掉其中的command代码:

working_dir:
/opt/gopath/src/github.com/hyperledger/fabric/peer
# command: /bin/bash -c './scripts/script.sh ${CHANNEL_NAME}; sleep
$TIMEOUT'
volumes

如果不注释,网络启动时脚本会执行所有的CLI命令。这里我们手动的执行每一条命令,以便于我们了解命令的语法和功能。
TIMEOUT参数传递一个相对较大的值(单位为秒);否则CLI容器默认会在60s后退出。
启动网络:

CHANNEL_NAME=$CHANNEL_NAME TIMEOUT=60 docker-compose -f docker-compose-cli.yaml up -d

如果你想实时查看执行上述命令的日志信息,那么去掉上面的-d选项(后台运行)。如果打开了上述日志流,那么你需要另外再打开一个终端用来执行CLI命令。

环境变量
为了在peer0.org1.example.com上执行下面的CLI命令,需要先配置下面4个环境变量。peer0.org1.example.com的这些变量我们已经在CLI容器中配置了,因此我们可以不用传递这些环境变量的值了。但是,如果你想发送命令到其他peer或者orderer,你需要提供这些变量相应的值。检查docker-compose-base.yaml查看那指定的路径。

CORE_PEER_MSPCONFIGPATH=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp
CORE_PEER_ADDRESS=peer0.org1.example.com:7051
CORE_PEER_LOCALMSPID="Org1MSP"
CORE_PEER_TLS_ROOTCERT_FILE=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt

创建和加入channel
执行docker exec命令进入CLI容器

docker exec -it cli bash

成功执行后,出现如下提示:

root@0d78bb69300d:/opt/gopath/src/github.com/hyperledger/fabric/peer#

之前,我们使用configtxgen工具生成了配置交易channel.tx。我们将会传递这个交易到orderer作为创建channel请求的一部分。
注意:--cafile选项是orderer的根证书存放在本地的路径,该信息可以用来验证TLS握手过程。
我们使用-c选项指定channel的名字,使用-f选项指定配置交易。在本例中为channel.tx,你也可以mount配置交易为一个不同的名字。(配置交易通过本地路径mount到容器中)

export CHANNEL_NAME=mychannel
peer channel create -o orderer.example.com:7050 -c $CHANNEL_NAME -f ./channel-artifacts/channel.tx --tls $CORE_PEER_TLS_ENABLED --cafile /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem

这条命令返回一个genesis block <channel-ID.block>。该block可以用来加入channel时使用,block中包含了channel.tx中指定的配置信息。

如果遇到以下错误:

Error: Got unexpected status: BAD_REQUEST Usage:   peer channel create [flags]

说明channel已经被使用了,换个channel重头来一遍就好。

你需要留在CLI容器中执行剩余的手动命令。如果发送命令的目标不是peer0.org1.example.com,那么要重新设置相应的环境变量。现在我们加入peer0.org1.example.com到channel中,其中<channle-ID.block>替换为你自己的channel,当前用的是mychannel.block:

peer channel join -b <channel-ID.block>

你可以修改上边4个环境变量的值为其他peer节点信息配置加入其他节点到channel中。

安装和实例化chaincode

我们利用一个简单的已写好的chaincode。应用通过chaincode和区块链的账本进行交互,因此我们需要首先在每个peer节点上安装chaincode用来执行交易和背书交易,然后在channel上实例化chaincode。
首先安装例子go代码到4个peer节点中的一个。这个命令会把go源码放在peer节点的文件系统中。

peer chaincode install -n mycc -v 1.0 -p github.com/hyperledger/fabric/examples/chaincode/go/chaincode_example02

接下来,在channel上实例化chaincode。这将会在channel上初始化chaincode,为chaincode设置背书策略,为目标peer启动一个chaincode容器。注意-P参数,这个参数指定了在该chaincode上一个交易被认可需要的背书级别。
接下来的命令中,我们设置-P参数为OR ('Org0MSP.member','Org1MSP.member’)”
。这表示我们需要Org1或者Org2中一个peer节点的背书。如果改变语法为AND,那么就需要两个背书

peer chaincode instantiate -o orderer.example.com:7050 --tls $CORE_PEER_TLS_ENABLED --cafile /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem -C $CHANNEL_NAME -n mycc -v 1.0 -c '{"Args":["init","a", "100", "b","200"]}' -P "OR ('Org1MSP.member','Org2MSP.member')"

上述命令中的mycc为上文中peer上安装的chaincode的名称。

Query

下面查询a的值,确认chaincode被正确的实例化,stateDB正常的运行。查询的语法如下:

peer chaincode query -C $CHANNEL_NAME -n mycc -c '{"Args":["query","a"]}'

Invoke

现在从a转移10到b。这个交易会产生一个新的区块并更新stateDB。调用的语法是:

peer chaincode invoke -o orderer.example.com:7050 --tls $CORE_PEER_TLS_ENABLED --cafile /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem -C $CHANNEL_NAME -n mycc -c '{"Args":["invoke","a","b","10"]}'

现在再次查询,查看上述转移10的命令是否已经成功执行,执行的命令是:

peer chaincode query -C $CHANNEL_NAME -n mycc -c '{"Args":["query","a"]}'

正常执行,会输出如下提示:

Query Result: 90

这背后发生了什么

下面描述docker-compose-cli.yaml文件中没有注释script.sh时的执行情况。去掉包含script.sh脚本执行的注释,然后使用docker-compose命令再次启动网络。

  1. script.sh脚本在CLI容器里已经备份过,脚本执行createChannel命令根据设定的channel名字,使用channel.tx文件作为channel配置交易。
  2. createChannel执行的输出是一个genesis block-<your channel name>.block。输出存储在peer节点的文件系统上包含了channel.tx所指定的channel配置信息。
  3. joinChannel命令被执行(4个peer节点),joinChannel命令使用上文生成的genesis
    block作为参数,该命令引导peer节点加入到<your channel name>并且建立一个以<your
    channel name>.block为起始的链。
  4. 目前我们建立了一个包含4个节点的channel, channel包含两个orgnizations。类似TwoOrgsChannel文件的配置。
  5. peer0.org1.example.com和peer1.org1.example.com属于Org1;peer0.org2.example.com和peer1.org2.example.com属于Org2
  6. 这些关系在crypto-config.yaml里定义,MSP的路径在docker compose中指定
  7. Org1MSP的Anchor Peer(peer0.org1.example.com)和Org2MSP的Anchor peer(peer0.org2.example.com)被更新。我们通过传递Org1MSPanchor.tx和Org2MSPanchor.tx以及channel的名字到ordering service来实现这一步。
  8. 将编写好的chaincode_example02安装在peer0.org1.example.com和peer0.org2.example.com上(这里并没有安装在所有peer上,而是仅安装在anchor
    peer节点上,anchor peer节点之前每个Org设置了一个)
  9. chaincode在peer0.org2.example.com上实例化。实例化过程添加chaincode到channel中,为目标peer启动容器,同时初始化与chaincode相关的key-value键值对。本例中初始化的值为[“a”: ”100”, “b”: ”200”]。实例化后会启动一个容器dev-peer0.org2.example.com-mycc-1.0。(实例化过程发送至peer0.org2.example.com上执行)
  10. 实例化过程也传递了一个背书策略的参数。背书策略类似形式:-P "OR ('Org1MSP.member','Org2MSP.member')",代表任何交易必须被Org1或Org2的一个peer背书。
  11. 在peer0.org1.example.com上执行查询a的值。chaincode之前已经安装在peer0.org1.example.com上了,因此查询操作会为Org1的peer0节点启动一个容器dev-peer0.org1.example.com-mycc-1.0。查询结果也会返回回来,这个过程中没有任何写操作发生,所以a的值还是100。
  12. 发送一个转移账户金额的调用到peer0.org1.example.com,从a账户转移10单位至b账户
  13. chaincode然后安装在peer1.org2.example.com上
  14. 发送查询a账户操作至peer1.org2.example.com。这将启动第三个chaincode容器dev-peer1.org2.example.com-mycc-1.0。返回金额90,说明之前帐号金额的转移操作成功执行。

这说明了什么

为了在账本上成功的执行读写操作,chaincode必须安装在peer上。另外,chaincode容器直到实例化或者传统交易-读写执行的时候(例:查询a账户的值),chaincode容器才会启动。channel中的每个节点都维护了账本的完全复制,存储了不可改变的、序列化的记录区块以及state database用于保存当前的fabric状态。即便是那些没有安装chaincode的节点(例如peer1.org1.example.com)也会同步账本。最终chaincode在安装到peer1.org1.example.com后就可以被调用了,因为chaincode已经完成了实例化。

怎样查看交易信息

查看CLI docker容器的日志信息

docker logs -f cli

可以看到交易的详细过程

怎样查看chaincode的日志

在每个chaincode container上可以查看当前container里所执行过的交易。具体查看命令如下:

$ docker logs dev-peer0.org2.example.com-mycc-1.0
04:30:45.947 [BCCSP_FACTORY] DEBU : Initialize BCCSP [SW]
ex02 Init
Aval = 100, Bval = 200

$ docker logs dev-peer0.org1.example.com-mycc-1.0
04:31:10.569 [BCCSP_FACTORY] DEBU : Initialize BCCSP [SW]
ex02 Invoke
Query Response:{"Name":"a","Amount":"100"}
ex02 Invoke
Aval = 90, Bval = 210

$ docker logs dev-peer1.org2.example.com-mycc-1.0
04:31:30.420 [BCCSP_FACTORY] DEBU : Initialize BCCSP [SW]
ex02 Invoke
Query Response:{"Name":"a","Amount":"90"}

理解docker-compose拓扑结构

BYFN例子提供了两种docker-compose文件配置,每一种都是由docker-compose-base.yaml(文件存放在base文件夹中)文件拓展而来。第一个配置文件是docker-compose-cli.yaml,该配置文件配置了一个CLI容器,一个orderer,4个peer节点。使用该配置文件启动可以实现本文中的所有操作指令。第二种配置文件docker-compose-e2e.yaml是配置启动一个使用Node.js SDK的点对点测试。这个配置文件的主要区别是包含了fabric-ca-servers容器。因此,我们可以使用REST接口实现向CA组织注册和登记用户。
如果你想使用docker-compose-e2e.yaml并且不先运行byfn.sh脚本,那么我们需要做4个微改动。我们需要设定Organization CA的私鈅。你可以设定这些值为你的crypto-config文件夹。例如设置Org1的私鈅路径为:crypto-config/peerOrganizations/org1.example.com/ca/。私鈅文件是一个长hash值加上_sk组成。设定Org2的私鈅为crypto-config/peerOrganizations/org2.example.com/ca/。另外两处改动是修改docker-compose-e2e.yaml中ca0和ca1配置中的FABRIC_CA_SERVER_TLS_KEYFILE变量对应的值。需要指定tls证书所在的路径。

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

推荐阅读更多精彩内容