CKB 上租赁管理的实现思路 | 火星技术帖

NervosNetwork·热度: 21518
CKB 开发者 Drift Luo 以实现租赁管理为手段来介绍 CKB 链上编程,更多的是思路的讲解,希望对开发者有一定的启发。

以下文章来源于漂流的江湖 ,作者漂流

CKB 主网上线快两个月了,就目前看,链上开发还存在一定的难度,有很多概念和定义并没有出现在文档里面,更多的是代码层面的定义。CKB 开发者 Drift Luo 的这篇文章主要目的是以实现租赁管理为手段来介绍 CKB 链上编程,涉及实际代码的地方会很少,更多的是思路的讲解,希望对开发者有一定的启发。

从 ckb verify 讲起

ckb 的 Transaction 是由一堆 cell 构成的,大致上可以分为引用的 cell 和拥有所有权的 cell,而每一个 Transaction 的作用归结到最后只是所有权的变更,它可能是所有权转移也可能只是更改 cell 自身的状态(考虑交易费的存在,这类交易已经不可能存在了),也就是说,每一笔交易必然存在所有权的转移,哪怕存在方式是隐式的交易费。

ckb 的编程说到底还是链上状态的变更,普通转账是简单的所有权变更,Dao 是 Cell 状态的变更,将来的 UDT 或者其他合约也是如此。无论什么样的变幻,都需要一个验证机制去保证交易产生的状态变更符合发起人的预期,而这样的 Transaction 验证(verify)就是整个链上编程的核心,也是最后的保护。开发者首先需要了解 verify 是怎么工作的,之后才能更好地写出匹配验证的逻辑和业务。

    table RawTransaction {    version:        Uint32,    cell_deps:      CellDepVec,   // 依赖的 cell    header_deps:    Byte32Vec,  // 依赖的 header    inputs:         CellInputVec,  // 需要销毁的 cell 列表    outputs:        CellOutputVec,  // 生成的 cell 列表    outputs_data:   BytesVec,  // 生成 cell 列表中每个 cell 对应的 data 数据}
    table Transaction { raw: RawTransaction, witnesses: BytesVec, // 见证信息}

    上方是 Transaction 的字段信息,大部分已经标注了字段的含义,接下来,大致讲解一下 ckb 中对 cell 的状态变化是怎么验证的。

    我们都知道每一个 cell 都可以同时有 lockscript 和可选的 typescript 两种 script 作为验证脚本,而一个 cell 被消耗时,都需要跑一遍它的 lockscript 和可能存在的 typescript,每一个 cell 被创建时,都需要跑一遍它的 typescript:

      cell consume:
      if run(cell.lock_script) == 0 && run(cell.type_script) == 0: return success
      cell create:
      if run(cell.type_script) == 0: return success

      上面这段伪代码就是 cell 验证的核心了,而之后又因为工程实现和便利问题有许多扩展:

      • cell_deps 是脚本执行的上下文中需要的必要环境,诸如加密库依赖、hash 库依赖
      • header_dep 获取链上时间的手段
      • dep_group 依赖库过于庞大,需要拆分 cell 进行存储,并在运行时统一加载
      • type_id 保留依赖库能更新但不 break 生态的手段(可查看第 0 个块的第二个 output 的 type script)
      • scriptgroup 将参数和脚本相同的 script 合并为同一组执行,减少 cycle 消耗

      简单讲一下 header_dep,这是一个妥协的结果,在区块链上,真实时间是无法获取的:

      • 执行环境是禁止访问宿主机的虚拟环境
      • block 记录的 time 是不准确的,它的验证是一个阈值范围
      • 新 block 上记录的 timestamp、epoch 都不可信,无法自证

      于是需要用链上历史证明时间。

      脚本执行可以看出是一个函数的执行过程,它需要入参,而入参来源可以分为两个,一是临时参数,即一次性使用,二是从链上获取的参数,可以把 Transaction 的 witness 认为是临时参数,而链上任意 cell 中 data 的数据(vm 提供了很多 syscall 接口用来获取链上数据)认为是链上参数。

      基于上述概念,我们需要知道的基础逻辑是:

      • 任何 Transaction 本质是 cell 状态的变更
      • cell 的变幻可以用 script 进行验证

      租赁

      这个业务在短期内是不会有市场的,当它正式登录 ckb 的时候,应用市场应该已经非常繁荣了。

      我们说的租赁是什么概念呢?

      首先,任何 cell 都是有所有权的,它可能是以任意方式存在于 lockscript 中,只有符合需求的参数输入才能解锁和使用对应的 cell,例如默认实现的单签和多签脚本。

      其次,cell 中的 data 字段是可以存储任意数据的,包括各种需要的算法或者库实现,并且可以在任意交易验证逻辑中加载使用。

      最后,当自身拥有的 ckb 不足以承载想要部署的数据时,可以有两种办法解决,第一是买,第二是租赁,但是租赁并没有默认提供的实现。

      接下来,就来谈谈实现它的思路

      畅想使用方式

      在考虑如何实现之前,需要想清楚用户应该如何使用它,只有使用起来简单才有可能真正用起来。那么对于租赁的使用,我大致想了以下几个方面的需求:

      • 租赁方无需与出租方沟通即可自行完成租赁过程
      • 租赁方可自行调整租赁时间
      • 出租方可自行将拥有的 ckb 转换为待出租的状态
      • 出租方可自定义租赁 epoch 单价、租赁 epoch 上限等
      • 租赁期间内,租赁方可进行 cell 状态变更(Option)
      • 租赁期间内,出租方无权强行收回 cell 空间

      实现思路

      整个实现思路是用组合的方式完成一个一个需求,而实际上最后的实现可以是多种多样的,复杂程度也并不相同,仅供参考。

      租赁方无需与出租方沟通即可自行完成租赁过程是一个什么概念呢,这意味着任何人都可以使用该 cell ,也就是说,放弃 lockscript 的销毁验证,转为 typescript 生成验证,typescript 验证的逻辑是,一,确认当前 cell 的状态,二,确认生成 cell 的 type 和 lock 符合预期。而任何人都可以租赁的前提是,首先部署一个 cell 作为 always success lock ,这样,待租赁的 cell 大概和下面差不多:

        cell {    capacity: 待租赁大小    lock: always success lock    type:租赁合约    data:{        max_term_of_lease(unit: epoch): u32,        unit_price(ckb/epoch): u64        lock_args: 出租方 lock args        code_hash:    }}

        任何人只要将其拥有的 ckb 转成上诉格式,即代表进入待租赁状态,而此时,可能需要一个第三方服务去展示链上目前可租赁的 cell 的信息,这大概是唯一需要链外做的事情了。

        下面方案中,验证租赁时间是否大于规定最大时间等基本验证被忽略了,留下的是关键的所有权转移的验证方案。

        第一种方案

        lockscript 用默认实现的带 since 约束的 lock_args,只需要写一个通用的 typescript,同时 typescript 的逻辑也相对来说比较简单,这种方案租赁过程是一次性的,租赁人无法在租赁期间内更改租赁 cell 的状态:

          // 待租赁 cell 初始状态if input_cell.lock == always_success_lock_hash:    // cell 所有人退出租赁模式    if output_cell.lock_args == input_cell.data.lock_args && output_cell.lock.code_hash == input_cell.data.lock.code_hash && output_cell.capacity == input_cell.capacity:        return success    // 租赁人付费用并使用    elif output_cell.lock_args == input_cell.data.lock_args + time_since && output_cell.lock.code_hash == input_cell.data.code_hash && output_cell.capacity >= input_cell.capacity + witness.term_of_lease_epoch_num * input_cell.data.unit_price:        return successelif:    // 租赁中 或 转入租赁状态    return success

          第二种方案

          在第一种方案的基础上,需要实现租赁期间内的租赁人可变更状态的同时,限制出租人只有在租赁结束后才可以进行状态变更。这里复杂的地方是 type 和 lock 都需要做一定的修改。

          lock 实现 or 模式:

            if lock_arg == 租赁人 or lock_arg + since == 出租人 + since:    return success

            type 实现会复杂一点:

              // 待租赁 cell 初始状态if input_cell.lock == always_success_lock_hash:    // cell 所有人退出租赁模式    if output_cell.lock_args == input_cell.data.lock_args && output_cell.lock_hash == input_cell.data.lock_hash && output_cell.capacity == input_cell.capacity:        return success    // 租赁人付费用并使用    elif output_cell.lock_args == any_lock_args_by_lessee + input_cell.data.lock_args + time_since && output_cell.lock.code_hash == or_code_hash  && output_cell.capacity >= input_cell.capacity + witness.term_of_lease_epoch_num * input_cell.data.unit_price && output_cell.data.lock_hash == input_cell.data.lock_hash:        return successelif  input_cell.lock.code_hash == or_code_hash:    // 租赁中租赁人变更 cell 状态    if output_cell.lock == input_cell.lock && output_cell.lock_hash == input_cell.lock_hash && output_cell.capacity >= input_cell.capacity && output_cell_type == input_cell_type && output_cell.data.lock_hash == input_cell.data.lock_hash:        return success    // 出租人取回到期的 cell    elif output_cell.lock_arg == input_cell.data.lock_arg && output_cell.lock.code_hash == input_cell.data.code_hash:        return successelif:    // 转入租赁状态    return success

              注意到,因为本方案 租赁人在租赁期间内可以无限度地更改 cell,我们需要对租赁状态下 cell 的 data 字段做一个约定,这个约定将保证任何变化都不会影响到 cell 所有权的判断:

                output_cell.lock_arg == input_cell.data.lock_arg && output_cell.lock.code_hash == input_cell.data.code_hash

                即 cell 的 data 必须保留所有权的 lock_hash,并且一直跟随到租赁结束,如果不在 cell 中保留所有者的 lock 信息,那多次转移后,所有权将难以在一个 Transaction 中进行追溯,可能需要类似 Dao 合约的 header_dep 的形式进行追踪,耗费的 cycle 与直接保存数据存在巨大差异。

                小结

                以上两个方案可以看出很明显的思路倾向:实际上 typescript 里就是写一个状态机,确认当前状态,下一步可到达的状态,然后验证 input 和 output 是否符合状态机允许的状态转移流程,这与写编译器前端的词法语法解析类似。当然,我不排除有其他的合约思路。

                更多的可能性

                上述的思路只是众多方案中的一小部分,更多的可能方案也许会比上面的思路更加出色,本文的重点并不是实现一个租赁合约,而是通过租赁的可能实现思路来理解 ckb 上 cell 编程的使用方法,期待更多更好的合约和产品的出现。


                关于作者:Drift Luo

                漂流的江湖,CKB 开发者,不愿做路人甲的 Rust 宣传委员。

                • 2014 年毕业于财经类院校会计系

                • 2016 年正式接触编程,从 VBA 到 Python

                • 2017 年 4 月左右开始学习 Rust,期间接触了 C/C++/Go 等编程语言

                声明:本文为入驻“火星号”作者作品,不代表火星财经官方立场。
                转载请联系网页底部:内容合作栏目,邮件进行授权。授权后转载时请注明出处、作者和本文链接。 未经许可擅自转载本站文章,将追究相关法律责任,侵权必究。
                提示:投资有风险,入市须谨慎,本资讯不作为投资理财建议。
                免责声明:作为区块链信息平台,本站所提供的资讯信息不代表任何投资暗示,本站所发布文章仅代表个人观点,与火星财经官方立场无关。鉴于中国尚未出台数字资产相关政策及法规,请中国大陆用户谨慎进行数字货币投资。
                语音技术由科大讯飞提供
                最近更新
                本文来源:NervosNetwork
                原文标题:
                24H热门新闻
                暂无内容

                评论0