LOADING...
LOADING...
LOADING...
当前位置: 玩币族首页 > 币圈百科 > CKB 上租赁管理的实现思路

CKB 上租赁管理的实现思路

2020-01-17 NervosNetwork 来源:火星财经

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

CKB 主网上线快两个月了,就目前看,链上开发还存在一定的难度,有很多概念和定义并没有出现在文档里面,更多的是代码层面的定义。CKB 开发者DriftLuo的这篇文章主要目的是以实现租赁管理为手段来介绍 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

玩币族申明:玩币族作为开放的资讯翻译/分享平台,所提供的所有资讯仅代表作者个人观点,与玩币族平台立场无关,且不构成任何投资理财建议。文章版权归原作者所有。

LOADING...
LOADING...