Nervos CKB 是一条基于 PoW 的 Layer 1 公链,其 Cell 模型是比特币 UTXO 模型的泛化,因此它的智能合约开发有别于基于以太坊账户模型的智能合约开发。在本文中,Nervso CKB 核心开发者 Dylan 为大家详细介绍了如何在 CKB 上开发智能合约,欢迎更多的开发者们来 CKB 上体验开发的乐趣。

概 要

以太坊的合约是链上计算,合约调用者需要给出合约方法的输入,链上会完成计算并得到输出,CKB 的合约是链上验证,合约调用者需要同时给出输入和输出,链上完成输入到输出的验证。

举一个简单的类比,如果你想实现在合约中实现 y = sqrt(x) 函数,对于以太坊你需要给出 x 的值,合约会计算 y 的值;而对于 CKB 来说,你需要同时给出 x 和 y 的值,合约负责验证 x 与 y 是否满足 y = sqrt(x)。

从这个例子中可以看到,以太坊合约开发只需要关注输入和需要调用的合约函数即可,链上会完成计算和状态的更新,而 CKB 则需要在链外提前计算输入和输出,合约只需要按照相同的计算规则来验证输入和输出是否满足要求,换言之,CKB 需要同时实现链外的 Generator 和链上的 Validator,这两者的验证规则是一致的。

对于熟悉以太坊智能合约的开发者来说,CKB 的智能合约相当于是一种全新的开发模型,所有的状态改变都需要链外的 Generator 提前设定好,链上要做的只是验证状态改变是否符合规则。相比于以太坊的只需要在链上实现合约规则,CKB 需要在链外和链上同时实现两套相同的规则,这在一定程度上增加了合约开发的复杂度,不过好处是合约运行的复杂度可以大大降低,因为验证通常要比计算更简单。

还是上文提到的例子,如果你想在合约中实现 y = sqrt(x) 函数,以太坊需要在合约中实现根据输入 x 做开平方运算得到 y,而 CKB 的合约其实只需要判断 x 和 y 是否满足 x = y^2,显然平方的计算复杂度要远小于开平方的复杂度。换言之,CKB 的合约算法可以不需要跟链外 Generator 完全保持一致,只要两者的计算是等价的即可。

Cell 和 Transaction 的数据结构

因为 CKB 的合约本质上是通过交易来改变 Cell 的状态,因此我们强烈建议先熟悉 Cell 和 Transaction 的数据结构,否则会影响后续合约的理解,详情可以参考 Transaction Structure:

  • https://github.com/nervosnetwork/rfcs/blob/master/rfcs/0022-transaction-structure/0022-transaction-structure.md
// Cell
{capacity: uint64,lock: Script,type: Script,
}// Script
{code_hash: H256,args: Bytes,hash_type: String    // type or data
}

inputs、outputs 和 outputs_data 代表了 Cell 在一笔交易前后的状态变化,Cell 包含了 lock script(必需)和 type script(非必需),CKB VM 会执行 inputs 中的所有的 lock scripts,以及 inputs 和 outputs 中的所有 type scripts,lock script和 type script 包含了对 Cell 状态约束的合约规则。

关于 Script 中 code_hash、args 以及 hash_type 可以参考Code Locating,请务必先阅读,否则会影响后续合约的理解:

  • https://github.com/nervosnetwork/rfcs/blob/master/rfcs/0022-transaction-structure/0022-transaction-structure.md#code-locating

VM Syscall

由于我们需要在合约中判断 Cell 在一笔交易中前后的状态变化是否符合一定的规则,那么首先我们就需要在合约中可以获取到 Cell 和 Transaction 中的数据,CKB VM 提供了 syscall 帮助我们在合约中访问 Cell 和 Transaction 中的数据:

  • ckb_load_tx_hash
  • ckb_load_transaction
  • ckb_load_script_hash
  • ckb_load_script
  • ckb_load_cell
  • ckb_load_cell_by_field
  • ckb_load_cell_data
  • ckb_load_cell_data_as_code
  • ckb_load_input
  • ckb_load_input_by_field
  • ckb_load_header
  • ckb_load_header_by_field
  • ckb_load_witness

可以看到 VM Syscall 提供了大量获取 Cell 和 Transaction 数据的方法,这些方法可以在 C 语言代码中直接调用,具体的参数和调用细节可以参考VM Syscall:

  • https://github.com/nervosnetwork/rfcs/blob/master/rfcs/0009-vm-syscalls/0009-vm-syscalls.md
#include "ckb_syscalls.h"// We are limiting the script size loaded to be 32KB at most.
// This should be more than enough.
// We are also using blake2b with 256-bit hash here,
// which is the same as CKB.
#define BLAKE2B_BLOCK_SIZE 32
#define SCRIPT_SIZE 32768#define ERROR_SYSCALL -3
#define ERROR_SCRIPT_TOO_LONG -21int main() {// First, let's load current running script,// so we can extract owner lock script hash from script args.unsigned char script[SCRIPT_SIZE];uint64_t len = SCRIPT_SIZE;int ret = ckb_load_script(script, &len, 0);if (ret != CKB_SUCCESS) {return ERROR_SYSCALL;}if (len > SCRIPT_SIZE) {return ERROR_SCRIPT_TOO_LONG;}return CKB_SUCCESS;
}

上面的合约例子展示了如何读取当前 script 数据,以及判断 script 数据是否符合长度要求,CKB 的系统合约都是 C 语言实现的,详情可以参考:

  • ckb-system-scripts:
    https://github.com/nervosnetwork/ckb-system-scripts/tree/master/c
  • ckb-miscellaneous-scripts:
    https://github.com/nervosnetwork/ckb-miscellaneous-scripts/tree/master/c

Capsule

为了降低合约开发、调试、测试和部署的门槛,Nervos CKB 推出了基于 Rust 语言的智能合约开发框架 Capsule,旨在提供开箱即用的解决方案,以帮助开发者快速而轻松地完成常见的开发任务:

  • https://github.com/nervosnetwork/capsule
USAGE:
capsule [SUBCOMMAND]FLAGS:-h, --help       Prints help information-V, --version    Prints version informationSUBCOMMANDS:check           Check environment and dependenciesnew             Create a new projectnew-contract    Create a new contractbuild           Build contractsrun             Run command in contract build imagetest            Run testsdeploy          Deploy contracts, edit deployment.toml to custodian deployment recipe.debugger        CKB debuggerhelp            Prints this message or the help of the given subcommand(s)

通过 Capsule 命令行可以完成智能合约的创建、编译、测试、调试和部署,关于 Capsule 的详细使用说明可以参考 Write a SUDT script by Capsule:

  • https://docs.nervos.org/docs/labs/sudtbycapsule

为了能让 Rust 开发者在 Capsule 框架中调用 VM Syscall 方法,Nervos CKB 提供了 ckb-std 以及相关使用文档,开发者可以在合约中引入 ckb-std,从而使用 high_level 模块下的方法完成对 CKB Cell 和 Transaction 数据的调用。

// Module ckb-std::high_level
find_cell_by_data_hash
load_cell
load_cell_capacity
load_cell_data
load_cell_data_hash
load_cell_lock
load_cell_lock_hash
load_cell_occupied_capacity
load_cell_type
load_cell_type_hash
load_header
load_header_epoch_length
load_header_epoch_number
load_header_epoch_start_block_number
load_input
load_input_out_point
load_input_since
load_script
load_script_hash
load_transaction
load_tx_hash
load_witness_args

以下是一些常见的使用 high_level 方法的例子:

// Call current script and check script args length
let script = load_script()?;
let args: Bytes = script.args().unpack();
if args.len() != 20 {return Err(Error::InvalidArgument);
}// Call the input of index 0
let cell_input = load_cell(0, Source::Input)?// Call the output of index 0
let cell_output = load_cell(0, Source::Output)?// Filter inputs whose lock script hash is equal to the given
// lock hash and calculate the sum of inputs' capacity
let cell_inputs = QueryIter::new(load_cell, Source::Input).position(|cell| &hash::blake2b(cell.lock().as_slice())== lock_hash)let inputs_sum_capacity = cell_inputs.into_iter().fold(0, |sum, c| sum + c.capacity().unpack())// Check if there is an output with lock script hash equal to
// the given lock hash
let has_output = QueryIter::new(load_cell, Source::Output).any(|cell| &hash::blake2b(cell.lock().as_slice())== lock_hash)// Check whether the witness args' lock is none of witness
// whose index in witnesses is 0
match load_witness_args(0, Source::Input) {Ok(witness_args) => {if witness_args.lock().to_opt().is_none() {Err(Error::WitnessSignatureWrong)} else {Ok(())}},Err(_) => Err(Error::WitnessSignatureWrong)
}

如果需要在合约中验证签名,ckb-dynamic-loading-secp256k1 给出了如何通过 Rust 代码调用系统 Secp256k1 的 C 代码,ckb-dynamic-loading-rsa 给出了如何通过 Rust 代码调用 RSA 签名算法的 C 代码。

更多关于 Capsule 开发智能合约的例子可以参考以下项目:

  • my-sudt
    https://github.com/jjyr/my-sudt
  • ckb-cheque-script
    https://github.com/duanyytop/ckb-cheque-script
  • ckb-passport-lock
    https://github.com/duanyytop/ckb-passport-lock

调 试

在合约开发过程中遇到不符合预期的错误是很常见的,比较常见的调试方式是在合约中打印日志,ckb-std 提供了 debug! ,其用法类似于 Rust 语言中的 print! ,而在合约的 tests 中,可以直接使用 print! 和 println! 来打印。

测 试

对于 CKB 智能合约来说,Capsule 可以帮助开发者实现合约的本地测试,而无需部署到 Nervos CKB 开发链或者测试链,可以极大地降低合约调试难度,提升合约测试效率。关于如何在 Capsule 中实现合约的测试用例,可以参考 Write a SUDT script by Capsule # Test:

  • https://docs.nervos.org/docs/labs/sudtbycapsule#testing

部 署

对于 CKB 智能合约来说,除了常规的二进制代码直接部署,用二进制代码的 hash 作为 code hash 的方式,还有 Type ID 部署方式,code hash 取自 type script hash。

TYPE ID 以及 dep_group 等部署方式可以在 deployment.toml 文件中配置,最终的部署可以参考 Write a SUDT script by Capsule # Deployment:

  • https://docs.nervos.org/docs/labs/sudtbycapsule#deployment

常见错误

合约在开发过程中,难免会遇到各种各样的错误,如何快速定位问题并修复就显得很重要了,如果你的合约中用到了 CKB 的系统合约,例如secp256k1_blake160_sighash_all、secp256k1_blake160_multisig_all或者 Nervos DAO,那么你可以参考系统合约错误码以及相应的错误解释来快速定位问题。

比较常见的错误有:

  • 1:数组越界,检查是否访问了超过数组长度的索引
  • 2:缺少某项数据,例如某个 Cell 需要有 type script,但是在拼装交易的时候漏掉了
  • -1:参数长度错误,有可能是 script args 或者 signature 长度不对
  • -2:编码异常,检查 Cell 和 Transaction 的数据是否符合 molecule 要求,比如多了或者少了 0x,hex string 长度为奇数等等
  • -101 ~ -103:Secp256k1 验签失败,检查合约和 Transaction Witnesses 以及 Script 参数是否正确
  • InvalidCodeHash: Script Code Hash 无效,检查 code hash 是否正确,以及 cell deps 是否包含了该 code hash 对应的 cell dep
  • ExceededMaximumCycles: 合约消耗的 Cycles 数量已经超过了最大上限
  • CapacityOverflow: Capacity 溢出,请检查 outputs 的 capacity 总和是否大于 inputs 的 capacity 总和
  • InsufficientCellCapacity: Cell 数据实际占用的字节数大于当前 Cell 的 capacity(capacity 代表 Cell 能承载的数据的字节数)
  • Immature: 由于 input since 不为零,当前 input 还不能被消费

当然还有很多系统合约错误,上面只是列举了比较常见的错误类型,详情可以参考:

  • Error Codes:
    https://github.com/nervosnetwork/ckb-system-scripts/wiki/Error-codes
  • Verification Error
    https://github.com/nervosnetwork/ckb/blob/develop/verification/src/error.rs
  • Script Error:
    https://github.com/nervosnetwork/ckb/blob/develop/script/src/error.rs

除了系统合约的错误码,对于特定的业务合约也会有自己的错误码,这个时候就需要去看定义在业务合约中的错误码,定位可能出错的地方,例如 ckb-cheque-script error code:

  • https://github.com/duanyytop/ckb-cheque-script/blob/main/contracts/ckb-cheque-script/src/error.rs

对于合约可能出错的地方都应该抛出相应的错误码,这也不仅有利于合约本身的调试,也可以帮助链外 Generator 在拼装交易的时候更容易定位问题。

// 如果你喜欢 Nervos 并且喜欢开发
// 你可以关注并私信我哦~
if (you like Nervos && you like dev) {println("you can follow me and private letter for me~");
}

教你如何极简上手 Nervos CKB 上的智能合约开发相关推荐

  1. 如何在 Nervos CKB 上开发智能合约

    概述 Nervos CKB 是一条基于 PoW 的 layer1 公链,其 Cell 模型是比特币 UTXO 模型的泛化,因此它的智能合约开发有别于基于以太坊账户模型的智能合约开发.以太坊的合约是链上 ...

  2. 【在 Nervos CKB 上做开发】Nervos CKB 脚本编程简介[1]:验证模型

    CKB 脚本编程简介[1]: 验证模型 本文作者:Xuejie原文链接:Introduction to CKB Script Programming 1: Validation Model 本文译者: ...

  3. WTF Solidity极简入门: 39链上随机数

    本文是按照以下教程编写的,在BNB测试网上运行失败,发现了ChainLink帮助文档中的参数错误,修正后才运行成功.TESTNET Binance (BNB) Blockchain Explorer ...

  4. c++ eos智能合约开发_十分钟教你开发EOS智能合约

    EOS环境搭建和启动节点 下面从EOS入门的环境搭建.编译运行一个智能合约.发送一些Aigsen,给大家做一些展示,希望能让非技术人员也有一些收获. 首先下载EOS环境搭建和启动节点.这一步其实还是比 ...

  5. Nervos CKB 共识协议 NC-Max:突破 Nakamoto Consensus 吞吐量的极限

    带宽实际上是区块链吞吐量的最大限制,在美国旧金山举办的 Scaling Bitcoin Meetup 中,Nervos & Cryptape 研究员张韧从「带宽利用率」角度分析了诸多共识协议的 ...

  6. mac屏保时钟_OneClock 不息屏的极简时钟,不仅仅是翻页时钟那么简单!

    OneClock Mac时钟软件包括翻页时钟.数字时钟.表盘时钟,更多表盘三种样式.支持黑色和白色主题切换.透明度设置等设计. 不仅仅适配Mac 在iOS设备上,限于苹果并未开放任何屏保时钟.桌面时钟 ...

  7. 极简主义APP界面UI设计实例模板,不简单!

    UI设计是对用户软件使用环境的设计.由于APPUI设计必须遵从易用性的特点,尤其是手机界面尺寸有限.因此,简洁大方的交互界面设计才能更容易的吸引用户.引导用户. RentHouse - 简单主页搜索移 ...

  8. 为什么要追求“极简”代码?

    本文将分享如何将极简主义概念应用到代码中,使其更简洁.更高效. 作者 | Paula Santamaria 译者 | 谭开朗,责编 | 郭芮 出品 | CSDN(ID:CSDNnews) 以下为译文: ...

  9. OneClock 不息屏的极简时钟,不仅仅是翻页时钟那么简单!

    OneClock Mac时钟软件包括翻页时钟.数字时钟.表盘时钟,更多表盘三种样式.支持黑色和白色主题切换.透明度设置等设计. 不仅仅适配Mac 在iOS设备上,限于苹果并未开放任何屏保时钟.桌面时钟 ...

最新文章

  1. R语言删除ggplot可视化图中的所有x轴轴标签实战:ggplot可视化默认包含所有x轴轴标签、删除ggplot可视化图中的所有x轴轴标签实战
  2. 【 MATLAB 】ndgrid 和 meshgrid 对比理解以及应用
  3. 关于网页显示乱码问题的一些个人见解(PHP、JSP...)
  4. 程序员如何用Python了解女朋友的情绪变化?
  5. Transaction rolled back because it has been marked as rollback-only
  6. 关于session丢失原因的分析
  7. 数据科学与大数据排名思考题_排名前5位的数据科学课程
  8. java.sql.SQLException: ORA-00604: 递归 SQL 级别 1 出现错误
  9. Android手机开发总结
  10. 夏普ar2048s打印机驱动安装_无光盘,轻松解决各种打印机的驱动安装
  11. 软件测试初学者如何编写Jmeter测试脚本?
  12. 宏电GPRS DTU H7210调试报告之DEMO测试
  13. 输入起止坐标,返回途径网格。
  14. 排序算法——侏儒排序(Gnome sort)【代码实现】
  15. 去掉选中div后出现的高亮淡蓝色边框
  16. 第一次vscode 推送已有代码 到gitee新仓库
  17. 开学季:20本Python经典书单
  18. 抖音多账号零粉直播工具源码实现无人直播
  19. 家里wifi密码忘了怎么办?
  20. 协同过滤算法概述与python 实现协同过滤算法基于内容(usr-item,item-item)

热门文章

  1. 初识Java中的并发
  2. string和long转换
  3. 深度学习调参trick 调参技巧
  4. 关于“秒杀”系统的技术分析
  5. 将两个嵌套for循环写成一个列表生成式
  6. 逻辑面试题:3顶黑帽子,2顶白帽子,我戴的是黑帽子吗?
  7. 高斯肤色概率模型matlab,基于肤色模型与模板匹配的人脸检测研究
  8. 卡巴斯基 windows 10 远程桌面
  9. JAVA17版本_我的世界JAVA版1.17版本内容预览
  10. C语言数据结构,边界标识法