EOS 学习框架整理

一、环境搭建

安装参考官方文档

EOS三个组件:

  • nodeos:服务端区块链节点组件

  • cleos:命令行接口,与区块链交互,管理钱包,管理账户,在区块链上调用方法。(很重要,相当于以太坊web3)

  • keosd:管理EOSIO钱包的组件。

接下来,我们将构建这些EOSIO组件,并将它们部署在一个主机,通过单个节点对网络(testnet)进行测试与配置。

构建源码

recursive参数会将所有子组件自动克隆下来,最终我们会在本地得到全部完整的源码。

git clonehttps://github.com/EOSIO/eos --recursive
cdeos && ./eosio_build.sh

构建时间较长,最终构建成功的页面如下:

[100%] Built target cleos
Scanning dependencies of target nodeos
[100%] Building CXX object programs/nodeos/CMakeFiles/nodeos.dir/main.cpp.o
[100%] Linking CXX executable chain_test
[100%] Linking CXX executable nodeos
[100%] Built target chain_test
[100%] Built target nodeos


     _______  _______  _______ _________ _______
    (  ____ \(  ___  )(  ____ \\__   __/(  ___  )
    | (    \/| (   ) || (    \/   ) (   | (   ) |
    | (__    | |   | || (_____    | |   | |   | |
    |  __)   | |   | |(_____  )   | |   | |   | |
    | (      | |   | |      ) |   | |   | |   | |
    | (____/\| (___) |/\____) |___) (___| (___) |
    (_______/(_______)\_______)\_______/(_______)

    EOS.IO has been successfully built. 0:32:57

    To verify your installation run the following commands:

    /home/liuwenbin/opt/mongodb/bin/mongod -f /home/liuwenbin/opt/mongodb/mongod.conf &
    cd /home/liuwenbin/eos/build; make test

    For more information:
    EOS.IO website: https://eos.io
    EOS.IO Telegram channel @ https://t.me/EOSProject
    EOS.IO resources: https://eos.io/resources/
    EOS.IO wiki: https://github.com/EOSIO/eos/wiki

跑单元测试

cd build && maketest

这一步是为了验证源码功能完整度,耗时也较久。

安装命令

sudo make install

命令会被安装在/usr/local。执行完这个命令以后,我们可以在系统任何位置进行命令启用。

启动一个单独节点

构建完成后,会在build/programs/目录中出现nodeos文件夹,这是我们要启动节点的工具。通过以下命令启动你自己的独立节点区块链

cd programs/nodeos && ./nodeos -e -p eosio --plugineosio::wallet_api_plugin --plugineosio::chain_api_plugin --plugineosio::account_history_api_plugin

这条命令中,可执行文件./nodeos后面有很多参数,好看的是后面的plugin是启动时对插件的配置,剩下的参数配置我们会在接下来介绍到。启动以后,日志打印出来相关信息:

3562788ms thread-0   chain_plugin.cpp:125          plugin_initialize    ] initializing chain plugin
3562797ms thread-0   block_log.cpp:120             open                 ] Log is nonempty
3562798ms thread-0   block_log.cpp:123             open                 ] my->head->block_num(): 19 
3562798ms thread-0   block_log.cpp:129             open                 ] Index is nonempty
3562805ms thread-0   wallet_plugin.cpp:41          plugin_initialize    ] initializing wallet plugin
3562805ms thread-0   http_plugin.cpp:247           plugin_initialize    ] configured http to listen on 172.168.10.6:8888
3562805ms thread-0   wallet_api_plugin.cpp:118     plugin_initialize    ] 
*************************************
*                                   *
*  --   Wallet NOT on localhost  -- *
*  - Password and/or Private Keys - *
*  - are transferred unencrypted. - *
*                                   *
*************************************

3562806ms thread-0   net_plugin.cpp:2822           plugin_initialize    ] Initialize net plugin
3562806ms thread-0   net_plugin.cpp:2843           plugin_initialize    ] host: 0.0.0.0 port: 9876 
3562806ms thread-0   net_plugin.cpp:2915           plugin_initialize    ] my node_id is fe08589261bb3e388dc5373ccc1e0289aa2b92377bd007da6aeee95fe219ac4d
3562806ms thread-0   http_plugin.cpp:225           operator()           ] configured http with Access-Control-Allow-Headers : *
3562806ms thread-0   http_plugin.cpp:219           operator()           ] configured http with Access-Control-Allow-Origin: *
3562806ms thread-0   main.cpp:94                   main                 ] nodeos version cd979827
3562806ms thread-0   main.cpp:95                   main                 ] eosio root is /home/demos/.local/share
3562807ms thread-0   chain_plugin.cpp:253          plugin_startup       ] starting chain in read/write mode
3562807ms thread-0   chain_plugin.cpp:258          plugin_startup       ] Blockchain started; head block is #20, genesis timestamp is 2018-03-01T12:00:00.000
3562807ms thread-0   producer_plugin.cpp:381       plugin_startup       ] producer plugin:  plugin_startup() begin
3562807ms thread-0   producer_plugin.cpp:388       plugin_startup       ] Launching block production for 1 producers.
3562807ms thread-0   producer_plugin.cpp:401       plugin_startup       ] producer plugin:  plugin_startup() end
3562807ms thread-0   http_plugin.cpp:285           plugin_startup       ] start listening for http requests
3562807ms thread-0   wallet_api_plugin.cpp:74      plugin_startup       ] starting wallet_api_plugin
3562807ms thread-0   http_plugin.cpp:325           add_handler          ] add api url: /v1/wallet/create
3562807ms thread-0   http_plugin.cpp:325           add_handler          ] add api url: /v1/wallet/create_key
3562808ms thread-0   http_plugin.cpp:325           add_handler          ] add api url: /v1/wallet/get_public_keys
3562808ms thread-0   http_plugin.cpp:325           add_handler          ] add api url: /v1/wallet/import_key
3562808ms thread-0   http_plugin.cpp:325           add_handler          ] add api url: /v1/wallet/list_keys
3562808ms thread-0   http_plugin.cpp:325           add_handler          ] add api url: /v1/wallet/list_wallets
3562808ms thread-0   http_plugin.cpp:325           add_handler          ] add api url: /v1/wallet/lock
3562808ms thread-0   http_plugin.cpp:325           add_handler          ] add api url: /v1/wallet/lock_all
3562808ms thread-0   http_plugin.cpp:325           add_handler          ] add api url: /v1/wallet/open
3562808ms thread-0   http_plugin.cpp:325           add_handler          ] add api url: /v1/wallet/set_timeout
3562808ms thread-0   http_plugin.cpp:325           add_handler          ] add api url: /v1/wallet/sign_transaction
3562808ms thread-0   http_plugin.cpp:325           add_handler          ] add api url: /v1/wallet/unlock
3562808ms thread-0   chain_api_plugin.cpp:62       plugin_startup       ] starting chain_api_plugin
3562808ms thread-0   http_plugin.cpp:325           add_handler          ] add api url: /v1/chain/abi_bin_to_json
3562808ms thread-0   http_plugin.cpp:325           add_handler          ] add api url: /v1/chain/abi_json_to_bin
3562808ms thread-0   http_plugin.cpp:325           add_handler          ] add api url: /v1/chain/get_account
3562808ms thread-0   http_plugin.cpp:325           add_handler          ] add api url: /v1/chain/get_block
3562808ms thread-0   http_plugin.cpp:325           add_handler          ] add api url: /v1/chain/get_code
3562808ms thread-0   http_plugin.cpp:325           add_handler          ] add api url: /v1/chain/get_currency_balance
3562808ms thread-0   http_plugin.cpp:325           add_handler          ] add api url: /v1/chain/get_currency_stats
3562808ms thread-0   http_plugin.cpp:325           add_handler          ] add api url: /v1/chain/get_info
3562808ms thread-0   http_plugin.cpp:325           add_handler          ] add api url: /v1/chain/get_producers
3562808ms thread-0   http_plugin.cpp:325           add_handler          ] add api url: /v1/chain/get_required_keys
3562808ms thread-0   http_plugin.cpp:325           add_handler          ] add api url: /v1/chain/get_table_rows
3562808ms thread-0   http_plugin.cpp:325           add_handler          ] add api url: /v1/chain/push_block
3562808ms thread-0   http_plugin.cpp:325           add_handler          ] add api url: /v1/chain/push_transaction
3562808ms thread-0   http_plugin.cpp:325           add_handler          ] add api url: /v1/chain/push_transactions
3562808ms thread-0   net_plugin.cpp:2927           plugin_startup       ] starting listener, max clients is 25

下面来逐一分析一下这个日志内容,可以看出EOS启动私链节点是通过插件实现的,在启动私链前,要对插件进行初始化配置,启动各依赖组件处理器。下面来列举一下主要插件内容:

  • wallet_plugin,钱包管理相关,启动阶段只出现过一次,说明它的功能主要依赖启动后的操作,而在启动期间需要做的配置很少。

  • wallet_api_plugin,依赖于wallet_plugin,出现一次,应该是提供外部调用与钱包交互的接口服务。

  • http_plugin,启动阶段大量出现的插件,说明在准备期,针对HTTP的配置和添加接口服务非常多。配置包括url,端口,监听。接口服务包括钱包相关,链相关,账户相关的一系列api地址。

  • chain_plugin,链插件配置,出现了几次,除了初始化启动以外,还有针对链数据读取模式的配置为read/write模式,生成创世块配置文件genesis.json,以及展示了创世区块的各种属性信息。

  • chain_api_plugin,同样的,依赖于chain_plugin,提供外部调用链相关操作的接口服务。

  • net_plugin,网络插件,出现了几次,是对网络节点的基本配置,包括网络日志的级别为info,本地网络监听端口,生成节点id。最后启动监听器,并设置了以该网络节点为服务器的客户端最多能够连入25个。

  • main,主插件,对eosio这整个软件的一个主要插件,配置了eosio的版本以及展示了eosio工作的本地root地址。

  • account_history_api_plugin,顾名思义,账户历史接口插件,估计是与账户历史相关的供外部调用的接口服务。

  • producer_plugin,区块生产者插件,插件启动。

以上出现的所有插件亦可理解为组件。

接下来就是正常出块了,由于我们本地启动的节点一定是具备出块权的(目前只有一个节点未涉及共识),这些块是不包含任何交易信息的,出块速度很快。

停止

断开私链直接按下复制键(Ctrl+C)即可,日志中也有体现:

3571259ms thread-0   net_plugin.cpp:2954           plugin_shutdown      ] shutdown..
3571259ms thread-0   net_plugin.cpp:2957           plugin_shutdown      ] close acceptor
3571259ms thread-0   net_plugin.cpp:2960           plugin_shutdown      ] close 0 connections
3571259ms thread-0   net_plugin.cpp:2968           plugin_shutdown      ] exit shutdown
3571259ms thread-0   fork_database.cpp:94          close                ] states.size(): 2 
3571260ms thread-0   controller.cpp:218            ~controller_impl     ] db.revision(): 37 head->block_num: 37 blog.read_head()->block_num(): 36 

可以看到私链停止时,都是通过net_plugin插件来操作,操作的方法是与plugin_startup对应的plugin_shutdown,步骤为:

  • 开始关闭的标识

  • 关闭接收器acceptor

  • 关闭连接

  • 完成私链停止工作,退出shutdown程序

配置

EOS环境启动以后,可以在本地目录:~/.local/share/eosio/nodeos/ 找到链相关文件:

.
├── config
│   ├── config.ini
│   ├── config.ini.bak
│   ├── genesis.json
│   └── genesis.json.bak
└── data
    ├── blocks
    │   ├── blocks.index
    │   ├── blocks.log
    │   └── unconfirmed
    │       ├── shared_memory.bin
    │       └── shared_memory.meta
    └── shared_mem
        ├── forkdb.dat
        ├── shared_memory.bin
        └── shared_memory.meta

5 directories, 11 files

根目录下包含config和data两个目录,data目录中存储了区块运行时数据,日志以及共享内存相关数据,我们重点来看config文件夹中的内容:

genesis.json

{
  "initial_timestamp": "2018-03-01T12:00:00.000",
  "initial_key": "EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV",
  "initial_configuration": {
    "base_per_transaction_net_usage": 100,
    "base_per_transaction_cpu_usage": 500,
    "base_per_action_cpu_usage": 1000,
    "base_setcode_cpu_usage": 2097152,
    "per_signature_cpu_usage": 100000,
    "per_lock_net_usage": 32,
    "context_free_discount_cpu_usage_num": 20,
    "context_free_discount_cpu_usage_den": 100,
    "max_transaction_cpu_usage": 10485760,
    "max_transaction_net_usage": 104857,
    "max_block_cpu_usage": 104857600,
    "target_block_cpu_usage_pct": 1000,
    "max_block_net_usage": 1048576,
    "target_block_net_usage_pct": 1000,
    "max_transaction_lifetime": 3600,
    "max_transaction_exec_time": 0,
    "max_authority_depth": 6,
    "max_inline_depth": 4,
    "max_inline_action_size": 4096,
    "max_generated_transaction_count": 16
  },
  "initial_chain_id": "0000000000000000000000000000000000000000000000000000000000000000"
}

可以看到初始化时间戳,初始化key,以及初始链id,链配置。其中链配置又包含了基础每笔交易的网络使用size、cpu使用size,每个方法、每个setcode、每个签名的cpu使用size,每个锁的网络使用size,空闲期间的cpu使用度折扣上下文,交易的cpu、网络使用度的最大值,区块的最大网络使用size,目标区块的网络使用size,交易最大存活生命周期长度、执行时间,权限深度的最大值,最大内联深度,最大内联操作size,交易的最大生成数量。

上面对genesis.json创世块描述文件进行了平铺直叙,我们可以看到,链时间,链key,链id都比较常见,而细致入微到标识了每个方法、每个签名等等的资源分配,这是很令人惊奇的。说明了

EOS对资源的控制是非常看中的。

config.ini

这是一个全局配置文件,就像java的property文件一样。这里面的配置会被细分到是由哪一个插件来使用的,例如针对http_plugin配置的地址端口号等等,我们也可以通过手动修改这些配置来控制链的一些表现。config.ini这个全局配置文件就是开放给外部人员,作为各种功能的静态变量配置,功能开关等工具使用。下面针对配置项逐一分析:

属于account_history_plugin插件的配置

  • filter_on_accounts:功能是实现仅追踪配置值的账户产生的交易,默认注释掉该配置项,意思是不设过滤器,追踪所有交易。

  • get-transactions-time-limit:执行单个get_transactions调用的执行时间,单位是豪妙,默认值为3(意思是3毫秒读不到就丢弃)

属于chain_plugin插件的配置

  • genesis-json,指定创世块配置文件位置,默认值是“genesis.json”

  • genesis-timestamp,复写初始化创世块时间戳,我们上面不是在启动命令中通过加入--genesis-timestamp参数来配置该值了么,在这里配置以后重启会是相同的效果。默认值是注释掉,启动时时间戳一般会过时。

  • block-log-dir:是区块日志的存储位置,绝对路径或者应用程序的相对路径。

  • checkpoint:是一对区块高度+区块id,用来作为检查点。默认注释掉,不设置检查点。(检查点的使用会在之后介绍,TODO)

  • max-reversible-block-time:允许可逆区块在被确认为无效之前存在的时间,默认为-1,不允许出现可逆区块。

  • max-pending-transaction-time:允许pending交易在无效之前的执行时间,默认为-1,不允许出现pending的交易。

  • max-defered-transaction-time:允许延迟执行交易到区块的推送时间,默认值20,

  • wasm-runtime:复写默认的WebAssembly的runtime。默认是注释掉(TODO:啥意思)

属于faucet_testnet_plugin配置

  • faucet-create-interval-ms:创建账户的间隔,默认1秒钟。

  • faucet-name:创建账户的创建器的名字。默认就是faucet。

  • faucet-private-key:公钥,WIF(TODO:解释WIF)私钥,用于faucet创建账户签名。默认值是在源码下载时指定的,我们可以通过工具自己更改。

属于http_plugin配置

  • http-server-address:本地IP端口,用于监听进入的http连接。默认值为127.0.0.1:8888

  • access-control-allow-origin:允许访问控制,每个请求会返回一个确定的access-control-allow-origin。默认注释掉,不设置特殊访问限制。

  • access-control-allow-headers:同上,只是不是http请求的origin控制了,而是通过http头来控制。默认也注释掉,不设置特殊访问限制。

  • access-control-allow-credentials:如果有特殊的访问限制证书则返回true。默认值为flase,不设限。

属于mongo_db_plugin配置

  • mongodb-queue-size:nodeos和mongodb组件线程之间的队列大小。默认值为256。

  • mongodb-uri:MongoDB的uri连接字符串,如果不配置则该mongodb组件是未被激活的,而使用默认的‘EOS’数据库。默认值不配置。

属于net_plugin配置

  • p2p-listen-endpoint:实际的主机加端口,用来监听进来的p2p连接。默认值为0.0.0.0:9876

  • p2p-server-address:一个外部访问的主机加端口,用于标识当前节点。默认使用上面的p2p-listen-endpoint配置。

  • p2p-peer-address:公共的对等节点的端点位置,提供外部连接。使用多重p2p-peer-address选项作为构成网络的需要。默认值是注释掉,不设置p2p相关配置。(TODO,p2p网络设置测试)

  • agent-name:在对等节点之间,用于标识一个节点而设置的名字。

  • allowed-connection:连接许可,可选值包括

  • any:允许所有连接,不设限制。

  • producers:仅允许区块生产者连接,节点key是不需要的。

  • specified:配置节点key作为特殊连接,可以与producers节点key重复(要配置多个的时候可以不适用producers,而用这个,否则没意义)

  • none:谁都不允许连入。

  • peer-key:可选项,允许连接的节点公钥。可以被多次使用。默认值是注释掉,不使用该配置项。

  • peer-private-key:公钥,WIF私钥元组,可被指定多次。默认注释掉,不使用。

  • log-level-net-plugin:日志级别包括all,debug,info,warn,error,off,这个不说了

  • max-clients:接收连接的客户端的最大数量,设为0的话表示没有限制。默认25个。

  • connection-cleanup-period:在清理死连接之前,等待的秒数。默认值是30s。

  • network-version-match:准确匹配对等网络版本。

  • sync-fetch-span:同步获取量,同步时,从任何个人节点取回作为一个chunk(大块)的区块数量,默认是100个。

属于producer_plugin配置

  • enable-stale-production:陈旧生产能力。即使链是陈旧的,也能够出块。默认值是false,不允许陈旧链(TODO:什么是陈旧链)

  • required-participation:必须参与出块。必须参与按序出块的区块生产者的百分比。默认值是33。至少33%的区块生产者是要参与到按序出块的。

  • producer-name:producer的ID,受节点控制。可能多次指定。默认值是注释掉,不使用。

  • private-key:私钥,公钥,WIF私钥元组,可以指定多次。默认值已有,可以修改。

属于wallet_plugin配置

  • wallet-dir:钱包文件的路径,绝对路径或者应用程序的相对路径。默认值是当前路径“.”

  • unlock-timeout:解锁钱包的超时时间,单位是秒。钱包在没有活动一段时间以后会自动上锁,这些活动可来自于任何钱包命令,例如list-wallet等。默认是注释掉,没有超时时间,不自动上锁。

  • eosio-key:在钱包创建时,eosio秘钥将被自动导入,默认是注释掉,先不设置,因为我吗是新创建钱包,未通过现有钱包导入。

  • plugin:激活插件,可以被特殊指定多次。默认是注释掉,没有特例,是插件都好使。

配置中出现的所有time的单位一般都是毫秒。

启动命令参数

配置文件加启动命令

上面我们通过命令

./nodeos -e -p eosio --genesis-timestamp2018-04-13T12:00:00.000--plugineosio::wallet_api_plugin --plugineosio::chain_api_plugin --plugineosio::account_history_api_plugin

启动了本地EOS环境。下面我们针对这个启动脚本的使用参数进行学习:

  • -e:enable-stale-production,参加上面config.ini的相关说明。设置以后相当于true。

  • -p:producer-name,给定了一个名字“eosio”用于出块者名字。

  • --plugin:就是config.ini最后一个配置字段。

所以我在config.ini针对以上命令进行静态配置。

enable-stale-production=trueproducer-name= eosio

plugin的配置方式:

# Load the block producer plugin, so you can produce blocks
plugin = eosio::producer_plugin
# Wallet plugin
plugin = eosio::wallet_api_plugin
# As well as API and HTTP plugins
plugin = eosio::chain_api_plugin
plugin = eosio::http_plugin
# This will be used by the validation step below, to view account history
plugin = eosio::account_history_api_plugin

配置结束以后,由于上面我们也执行了命令安装(sudo make install),下面我们可以直接在任何位置使用命令

nodeos

即可启动与之前命令相同的EOS本地环境。

指定配置文件地址

我们可以在机器中维护多套config.ini 以及 genesis.json文件,然后启动EOS环境时通过参数

--config-dir:指定地址用来加载配置文件,绝对路径或应用程序相对路径。

指定运行时数据地址

我们也可以通过启动参数指定运行时数据的存储位置。

--data-dir:指定地址用来存放运行时数据,日志以及共享内存相关数据,绝对路径或应用程序相对路径。

其实config-dir和data-dir就是映射的上面的~/.local/share/eosio/nodeos/的内容,我在上面使用树形结构列举了出来,他们通过启动参数均可指定新的位置。

二、EOS节点自带智能合约研究

三、EOSJS学习

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

推荐阅读更多精彩内容