用Nim语言开发CKB合约

前言

众所周知,Nervos 底层公链 CKB 的虚拟机(CKB-VM)是基于 RISC-V 指令集打造的区块链虚拟机,
其中一个特性就是"任何可以编译成 RISC-V 指令集的语言均可以直接用来为 CKB 开发智能合约",

理论上只要支持CKB VM里的syscall就可以作为合约的开发语言,又看到 https://hookrace.net/blog/nim-binary-size/ 里提到如何精简nim编译出来的二进制文件与systemcall,
因此,我就在想,是不是可以用nim编译出可以在CKB VM上运行的程序呢?
想到就去做。

准备工作

# Debug工具
docker pull muxueqz/ckb-standalone-debugger-plus:latest
# Nim工具链
docker pull muxueqz/ckb-nim-builder:latest
# nim ckb syscall
git clone https://github.com/muxueqz/ckb-nim-builder.git --recurse-submodules

编写测试合约

用Nim编写合约的代码如下:

import ckb_syscall


proc main: clong {.exportc: "_start".} =
   ckb_debug("hello world")
   ckb_exit 0

这份代码在ckb-nim-builder已经有了:example.nim

接下来我们把它编译成链上可运行的二进制:

cd ckb-nim-builder
docker run -w /app -v $PWD/:/app -it --rm muxueqz/ckb-nim-builder bash -x /app/build.sh example.nim

用学姐的ckb-standalone-debugger测试编译出来的二进制能不能运行

docker run -w /data/ -v $PWD:/data  -it --rm muxueqz/ckb-standalone-debugger-plus:latest /data/example 0x10000000

如果得到的结果如下,那就说明可以正常运行了

Executing: /opt/debugger/ckb-debugger --tx-file tx.json --script-group-type type -i 0 -e input
DEBUG:<unknown>: script group: Byte32(0xe858b26d459d418fa5f78a8dd6c639cb7714942816caacb9749830e5203f31ac) DEBUG OUTPUT: hello world
Run result: Ok(0)
Total cycles consumed: 1091
Transfer cycles: 56, running cycles: 1035

参考

7 Likes

import ckb_syscall

这个系统调用哪来的?你前面的代码做了一个 bridge 吗?

对,在这里:https://github.com/muxueqz/ckb-nim-builder/blob/master/ckb_syscall.nim

能做个稍微详细的介绍么,比如用 nim 写的典型代码的大小,和跟 c 原生的比较。nim 可以提供的灵活性介绍等等。

好,目前默认情况下
以文中等价的C代码为例:

#include "ckb_syscalls.h"

int main() {
  const char * value = "hello world";
  int ret = ckb_debug(value);

  ckb_exit(0);
  return 0;
}

编译C代码

riscv64-unknown-elf-gcc -I ckb-c-stdlib/ -Os hello_native_main.c -o hello_native

# nostdlib
riscv64-unknown-elf-gcc -nostdlib -I ckb-c-stdlib/libc/ -I ckb-c-stdlib/ -Os hello_native_main.c -o hello_native_nostdlib

C编译出来的binary是11KB(使用nostdlib后是4KB),Nim编译出来的binary是1.4KB,之所以Nim比C编译出来的小可能是因为我在Nim中使用了nostdlib,如果调整编译参数、开启stdlib则是39KB。

而C运行的cycles情况如下:

# docker run -w /data/ -v $PWD:/data  -it --rm ckb-standalone-debugger-plus /data/hello_native 0x10000000
Executing: /opt/debugger/ckb-debugger --tx-file tx.json --script-group-type type -i 0 -e input
DEBUG:<unknown>: script group: Byte32(0x1223ed4fca82fc4e86880dfacd5a573952243eb110d8855dda04da95e9e6cdba) DEBUG OUTPUT: hello world
Run result: Ok(0)
Total cycles consumed: 2830
Transfer cycles: 1106, running cycles: 1724

# docker run -w /data/ -v $PWD:/data  -it --rm ckb-standalone-debugger-plus /data/hello_native_nostdlib 0x10000000
Executing: /opt/debugger/ckb-debugger --tx-file tx.json --script-group-type type -i 0 -e input
DEBUG:<unknown>: script group: Byte32(0xb17c59affe5f17367856b4727d691491058cc1b82c304c36ed6b978bb7b2b419) DEBUG OUTPUT: hello world
Run result: Ok(0)
Total cycles consumed: 1587
Transfer cycles: 510, running cycles: 1077

对比上文Nim的1091,Nim和C的性能非常接近。

PS: 关于Nim的nostdlib,有些用法需要调整,比如一些字符串拼接,因为涉及内存管理,而开启之后大概会到2万左右,比起javascript动辄百万千万级的cycles已经划算很多了,而且我试了下,做些微调就可以把开启stdlib才能运行的代码改成nostdlib也可以跑的,之后也许可以用工具去自动做这个调整

2 Likes

从哪里看出来,性能非常接近的?!

C消耗的cycles是1587,Nim消耗的cycles是1091,比起js消耗的百万千万级的cycles,Nim和C的性能不是很接近吗?