從 DAS 開始瞭解 CKB 應用開發(一)—— 如何保證 DAS 帳戶的惟一性

近日,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 能作什麼,應該怎麼作。ui

須要說明的是:加密

在面對一個問題時,咱們採用的思路和解決方案,不必定是最優解,甚至大機率不是。但這些知足咱們場景的思路和解決方案,若能給你們帶來啓發,目的便已達到。
這一系列的文章,都假定讀者已經充分理解 Cell 模型和「鏈下計算,鏈上驗證」模型。

如何保證 DAS 帳戶的惟一性

咱們將在第一篇文章中,探討 DAS 面臨的第一個棘手問題:spa

每一個 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/

在這裏插入圖片描述

相關文章
相關標籤/搜索