近日,DAS 创始人 TimYang (杨敏)在 Nervos CKB 上开发了 DAS
去中心化帐户服务。借着此次的产品开发,TimYang 将经过《从 DAS 开始了解 CKB
应用开发》系列文章,向你们阐述他的设计思路和开发历程,让你们了解如何在世界上第一个基于 UTXO 架构的公链 CKB 上构建产品级应用。数据库在第一篇文章中,Tim 将向你们介绍他们在设计 DAS 时面临的第一个大问题 ——如何保证 DAS 帐户的惟一性。欢迎阅读及体验。架构
DAS(Decentralized Account Services),是基于 CKB 构建的去中心化帐户服务。DAS 项目自己,旨在为新世界提供一套兼具抗审查性、惟一性、可识别性的帐户体系。在 DAS 的第一阶段,它看起来像是以太坊的 ENS,而且具有一些比 ENS 更为优秀的特性。但 DAS 要作的,不只仅是更好的 ENS,而是试图为加密世界的「去中心化帐户/身份」这块拼图,带来新的定义。并发
DAS 不是一个概念性产品,它目前已经运行在 CKB 测试网上,并预计于近期上线主网。能够经过 https://da.services 体验测试版本。学习
DAS 是基于 CKB 开发的区块链应用。在诸多公链中,为何咱们要选择基于 CKB 来进行开发呢?缘由有二:区块链
CKB 是少有的在 UTXO 模型之上构建智能合约环境,并主张「链下计算,链上验证」的公链平台。这些主张和设计通过了充分的考量,很是具备前瞻性,但同时也带来了全新的去中心化应用开发范式。习惯了中心化应用开发和以太坊智能合约开发的开发者,在刚开始接触 CKB 开发时,会充满不适应。加之目前尚没有标杆应用出现,这让开发者们对于 CKB 到底能作什么,是否是真的值得花精力去学习 CKB,充满疑问。测试
《从 DAS 开始了解 CKB 应用开发》系列文章的目的,也正在于此。咱们将咱们在 DAS 实践过程当中的问题、思考,以及解决方案整理成一系列文章,让你们了解咱们是如何基于 CKB 构建产品级应用的。但愿借此给更多开发者带来启发,了解 CKB 能作什么,应该怎么作。ui
须要说明的是:加密
在面对一个问题时,咱们采用的思路和解决方案,不必定是最优解,甚至大几率不是。但这些知足咱们场景的思路和解决方案,若能给你们带来启发,目的便已达到。
这一系列的文章,都假定读者已经充分理解 Cell 模型和「链下计算,链上验证」模型。
咱们将在第一篇文章中,探讨 DAS 面临的第一个棘手问题:spa
每一个 DAS 帐户都须要一个 Cell 来存储其数据,Cell 是经过不一样交易来建立的,这意味着 DAS
系统的全局状态数据是分散存储在各个角落的。同时每一个 DAS 帐户又必须具备惟一性。那么,当一个 DAS
帐户注册行为发生时,咱们如何判断该帐户是否已经存在呢?
咱们把这个问题通常化:对于分散存储的数据集,在插入数据时,如何保证每条数据的惟一性?设计
对于习惯了中心化应用开发和以太坊智能合约开发的开发者而言,要保证注册的帐户不重复,这一件几乎不用思考的事情,你能够把全部的数据都放入合约的存储空间,因为这些数据是集中存储的,因此在插入数据以前,你只须要先检索一下数据是否存在便可。
但鉴于 CKB 的 Cell 模型,数据分散存储在用户本身的空间中,咱们没法在链上去检索全部数据。毕竟咱们不可能在一笔交易的输入中,放下全部已经存在的 Cell。即使能放下,链上脚本也没法知晓这笔交易在构造时,交易发起人是否真的将全部须要 Cell 都放到了输入中。
咱们将列举全部咱们曾考虑过的保证惟一性的方案。之因此把最终没采用的方案都拿出来分析,是但愿你们能够经过观察咱们走过的「弯路」,开始适应 CKB 的开发范式,避免之后本身走「弯路」。
在讨论方案以前,咱们应先明确咱们的设计原则。正是这些原则,最终决定了咱们采用什么样的方案。这些原则,优先级从高到低依次为:
若是你只关心最终方案,能够直接跳转到「方案六」开始阅读。
这是最符合直觉的一种方案,毕竟以太坊的智能合约就能够这么干。建立一个 GlobalStatusCell,在 GlobalStatusCell 的 data 中存放全部已注册的帐户。当新的注册发生时,在交易中把这个 GlobalStatusCell 做为输入,修改后的GlobalStatusCell 做为输出。type 脚本检查新注册的帐户是否已经存在,若是存在就返回非 0,交易失败;若是不存在,那就检查输出的 GlobalStatusCell 中是否包含了新帐户,而后返回 0,交易成功,注册完成。
这种思路不可行的缘由在于:
事实上咱们会发现,「Cell 竞争问题」是在 CKB 上开发应用时,要时刻警戒的问题。它对用户体验的影响多是致命的。
既然一个 GlobalStatusCell 放全部帐户会致使竞争,那咱们把帐户分散到多个帐户呢?好比,对帐户名作 hash,将全部 hash 值前 3 位相同的已注册帐户放到同一个 SubStatusCell 里。当一个新的注册产生时,必须将对应的 SubStatusCell 消费,以修改其内部数据。
这个方案仍存在一些问题:
依然存在必定的 Cell 竞争,若是按 hash 前3位来建立 SubStatusCell,须要提早建立 4096 个 SubStatusCell,假定在一个周期内有 50 个并发的注册请求,按照「抽屉原理」,仍有 26% 的几率出现 Cell 竞争。尽管 50 的并发请求稍显苛刻,在早期可能根本达不到,但应该认识到:
再次强调:在 CKB 上开发应用时,应该时刻关注你的应用会占用多少 CKB 存储空间,由于总的状态空间是极其有限的。
全部的注册都要经过 DAS 官方的服务进行,DAS 官方断定可注册后,用官方私钥签名一笔交易,向用户发放 DAS 帐户 Cell。这个方案在实现上很是简单,但问题也很明显:
好比,找 7 个「能够信任」的组织做为超级节点,管理各自的私钥。超级节点们运行超级节点服务程序,将全部已注册的帐户存储在本身的中心化数据库中,当一个注册请求产生时(指用户构造一个包含注册信息的 Cell),各个超级节点将判断其是否已经注册过。若是未注册过,那就用私钥签名一笔交易,释放一个代表「本超级节点认为这个帐户能够注册」的 Cell,当有 4 个以上的超级节点都释放了这样的 Cell 时,其中一个节点就会汇聚全部的这些 Cell,做为依据去建立 DAS 帐户。
这种思路,看似能够很好的解决方案三中的一些问题,但却引入了更多的问题:
既然要实现注册时去重这么复杂,那干脆注册时就不去重了。任何人在任什么时候候均可以「注册」任何帐户,而后在用户要查询一个帐户的解析记录时,由解析程序去找出那个最先「注册」的帐户,将其做为合法的帐户返回给用户。
这种独特的思路,存在的问题主要是如何保证客户端运行「合理」的解析程序:
若是不能保证你们始终运行相同的最新的解析程序,整个系统势必会在应用层面上不一致。由此会引起各类形式的欺诈,最终你们会对这个系统失去信心。
最后,咱们来介绍 DAS 最终所采用的方案 —— 有序链表。
咱们将咱们要解决的问题,作更通常化的表述:
对于分散存储的数据集,在插入数据时,如何保证每条数据的惟一性?
答案是,使用逻辑上的有序链表。感谢 @guiqing 的启发。
每一个已注册的 DAS 帐户,都有一个 Cell 用来存储其相关的信息,称为 AccountCell。咱们要求全部的 AccountCell 按某种顺序排序,好比按帐户名作字典序升序。当要注册一个新的 DAS 帐户时,其 AccountCell 必须插入到合适的位置,以保证不破坏这种顺序。
AccountCell 的简化结构以下:
注意:account_id 取值为帐户名,仅仅是为了表述方便,实际上 DAS 使用的是其帐户名 hash 的前 10 位。
咱们假定链上已经有 a.bit,b.bit,如今一个用户要注册 d.bit,注册前链表结构以下:
注册后的链表结构以下:
随后,有一个用户要注册 c.bit,那么注册后的链表结构以下:
从上面咱们能够看到,当须要注册一个新帐户时,须要对链表中处于其前方的AccountCell 的 next_account_id 字段进行修改。这也意味着,须要构造一笔交易,能消费掉其前方的 Cell,并建立相对应的新 Cell。对于应该修改哪一个 Cell,也即新的 DAS 帐户应该插入在链表的哪一个位置,这些都是由用户使用的注册程序根据链上的状态,自动帮用户完成的(看,链下计算)。
那若是注册程序不当心(或者用户恶意的)构造一笔交易,试图建立重复帐户,或者将帐户插入错误的位置,会怎么样呢。这时候咱们的 type 脚本就起做用了,会致使这类交易失败不被打包进区块(看,链上验证)。
Cell 的 type 脚本会在 Cell 做为输入和输出时都运行。咱们的 type 脚本就能够作一些判断,好比:
因此上述的这些判断结果若是都为真,且整个交易结构也知足其余一些必要的条件,那么 type 脚本就会返回 0 ,意味着这是一笔合法的交易,当这笔交易归入区块以后,帐户也就注册完整了,DAS 系统的状态完成了更新。而对于不知足这些条件的交易,根本就是不合法的交易,也就不会注册成功。
能够看到,这个方案知足咱们前面设定的 4 个设计原则。
判断个数据重复性而已,在 CKB 上就要这么复杂吗?
咱们要理解,之因此「复杂」,其背后的本质缘由是 UTXO 模型,是 UTXO 模型致使了数据的分散存储。
那为何 CKB 要采用 UTXO 模型,ETH 的帐户模型不就很好吗?
UTXO 模型和帐户模型各有优劣,UTXO 模型的部分优点在于:
咱们更应理解,感觉上的「复杂」,更多的来自于咱们对新范式的不适应。
应把链上验证看做一种协议
能够看到,type 脚本的约束,更像是一种协议。他规定了一笔交易应该有什么样的输入和输出,但谁来建立交易,以什么方式建立交易,并非协议所关心的问题。
方案六也有 Cell 竞争问题呀?
是的,若是多个新的注册的帐户都应该直接插入到某个 AccountCell 的后面,那就会面临 Cell 竞争的问题。因此,咱们将在下一篇文章中介绍,如何经过一种咱们称做 「Keeper」的机制,在方案六的基础上,完全解决 Cell 竞争问题。
最后,如咱们在开头提到的那样:
在面对一个问题时,咱们采用的思路和解决方案,不必定是最优解,甚至大几率不是。但这些知足咱们场景的思路和解决方案,若能给你们带来启发,目的便已达到。
未完待续……
在下一篇文章中,Tim 将为咱们介绍一种称为「Keeper」的机制,来处理 Cell 竞争问题。欢迎你们前往 https://talk.nervos.org/t/das-ckb-das/5669 催更。
如若您有更多关于 DAS 产品的使用心得,以及在 CKB 上开发的看法,欢迎前往 Nervos Talk 论坛讨论:
https://talk.nervos.org/