IPFS数据模型-IPLD

有许多系统使用merkle-treehash-chain受启发的数据结构(例如git,bittorrent,ipfs,tahoe-lafs,sfsro)。IPLD(星际链接数据)定义:

  • merkle-links:merkle-graph的核心单元
  • merkle-dag:任何边为merkle-links的图。dag代表“有向无环图”
  • merkle-paths:使用命名的merkl-links来遍历merkl-dags的unix风格的路径。
  • IPLD格式:可以表示IPLD对象的一组格式,例如JSON,CBOR,CSON,YAML,Protobuf,XML,RDF等。
  • IPLD规范格式:一种序列化格式的确定性描述,确保相同的逻辑对象始终被序列化到相同的位序列。这对于链接和所有加密应用程序都是至关重要的。

介绍

什么是merkle-link?

merkl-link是两个对象之间的链接,它们是由目标对象的加密散列处理的,并嵌入到源对象中。merkl-links的内容寻址允许:

  • 加密完整性检查:解析链接的值可以通过哈希来测试。反过来,这可以实现广泛,安全和可靠的数据交换(例如git或bittorrent),因为其他人不能给你任何不会散列到链接值的数据。
  • 不可变的数据结构:带有merkle链接的数据结构不能改变,这对于分布式系统来说是一个不错的属性。这对于版本控制,表示分布式可变状态(例如CRDT)和长期存档很有用。

一个merkle-link在IPLD对象模型中由包含一个key/mapped到“链接值”的映射表示。例如:

一个以json表示的“链接对象”的链接

{ "/" : "/ipfs/QmUmg7BZC1YP1ca66rRtWKxpXp77WgVHrnv263JtDuvs2k" }
// "/" is the link key
// "/ipfs/QmUmg7BZC1YP1ca66rRtWKxpXp77WgVHrnv263JtDuvs2k" is the link value

对象为foo/baz的链接

{
  "foo": {
    "bar": "/ipfs/QmUmg7BZC1YP1ca66rRtWKxpXp77WgVHrnv263JtDuvs2k", // not a link
    "baz": {"/": "/ipfs/QmUmg7BZC1YP1ca66rRtWKxpXp77WgVHrnv263JtDuvs2k"} // link
  }
}

对象在files/cat.jpg/link的实际链接以及files/cat.jpg中的伪“链接对象”。

{
   “ files ”: {
      “ cat.jpg ”: { //将链接属性封装在另一个对象中
      “ link ”: { “ / ”: “ / ipfs / QmUmg7BZC1YP1ca66rRtWKxpXp77WgVHrnv263JtDuvs2k ” },//链接
      “ mode ”: 0755,
      “owner”: “ jbenet ”
    }
  }
}

当取消链接时,映射本身将被它指向的对象替换,除非链接路径无效。

该链接可以是multihash,在这种情况下,假设它是/ipfs层次结构中的链接,或者直接指向对象的绝对路径。目前,只允许使用/ipfs层次结构。

如果应用程序想要将具有单个/key的对象用于其他目的,则应用程序本身应负责转义/IPLD对象中的/key,这样应用程序的key就不会与IPLD的特殊/key发生冲突。

什么是merkle-graph或merkle-dag?

带有merkl-links的对象形成一个Graph(merkle-graph),如果加密散列函数的属性保持不变,则这些对象必然都是定向的,并且可以认为它是非循环的,即merkle-dag。因此,所有使用merkle-linking(merkle-graph)的图必定也是有向无环图(DAG,因此为merkle-dag)。

什么是merkle路径?

merkl-path是一种unix风格的路径(例如,/a/b/c/d),它最初通过merkl-link进行引用,并允许访问被引用节点和其他节点的元素。

我们鼓励通用文件系统在IPLD上设计一个对象模型,该模型将专门用于文件操作,并有特定的路径算法来查询该模型。

merkle-paths如何工作?

merkl-path是一种unix风格的路径,最初通过merkl-link进行引用,然后在中间对象中命名merkl-links。名称后面的意思是查找对象,查找名称并解析相关的merkl-link。

例如,假设我们有这个merkle-path:

/ipfs/QmUmg7BZC1YP1ca66rRtWKxpXp77WgVHrnv263JtDuvs2k/a/b/c/d

路径表述:

  • ipfs 是一个协议命名空间(允许计算机识别要做什么)
  • QmUmg7BZC1YP1ca66rRtWKxpXp77WgVHrnv263JtDuvs2k 是一个加密哈希。
  • a/b/c/d是一个路径遍历,就像在unix中一样。

路径遍历,用符号表示/,发生在两种链接上:

  • 对象内遍历遍历同一对象内的数据。
  • 跨对象遍历从一个对象遍历到另一个对象,通过merkle-link解析。
例子

使用以下数据集:

> ipfs object cat --fmt=yaml QmUmg7BZC1YP1ca66rRtWKxpXp77WgVHrnv263JtDuvs2k
---
a:
  b:
    link:
      /: QmV76pUdAAukxEHt9Wp2xwyTpiCmzJCvjnMxyQBreaUeKT
    c: "d"
    foo:
      /: QmQmkZPNPoRkPd7wj2xUJe5v5DsY6MX33MFaGhZKB2pRSE

> ipfs object cat --fmt=yaml QmV76pUdAAukxEHt9Wp2xwyTpiCmzJCvjnMxyQBreaUeKT
---
c: "e"
d:
  e: "f"
foo:
  name: "second foo"

> ipfs object cat --fmt=yaml QmQmkZPNPoRkPd7wj2xUJe5v5DsY6MX33MFaGhZKB2pRSE
---
name: "third foo"

路径的一个例子:

  • /ipfs/QmUmg7BZC1YP1ca66rRtWKxpXp77WgVHrnv263JtDuvs2k/a/b/c将遍历第一个对象并获取字符串d
  • /ipfs/QmUmg7BZC1YP1ca66rRtWKxpXp77WgVHrnv263JtDuvs2k/a/b/link/c将遍历两个对象并获取字符串e
  • /ipfs/QmUmg7BZC1YP1ca66rRtWKxpXp77WgVHrnv263JtDuvs2k/a/b/link/d/e遍历两个对象并获取字符串f
  • /ipfs/QmUmg7BZC1YP1ca66rRtWKxpXp77WgVHrnv263JtDuvs2k/a/b/link/foo/name遍历第一个和第二个对象并获取字符串second foo
  • /ipfs/QmUmg7BZC1YP1ca66rRtWKxpXp77WgVHrnv263JtDuvs2k/a/b/foo/name遍历第一个和最后一个对象并获取字符串third foo

什么是IPLD数据模型?

IPLD数据模型为所有merkle-dag定义了一个简单的基于JSON的结构,并标识了一组格式来将结构编码进去。

限制和欲望

一些限制:

  • IPLD路径必须是明确的。给定的路径字符串必须总是确定性地遍历到同一个对象。(例如避免重复链接名称)
  • IPLD路径必须是通用的,避免对非英语国家不友好(例如使用UTF-8,而不是ASCII)。
  • IPLD路径必须在UNIX和Web上干净地分层(使用/,对ASCII系统具有确定性的变换)。
  • 考虑到JSON的广泛成功,大量的系统提供了JSON接口。IPLD必须能够对JSON进行简单的导入和导出。
  • JSON数据模型也非常简单易用。IPLD必须一样易于使用。
  • 定义新数据结构必须非常简单。要在IPLD上尝试新的定义,不应该很麻烦,也不需要太多的知识。
  • 由于IPLD基于JSON数据模型,因此它完全可以通过JSON-LD与RDF和关联数据标准兼容。
  • IPLD序列化格式(在磁盘上和在线上)必须快速且节省空间。(不应该使用JSON作为存储格式,而应使用CBOR或类似的格式)
  • IPLD密码哈希必须是可升级的(使用multihash

一些不错的点:

  • IPLD不应该犯下错误,例如JSON中缺少整数。
  • IPLD应该是可升级的,例如,如果出现更好的磁盘格式,系统应该能够迁移到它之上并且最小化这样做的成本。
  • IPLD对象应该能够解析路径的属性,而不仅仅是merkle links。
  • IPLD Canonical Format应该易于编写解析器。
  • IPLD Canonical Format应该能在不解析完整对象的情况下进行查找。(CBOR和Protobuf允许)。

格式定义

(注意:这里我们将使用JSON和YML来显示格式是什么样的。我们显式地使用这两种方法来显示不同格式的对象的等价性。)

IPLD数据模型“是JSON”,它(a)也是基于树的文档,具有一些基本类型,(b)1:1映射到JSON, (c)用户可以通过JSON本身使用它。它“不是JSON”(a)在某些错误上有所改进,(b)具有高效的序列化表示,(c)实际上并没有指定单一的on-wire格式,因为众所周知,这个世界正在改进。

基本的节点

以下是JSON中的示例IPLD对象:

{
   “ name ”:“ Vannevar Bush ” 
}

假设它散列的multihash值QmAAA...AAA。注意,它根本没有链接,只是一个字符串名称值。但是我们仍然能够“解析”它下面的key name:

> ipld cat --json QmAAA...AAA
{
  "name": "Vannevar Bush"
}

> ipld cat --json QmAAA...AAA/name
"Vannevar Bush"

当然,我们可以用其他格式来查看它

> ipld cat --yml QmAAA...AAA
---
name: Vannevar Bush

> ipld cat --xml QmAAA...AAA
<!xml> <!-- todo -->
<node>
  <name>Vannevar Bush</name>
</node>
链接节点之间

节点之间的Merkle-Linking是IPLD存在的原因。IPLD中的链接只是一种特殊格式的嵌入式节点:

{
  "title": "As We May Think",
  "author": {
    "/": "QmAAA...AAA" // links to the node above.
  }
}

假设这个散列值为multihash值QmBBB...BBB。该节点通过子路径author链接到QmAAA...AAA,上面这一节的节点。所以我们现在可以做到:

> ipld cat --json QmBBB...BBB
{
  "title": "As We May Think",
  "author": {
    "/": "QmAAA...AAA" // links to the node above.
  }
}

> ipld cat --json QmBBB...BBB/author
{
  "name": "Vannevar Bush"
}

> ipld cat --yml QmBBB...BBB/author
---
name: "Vannevar Bush"

> ipld cat --json QmBBB...BBB/author/name
"Vannevar Bush"
链接属性约定

IPLD允许用户构建复杂的数据结构,以及与链接相关的其他属性。这对编码其他信息以及链接(例如关系类型或链接中所需的辅助数据)很有用。这与下面讨论的“链接对象约定”不同,它们本身非常有用。但有时候,你只是想在链接上添加一些数据而不必创建另一个对象。IPLD不会妨碍你。您可以简单地通过将实际的IPLD链接嵌套在另一个对象中,并使用其他属性来完成。

重要提示:链接属​​性不允许直接在链接对象中使用,因为存在明显的歧义。阅读规格历史,了解有关难点的讨论。

例如,假设您有一个文件系统,并希望在对象之间的链接中分配类似于权限或所有者的元数据。假设你有一个哈希值为QmCCC...CCC的目录对象像这样:

{
  "foo": { // link wrapper with more properties
    "link": {"/": "QmCCC...111"} // the link
    "mode": "0755",
    "owner": "jbenet"
  },
  "cat.jpg": {
    "link": {"/": "QmCCC...222"},
    "mode": "0644",
    "owner": "jbenet"
  },
  "doge.jpg": {
    "link": {"/": "QmCCC...333"},
    "mode": "0644",
    "owner": "jbenet"
  }
}

或YML

---
foo:
  link:
    /: QmCCC...111
  mode: 0755
  owner: jbenet
cat.jpg:
  link:
    /: QmCCC...222
  mode: 0644
  owner: jbenet
doge.jpg:
  link:
    /: QmCCC...333
  mode: 0644
  owner: jbenet

虽然我们在特定于此数据结构的链接中拥有新属性,但我们仍然可以很好地解析链接:

> ipld cat --json QmCCC...CCC/cat.jpg
{
  "data": "\u0008\u0002\u0012��\u0008����\u0000\u0010JFIF\u0000\u0001\u0001\u0001\u0000H\u0000H..."
}

> ipld cat --json QmCCC...CCC/doge.jpg
{
  "subfiles": [
    {
      "/": "QmPHPs1P3JaWi53q5qqiNauPhiTqa3S1mbszcVPHKGNWRh"
    },
    {
      "/": "QmPCuqUTNb21VDqtp5b8VsNzKEMtUsZCCVsEUBrjhERRSR"
    },
    {
      "/": "QmS7zrNSHEt5GpcaKrwdbnv1nckBreUxWnLaV4qivjaNr3"
    }
  ]
}

> ipld cat --yml QmCCC...CCC/doge.jpg
---
subfiles:
  - /: QmPHPs1P3JaWi53q5qqiNauPhiTqa3S1mbszcVPHKGNWRh
  - /: QmPCuqUTNb21VDqtp5b8VsNzKEMtUsZCCVsEUBrjhERRSR
  - /: QmS7zrNSHEt5GpcaKrwdbnv1nckBreUxWnLaV4qivjaNr3

> ipld cat --json QmCCC...CCC/doge.jpg/subfiles/1/
{
  "data": "\u0008\u0002\u0012��\u0008����\u0000\u0010JFIF\u0000\u0001\u0001\u0001\u0000H\u0000H..."
}

但是我们无法像其他属性那样很好地提取链接,因为链接是要通过解析的。

重复属性keys

注意,有两个同名的属性是不允许的,但实际上不可能阻止(有人会这样做并将它提供给解析器),所以为了安全起见,我们定义了路径遍历的值作为序列化表示中的第一个条目。例如,假设我们有对象:

{
  "name": "J.C.R. Licklider",
  "name": "Hans Moravec"
}

假设这是规范格式(不是json,而是cbor)的准确顺序,并且它的散列为QmDDD…DDD。我们总是得到:

> ipld cat --json QmDDD...DDD
{
  "name": "J.C.R. Licklider",
  "name": "Hans Moravec"
}
> ipld cat --json QmDDD...DDD/name
"J.C.R. Licklider"
路径限制

Unix和Web中的路径描述有一些重要的问题。有关讨论,请参阅此讨论。为了与unix和web的模型和期望兼容,IPLD明确禁止具有特定路径组件的路径。请注意,数据本身可能仍然包含这些属性(有人会这样做,并且有合法用途)。所以只有路径解析器不能通过这些路径来解析。这些限制与典型的unix和UTF-8路径系统相同:

todo:

  • [ ] 列表路径解析限制
  • [ ] show示例
JSON中的整型

IPLD可以直接与JSON兼容,以利用JSON的成功,但它不需要因为JSON的错误而受到限制。这是我们可以遵循格式惯用选择的地方,但必须注意确保始终存在定义明确的1:1映射。

关于整数,在JSON中存在多种表示整数为字符串的格式,例如EJSON。这些可以被使用和转换到其他格式,这是自然发生的- 也就是说,将JSON转换为CBOR时,应该将EJSON整数自然地转换为合适的CBOR整数,而不是将其表示为字符串值的映射。

序列化数据格式

IPLD通过multicodec支持各种序列化数据格式。这些可以使用,但是对于格式是惯用的,例如CBOR,我们可以使用CBOR类型tags来表示merkl-link,并避免写出完整的字符串键@link。鼓励用户充分使用这些格式,并以最有意义的任何格式存储和传输IPLD数据。唯一的要求是必须有一个明确定义的IPLD规范格式的一对一映射。这样就可以将数据从一种格式转换为另一种格式,而不必改变其含义或密码哈希值。

带标签的序列化CBOR

在CBOR中,可以使用定义在RFC 7049 section 2.4.中的标记来表示IPLD链接。

标签<tag-link-object>被定义。这个标签可以是一个文本字符串(主类型3)或者是对应于链接目标的字节字符串(主类型2)。

将IPLD“链接对象”编码到CBOR时,请使用以下算法:

  • 提取链接值。
  • 如果链接值是一个有效的multiaddress,并且将该链接文本转换为多地址二进制字符串,并返回到文本将保证产生完全相同的文本,则该链接将转换为存储在CBOR中的二进制多地址字节字符串(主类型2)。
  • 否则,链接值被存储为文本(主类型3)
  • 由此产生的编码是链接值<tag-link-object>的CBOR表示

当解码CBOR并将其转换为IPLD时,<tag-link-object>的每一个事件都由以下算法转换:

  • 以下值必须是提取的链接值。
  • 如果链接是二进制字符串,则将其解释为多地址并转换为文本格式。否则,直接使用文本字符串。
  • 用一个键值对创建一个映射。关键是标准的IPLD链接key/,该值是包含链接值的文本字符串。

当一个IPLD对象以这里所述的方式包含这些标记时,用于表示对象编解码器的multicodec头必须是/cbor/ IPLD -tagsv1,而不仅仅是/cbor。读者应该能够使用优化的阅读过程来检测使用这些标签的链接。

规范格式

为了保持merkle-linking的能力,我们必须确保IPLD文档有一个单一的规范序列化表示。这可确保应用程序获得相同的加密哈希。应该指出的是,这是一个系统范围的参数。未来的系统可能会改变它来演进表示。不过,我们估计这需要十年不超过一次。

IPLD规范格式是带tags的规范化CBOR。

除了此处定义的规则外,规范CBOR格式必须遵循RFC 7049 section 3.9中定义的规则。

这种格式的用户不应该期望keys的任何特定排序,因为keys可能以不同的非标准格式排序。

传统的规范格式是protocol buffers。

这种规范格式用于决定首次创建对象并计算其哈希时使用的格式。一旦为IPLD对象决定了格式,它必须在所有通信中使用,以便发送者和接收者可以根据散列来检查数据。

例如,当发送一个在protocol buffers中编码的传统对象时,发送方不得发送CBOR版本,因为接收方将无法检查文件的有效性。

同样,当接收者存储对象时,它必须确保该对象的规范格式与对象一起存储,以便能够与其他节点共享该对象。

用它们的格式存储这些对象的一种简单方法是用它们的multicodec头存储它们。

数据结构示例

重要的是,IPLD是一种简单,灵活和可扩展的格式,不会妨碍用户定义新的或导入旧数据文件的方式。为此,下面我将展示一些示例数据结构。

Unix文件系统

一个小文件

{
   “ data ”: “ hello world ”,
   “ size ”: “ 11 ” 
}

一个分块文件
分割成多个独立的子文件。

{
  "size": "1424119",
  "subfiles": [
    {
      "link": {"/": "QmAAA..."},
      "size": "100324"
    },
    {
      "link": {"/": "QmAA1..."},
      "size": "120345",
      "repeat": "10"
    },
    {
      "link": {"/": "QmAA1..."},
      "size": "120345"
    },
  ]
}

目录

{
   “ foo ”: {
     “ link ”: { “ / ”: “ QmCCC ... 111 ” },
     “ mode ”: “ 0755 ”,
     “ owner ”: “ jbenet ”
  },
  “ cat.jpg ”: {
     “ link ”: { “ / ”: “ QmCCC ... 222 ” },
     “ mode ”: “ 0644 ”,
     “ owner ”: “ jbenet ”
  },
  “ doge.jpg ”: {
     “ link ”: { “ / ”: “ QmCCC ... 333 ” },
     “ mode ”: “ 0644 ”,
     “ owner ”: “ jbenet ”
  }
}

git

git blob

{
   “ data ”: “ hello world ” 
}

git tree

{
   “ foo ”: {
     “ link ”: { “ / ”: “ QmCCC ... 111 ” },
     “ mode ”: “ 0755 ”
  },
  “ cat.jpg ”: {
     “ link ”: { “ / ”: “ QmCCC ... 222 ” },
     “ mode ”: “ 0644 ”
  },
  “ doge.jpg ”: {
     “ link ”: { “ / ”: “ QmCCC ... 333 ” },
     “ mode ”: “ 0644 ”
  }
}

git commit

{
  "tree": {"/": "e4647147e940e2fab134e7f3d8a40c2022cb36f3"},
  "parents": [
    {"/": "b7d3ead1d80086940409206f5bd1a7a858ab6c95"},
    {"/": "ba8fbf7bc07818fa2892bd1a302081214b452afb"}
  ],
  "author": {
    "name": "Juan Batiz-Benet",
    "email": "juan@benet.ai",
    "time": "1435398707 -0700"
  },
  "committer": {
    "name": "Juan Batiz-Benet",
    "email": "juan@benet.ai",
    "time": "1435398707 -0700"
  },
  "message": "Merge pull request #7 from ipfs/iprs\n\n(WIP) records + merkledag specs"
}

比特币

比特币block

{
   “ parent ”: { “ / ”: “ Qm000000002CPGAzmfdYPghgrFtYFB6pf1BqMvqfiPDam8 ” },
   “ transactions ”: { “ / ”: “ QmTgzctfxxE8ZwBNGn744rL5R826EtZWzKvv2TF2dAcd9n ” },
   “ nonce ”: “ UJPTFZnR2CPGAzmfdYPghgrFtYFB6pf1BqMvqfiPDam8 ” 
}

比特币交易

这一次,在YML中。TODO:让它是一个真正的txn

---
inputs:
  - input: {/: Qmes5e1x9YEku2Y4kDgT6pjf91TPGsE2nJAaAKgwnUqR82}
    amount: 100
outputs:
  - output: {/: Qmes5e1x9YEku2Y4kDgT6pjf91TPGsE2nJAaAKgwnUqR82}
    amount: 50
  - output: {/: QmbcfRVZqMNVRcarRN3JjEJCHhQBcUeqzZfa3zoWMaSrTW}
    amount: 30
  - output: {/: QmV9PkR2gXcmUgNH7s7zMg9dsk7Hy7bLS18S9SHK96m7zV}
    amount: 15
  - output: {/: QmP8r8fLUnEywGnRRUrHB28nnBKwmshMLiYeg8udzYg7TK}
    amount: 5
script: OP_VERIFY
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念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

推荐阅读更多精彩内容