共计 6034 个字符,预计需要花费 16 分钟才能阅读完成。
近日,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 来进行开发呢?起因有二:
- PoW 共识 + Cell(UTXO)模型
- 自定义密码学原语(高度凋谢的架构),基于此咱们可能实现 DAS 账户能够被任意公链地址所持有。
CKB 是少有的在 UTXO 模型之上构建智能合约环境,并主张「链下计算,链上验证」的公链平台。这些主张和设计通过了充沛的考量,十分具备前瞻性,但同时也带来了全新的去中心化利用开发范式。习惯了中心化利用开发和以太坊智能合约开发的开发者,在刚开始接触 CKB 开发时,会充斥不适应。加之目前尚没有标杆利用呈现,这让开发者们对于 CKB 到底能做什么,是不是真的值得花精力去学习 CKB,充斥疑难。
《从 DAS 开始理解 CKB 利用开发》系列文章的目标,也正在于此。咱们将咱们在 DAS 实际过程中的问题、思考,以及解决方案整顿成一系列文章,让大家理解咱们是如何基于 CKB 构建产品级利用的。心愿借此给更多开发者带来启发,理解 CKB 能做什么,应该怎么做。
须要阐明的是:
在面对一个问题时,咱们采纳的思路和解决方案,不肯定是最优解,甚至大概率不是。但这些满足咱们场景的思路和解决方案,若能给大家带来启发,目标便已达到。
这一系列的文章,都假设读者曾经充沛了解 Cell 模型和「链下计算,链上验证」模型。
如何保障 DAS 账户的唯一性
咱们将在第一篇文章中,探讨 DAS 面临的第一个辣手问题:
每个 DAS 账户都须要一个 Cell 来存储其数据,Cell 是通过不同交易来创立的,这意味着 DAS
零碎的全局状态数据是扩散存储在各个角落的。同时每个 DAS 账户又必须具备唯一性。那么,当一个 DAS
账户注册行为产生时,咱们如何判断该账户是否曾经存在呢?
咱们把这个问题一般化:对于扩散存储的数据集,在插入数据时,如何保障每条数据的唯一性?
对于习惯了中心化利用开发和以太坊智能合约开发的开发者而言,要保障注册的账户不反复,这一件简直不必思考的事件,你能够把所有的数据都放入合约的存储空间,因为这些数据是集中存储的,所以在插入数据之前,你只须要先检索一下数据是否存在即可。
但鉴于 CKB 的 Cell 模型,数据扩散存储在用户本人的空间中,咱们无奈在链下来检索所有数据。毕竟咱们不可能在一笔交易的输出中,放下所有曾经存在的 Cell。即使能放下,链上脚本也无奈通晓这笔交易在结构时,交易发起人是否真的将所有须要 Cell 都放到了输出中。
咱们将列举所有咱们曾思考过的保障唯一性的计划。之所以把最终没采纳的计划都拿进去剖析,是心愿大家能够通过观察咱们走过的「弯路」,开始适应 CKB 的开发范式,防止当前本人走「弯路」。
在探讨计划之前,咱们应先明确咱们的设计准则。正是这些准则,最终决定了咱们采纳什么样的计划。这些准则,优先级从高到低顺次为:
- 去中心化水平,对于 DAS 要达成的指标而言,去中心化是最基础性的准则
- 用户体验,技术计划不容许带来蹩脚的用户体验
- 工程复杂度,越简略的架构往往越无效
- 费用成本低,能节俭的费用尽量节俭
如果你只关怀最终计划,能够间接跳转到「计划六」开始浏览。
计划一:把所有账户存储在一个 Cell 里
这是最合乎直觉的一种计划,毕竟以太坊的智能合约就能够这么干。创立一个 GlobalStatusCell,在 GlobalStatusCell 的 data 中寄存所有已注册的账户。当新的注册产生时,在交易中把这个 GlobalStatusCell 作为输出,批改后的 GlobalStatusCell 作为输入。type 脚本查看新注册的账户是否曾经存在,如果存在就返回非 0,交易失败;如果不存在,那就查看输入的 GlobalStatusCell 中是否蕴含了新账户,而后返回 0,交易胜利,注册实现。
这种思路不可行的起因在于:
- Cell 竞争问题,每个新账户的注册都须要把这个 GlobalStatusCell 作为输出破费掉,而一个 Live Cell 只能破费一次,那意味着同一时刻,永远只能解决一个注册申请。竞争 Cell 失败的用户,不得不一遍又一遍的签订交易,直到胜利的竞争到 Cell。
- 空间老本问题,CKB 是分层架构,Layer 1 上最终的状态空间限度为大概 80 GB,在下面存储数据须要应用 CKB 购买存储空间。假如最终有 100w 个 DAS 账户被注册,那这个 GlobalStatusCell 须要的 capacity 将会微小无比。当然,因为这个存储空间是随着注册量逐步减少的,对于单个用户而言,只需为本次注册所对应的增量空间领取 CKB,单个用户的老本还算能够承受。
事实上咱们会发现,「Cell 竞争问题」是在 CKB 上开发利用时,要时刻警觉的问题。它对用户体验的影响可能是致命的。
计划二:那就把所有账户扩散到多个 Cell 里
既然一个 GlobalStatusCell 放所有账户会导致竞争,那咱们把账户扩散到多个账户呢?比方,对账户名做 hash,将所有 hash 值前 3 位雷同的已注册账户放到同一个 SubStatusCell 里。当一个新的注册产生时,必须将对应的 SubStatusCell 生产,以批改其外部数据。
这个计划仍存在一些问题:
仍然存在肯定的 Cell 竞争,如果按 hash 前 3 位来创立 SubStatusCell,须要提前创立 4096 个 SubStatusCell,假设在一个周期内有 50 个并发的注册申请,依照「抽屉原理」,仍有 26% 的概率呈现 Cell 竞争。只管 50 的并发申请稍显刻薄,在晚期可能基本达不到,但应该意识到:
- 因为 SubStatusCell 数量固定,这种竞争的概率,无论在哪个阶段都是一样的「概率」自身意味着不确定性,它的用户体验的影响可能没有,也可能十分大。
- 初始化时存在费用老本,假设一个 SubStatusCell 初始时只须要 100 CKB 作为其 capacity,那初始化所有的 SubStatusCell 就须要 409,600 个 CKB。
再次强调:在 CKB 上开发利用时,应该时刻关注你的利用会占用多少 CKB 存储空间,因为总的状态空间是极其无限的。
计划三:由 DAS 官网来判断一个账户是否曾经注册过
所有的注册都要通过 DAS 官网的服务进行,DAS 官网断定可注册后,用官网私钥签名一笔交易,向用户发放 DAS 账户 Cell。这个计划在实现上非常简单,但问题也很显著:
- 不去中心化,如何确保 DAS 官网服务的唯一性判断是正确的。如果官网被动作恶呢?如果官网因为程序故障或者私钥保存不善,导致被动作恶呢?
- 脏数据问题,无论什么模式的作恶,中心化的判断都是一种链下判断,不能相对无效的保障唯一性,因而,随时可能产生链上脏数据。如何清理这些脏数据呢?势必要引入一套脏数据清理的机制。
- 衍生而来的可用性问题,如果官网服务宕机,那整个注册服务就不可用。
计划四:那就多中心化,用多个链下节点一起判断一个账户是否曾经注册过
比方,找 7 个「能够信赖」的组织作为超级节点,治理各自的私钥。超级节点们运行超级节点服务程序,将所有已注册的账户存储在本人的中心化数据库中,当一个注册申请产生时(指用户结构一个蕴含注册信息的 Cell),各个超级节点将判断其是否曾经注册过。如果未注册过,那就用私钥签名一笔交易,开释一个表明「本超级节点认为这个账户能够注册」的 Cell,当有 4 个以上的超级节点都开释了这样的 Cell 时,其中一个节点就会汇聚所有的这些 Cell,作为根据去创立 DAS 账户。
这种思路,看似能够很好的解决方案三中的一些问题,但却引入了更多的问题:
- 信赖问题,「能够信赖」的组织,怎么样算能够信赖的组织,咱们应该如何甄选这 7 个节点。一个组织的道德或者是能够信赖的,并不代表其行为也是能够信赖的。咱们能够找最有公信力的组织来做节点,但在一个我的项目的晚期,最有公信力的组织很难有能源来保护节点。
- 脏数据问题,因为「必然」存在的程序 bug,这些超级节点们齐全可能做出一致性的错误判断。当一致性的谬误呈现时,还得有一套脏数据清理逻辑机制
- 节点轮换问题,因为私钥失落或者其余起因,节点不可避免的要进行轮换,轮换如何进行?通过链下磋商还是链上共识?链下磋商意味着得有一套公开通明的治理流程;链上共识,意味着要有的简单的工程实现。
- 复杂度,这既包含工程的复杂度,也包含治理的复杂度。其中的大量工作都曾经偏离了一个 dApp 自身的业务逻辑。试想,如果每个利用开发者都须要思考这么多与业务逻辑关联度并不高的问题,那利用不可能被高效的发明进去。这也意味着,多核心的计划必然不能是最佳实际。
计划五:注册时不去重,解析时去重
既然要实现注册时去重这么简单,那罗唆注册时就不去重了。任何人在任何时候都能够「注册」任何账户,而后在用户要查问一个账户的解析记录时,由解析程序去找出那个最早「注册」的账户,将其作为非法的账户返回给用户。
这种独特的思路,存在的问题次要是如何保障客户端运行「正当」的解析程序:
- 开发者会都运行对立的解析程序吗?
- 当官网解析程序降级时,那些抉择运行官网解析程序的开发者,他们会不会,以及能不能做到及时降级?
如果不能保障大家始终运行雷同的最新的解析程序,整个零碎势必会在利用层面上不统一。由此会引发各种模式的欺诈,最终大家会对这个零碎失去信念。
计划六:有序链表
最初,咱们来介绍 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 脚本就能够做一些判断,比方:
- inputs 中,引入的父 AccountCell 的 account_id 是否小于新注册的账户的 account_id
- inputs 中,引入的父 AccountCell 的 next_account_id 是否大于新注册的账户的 account_id
- outputs 中,新的父 AccountCell 的 next_account_id 是否等于新注册账户的 account_id
- outputs 中,新注册账户的 next_account_id 是否等于 inputs 中引入的父 AccountCell 的 next_account_id
所以上述的这些判断后果如果都为真,且整个交易构造也满足其余一些必要的条件,那么 type 脚本就会返回 0,意味着这是一笔非法的交易,当这笔交易纳入区块之后,账户也就注册残缺了,DAS 零碎的状态实现了更新。而对于不满足这些条件的交易,根本就是不非法的交易,也就不会注册胜利。
能够看到,这个计划满足咱们后面设定的 4 个设计准则。
进一步衍生
判断个数据重复性而已,在 CKB 上就要这么简单吗?
咱们要了解,之所以「简单」,其背地的实质起因是 UTXO 模型,是 UTXO 模型导致了数据的扩散存储。
那为什么 CKB 要采纳 UTXO 模型,ETH 的账户模型不就很好吗?
UTXO 模型和账户模型各有优劣,UTXO 模型的局部劣势在于:
- 并行计算。ETH 的单个账户下的所有交易都必须串行,一笔交易卡住,前面所有的交易都无奈进行。
- 用户数据 存储在用户本人的 UTXO(Cell)里,而不是集中存储在合约中,不更合乎去中心化精力吗?
咱们更应了解,感触上的「简单」,更多的来自于咱们对新范式的不适应。
应把链上验证看作一种协定
能够看到,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/