原文做者:Xuejie
原文连接:Introduction to CKB Script Programming 2: Script
本文译者:Shooter,Jason,Orange (排名不分前后)html
上一篇咱们介绍了当前 CKB 的验证模型。这一篇会更加有趣一点,咱们要向你们展现如何将脚本代码真正部署到 CKB 网络上去。我但愿在你看完本文后,你能够有能力自行去探索 CKB 的世界并按照你本身的意愿去编写新的脚本代码。git
须要注意的是,尽管我相信目前的 CKB 的编程模型已经相对稳定了,可是开发仍在进行中,所以将来还可能会有一些变化。我将尽力确保本文始终处于最新的状态,可是若是在过程到任何疑惑,本文以 此版本下的 CKB 做为依据。程序员
警告:这是一篇很长的文章,由于我想为下周更有趣的话题提供充足的内容。因此若是你没有充足的时间,你没必要立刻完成它。我在试着把它分红几个独立的不凡,这样你就能够一次尝试一个。
github
在继续以前,咱们先来区分两个术语:脚本(script)和脚本代码(script code)docker
在本文以及整个系列文章内,咱们将区分脚本和脚本代码。脚本代码其实是指你编写和编译并在 CKB 上运行的程序。而脚本,其实是指 CKB 中使用的脚本数据结构,它会比脚本代码稍微多一点点:编程
pub struct Script { pub args: Vec<Bytes>, pub code_hash: H256, pub hash_type: ScriptHashType, }
咱们目前能够先忽略hash_type
,以后的文章再来解释什么是hash_type
以及它有什么有趣的用法。在这篇文章的后面,咱们会说明code_hash
其实是用来标识脚本代码的,因此目前咱们能够只把它当成脚本代码。那脚本还包括什么呢?脚本还包括args
这个部分,它是用来区分脚本和脚本代码的。args
在这里能够用来给一个 CKB 脚本提供额外的参数,好比:虽然你们可能都会使用相同的默认的 lock script code,可是每一个人可能都有本身的 pubkey hash,args
就是用来保存 pubkey hash 的位置。这样,每个CKB 的用户均可以拥有不一样的 lock script ,可是却能够共用一样的 lock script code。json
请注意,在大多数状况下,脚本和脚本代码是能够互换使用的,可是若是你在某些地方感到了困惑,那么你可能有必要考虑一下二者间的区别。
ubuntu
你可能以前就已经听所过了,CKB (编者注:此处指的应该是 CKB VM)是基于开源的 RISC-V 指令集编写的。但这到底意味着什么呢?用我本身的话来讲,这意味着咱们(在某种程度上)在 CKB 中嵌入了一台真正的微型计算机,而不是一台虚拟机。一台真正的计算机的好处是,你能够用任何语言编写任何你想写的逻辑。在这里,咱们展现的前面几个例子将会用 C语言编写,以保持简单性(我是说工具链中的简单性,而不是语言),以后咱们还会切换到基于 JavaScript 的脚本代码,并但愿在本系列中展现更多的语言。记住,在 CKB 上有无限的可能!api
正如咱们提到的,CKB VM 更像是一台真正的微型计算机。CKB 的代码脚本看起来也更像是咱们在电脑上跑的一个常见的 Unix 风格的可执行程序。数组
int main(int argc, char* argv[]) { return 0; }
当你的代码经过 C 编译器编译时,它将成为能够在 CKB 上运行的脚本代码。换句话说,CKB 只是采用了普通的旧式 Unix 风格的可执行程序(但使用的是 RISC-V 体系结构,而不是流行的 x86 体系结构),并在虚拟机环境中运行它。若是程序的返回代码是 0 ,咱们认为脚本成功了,全部非零的返回代码都将被视为失败脚本。
在上面的例子中,咱们展现了一个老是成功的脚本代码。由于返回代码老是 0。可是请不要使用这个做为您的 lock script code ,不然您的 token 可能会被任何人拿走。
可是显然上面的例子并不有趣,这里咱们从一个有趣的想法开始:我我的不是很喜欢胡萝卜。我知道胡萝卜从养分的角度来看是很好的,但我仍是想要避免它的味道。若是如今我想设定一个规则,好比我想让我在 CKB 上的 Cell 里面都没有以carrot
开头的数据?让咱们编写一个脚本代码来实现这一点。
为了确保没有一个 cell 在 cell data
中包含carrot
,咱们首先须要一种方法来读取脚本中的 cell data。CKB 提供了syscalls
来帮助解决这个问题。
为了确保 CKB 脚本的安全性,每一个脚本都必须在与运行 CKB 的主计算机彻底分离的隔离环境中运行。这样它就不能访问它不须要的数据,好比你的私钥或密码。然而,要使得脚本有用,必须有特定的数据要访问,好比脚本保护的 cell 或脚本验证的事务。CKB 提供了syscalls
来确保这一点,syscalls
是在 RISC-V 的标准中定义的,它们提供了访问环境中某些资源的方法。在正常状况下,这里的环境指的是操做系统,可是在 CKB VM 中,环境指的是实际的 CKB 进程。使用syscalls
, CKB脚本能够访问包含自身的整个事务,包括输入(inputs)、输出(outpus)、见证(witnesses)和 deps。
好消息是,咱们已经将syscalls
封装在了一个易于使用的头文件中,很是欢迎您在这里查看这个文件,了解如何实现syscalls
。最重要的是,您能够只获取这个头文件并使用包装函数来建立您想要的系统调用。
如今有了syscalls
,咱们能够从禁止使用carrot
的脚本开始:
#include <memory.h> #include "ckb_syscalls.h" int main(int argc, char* argv[]) { int ret; size_t index = 0; volatile uint64_t len = 0; /* (1) */ unsigned char buffer[6]; while (1) { len = 6; memset(buffer, 0, 6); ret = ckb_load_cell_by_field(buffer, &len, 0, index, CKB_SOURCE_OUTPUT, CKB_CELL_FIELD_DATA); /* (2) */ if (ret == CKB_INDEX_OUT_OF_BOUND) { /* (3) */ break; } if (memcmp(buffer, "carrot", 6) == 0) { return -1; } index++; } return 0; }
如下几点须要解释一下:
len
字段须要标记为volatile
。咱们会同时使用它做为输入和输出参数,CKB VM 只能在它还保存在内存中时,才能够把它设置输出参数。而volatile
能够确保 C 编译器将它保存为基于 RISC-V 内存的变量。syscall
时,咱们须要提供如下功能:一个缓冲区来保存syscall
提供的数据;一个len
字段,来表示系统调用返回的缓冲区长度和可用数据长度;一个输入数据缓冲区中的偏移量,以及几个咱们在交易中须要获取的确切字段的参数。详情请参阅咱们的RFC。CKB_SUCCESS
) 意味着成功,1 (or CKB_INDEX_OUT_OF_BOUND
) 意味着您已经经过一种方式获取了全部的索引,2 (orCKB_ITEM_MISSING
) 意味着不存在一个实体,好比从一个不包含该 type 脚本的 cell 中获取该 type 的脚本。概况一下,这个脚本将循环遍历交易中的全部输出 cells,加载每一个 cell data 的前6个字节,并测试这些字节是否和carrot
匹配。若是找到匹配,脚本将返回-1
,表示错误状态;若是没有找到匹配,脚本将返回0
退出,表示执行成功。
为了执行该循环,该脚本将保存一个index
变量,在每次循环迭代中,它将试图让 syscall 获取 cell 中目前采用的index
值,若是 syscall 返回 CKB_INDEX_OUT_OF_BOUND
,这意味着脚本已经遍历全部的 cell,以后会退出循环;不然,循环将继续,每测试 cell data 一次,index
变量就会递增一次。
这是第一个有用的 CKB 脚本代码!在下一节中,咱们将看到咱们是如何将其部署到 CKB 中并运行它的。
首先,咱们须要编译上面写的关于胡萝卜的源代码。因为 GCC 已经提供了 RISC-V 的支持,您固然可使用官方的 GCC 来建立脚本代码。或者你也可使用咱们准备的 docker 镜像来避免编译 GCC 的麻烦:
$ ls carrot.c ckb_consts.h ckb_syscalls.h $ sudo docker run --rm -it -v `pwd`:/code nervos/ckb-riscv-gnu-toolchain:xenial bash root@dc2c0c209dcd:/# cd /code root@dc2c0c209dcd:/code# riscv64-unknown-elf-gcc -Os carrot.c -o carrot root@dc2c0c209dcd:/code# exit exit $ ls carrot* carrot.c ckb_consts.h ckb_syscalls.h
就是这样,CKB 能够直接使用 GCC 编译的可执行文件做为链上的脚本,无需进一步处理。咱们如今能够在链上部署它了。注意,我将使用 CKB 的 Ruby SDK,由于我曾经是一名 Ruby 程序员,固然 Ruby 对我来讲是最天然的(但不必定是最好的)。如何设置请参考官方 Readme 文件。
要将脚本部署到 CKB,咱们只需建立一个新的 cell,把脚本代码设为 cell data 部分:
pry(main)> data = File.read("carrot") pry(main)> data.bytesize => 6864 pry(main)> carrot_tx_hash = wallet.send_capacity(wallet.address, CKB::Utils.byte_to_shannon(8000), CKB::Utils.bin_to_hex(data))
在这里,我首先要经过向本身发送 token 来建立一个容量足够的新的 cell。如今咱们能够建立包含胡萝卜脚本代码的脚本:
pry(main)> carrot_data_hash = CKB::Blake2b.hexdigest(data) pry(main)> carrot_type_script = CKB::Types::Script.new(code_hash: carrot_data_hash, args: [])
回忆一下脚本数据结构:
pub struct Script { pub args: Vec<Bytes>, pub code_hash: H256, pub hash_type: ScriptHashType, }
咱们能够看到,咱们没有直接将脚本代码嵌入到脚本数据结构中,而是只包含了代码的哈希,这是实际脚本二进制代码的 Blake2b 哈希。因为胡萝卜脚本不使用参数,咱们能够对args
部分使用空数组。
注意,这里仍然忽略了 hash_type
,咱们将在后面的文章中经过另外一种方式讨论指定代码哈希。如今,让咱们尽可能保持简单。
要运行胡萝卜脚本,咱们须要建立一个新的交易,并将胡萝卜 type 脚本设置为其中一个输出 cell 的 type 脚本:
pry(main)> tx = wallet.generate_tx(wallet2.address, CKB::Utils.byte_to_shannon(200)) pry(main)> tx.outputs[0].instance_variable_set(:@type, carrot_type_script.dup)
咱们还须要进行一个步骤:为了让 CKB 能够找到胡萝卜脚本,咱们须要在一笔交易的 deps 中引用包含胡萝卜脚本的 cell:
pry(main)> carrot_out_point = CKB::Types::OutPoint.new(cell: CKB::Types::CellOutPoint.new(tx_hash: carrot_tx_hash, index: 0)) pry(main)> tx.deps.push(carrot_out_point.dup)
如今咱们准备签名并发送交易:
[44] pry(main)> tx.witnesses[0].data.clear [46] pry(main)> tx = tx.sign(wallet.key, api.compute_transaction_hash(tx)) [19] pry(main)> api.send_transaction(tx) => "0xd7b0fea7c1527cde27cc4e7a2e055e494690a384db14cc35cd2e51ec6f078163"
因为该交易的 cell 中没有任何一个的 cell data 包含carrot
,所以 type 脚本将验证成功。如今让咱们尝试一个不一样的交易,它确实含有一个以carrot
开头的 cell:
pry(main)> tx2 = wallet.generate_tx(wallet2.address, CKB::Utils.byte_to_shannon(200)) pry(main)> tx2.deps.push(carrot_out_point.dup) pry(main)> tx2.outputs[0].instance_variable_set(:@type, carrot_type_script.dup) pry(main)> tx2.outputs[0].instance_variable_set(:@data, CKB::Utils.bin_to_hex("carrot123")) pry(main)> tx2.witnesses[0].data.clear pry(main)> tx2 = tx2.sign(wallet.key, api.compute_transaction_hash(tx2)) pry(main)> api.send_transaction(tx2) CKB::RPCError: jsonrpc error: {:code=>-3, :message=>"InvalidTx(ScriptFailure(ValidationFailure(-1)))"} from /home/ubuntu/code/ckb-sdk-ruby/lib/ckb/rpc.rb:164:in `rpc_request'
咱们能够看到,咱们的胡萝卜脚本拒绝了一笔生成的 cell 中包含胡萝卜的交易。如今我可使用这个脚原本确保全部的 cell 中都不含胡萝卜!
因此,总结一下,部署和运行一个 type 脚本的脚本,咱们须要作的是:
code hash
,补齐args
部分中脚本代码的须要的参数这就是你全部须要的!若是您的脚本遇到任何问题,您须要检查这些要点。
虽然在这里咱们只讨论了 type 脚本,可是 lock 脚本的工做方式彻底相同。您唯一须要记住的是,当您使用特定的 lock 脚本建立 cell 时,lock 脚本不会在这里运行,它只在您使用 cell 时运行。所以, type 脚本能够用于构造建立 cell 时运行的逻辑,而 lock 脚本用于构造销毁 cell 时运行的逻辑。考虑到这一点,请确保您的 lock 脚本是正确的,不然您可能会在如下场景中丢失 token:
您的 lock 脚本有一个其余人也能够解锁您的 cell 的 bug。
您的 lock 脚本有一个 bug,任何人(包括您)都没法解锁您的 cell。
在这里咱们能够提供的一个技巧是,始终将您的脚本做为一个 type 脚本附加到你交易的一个 output cell 中去进行测试,这样,发生错误时,您能够当即知道,而且您的 token 能够始终保持安全。
根据已经掌握的知识,让咱们看看 CKB 中包含的默认的 lock 脚本代码。 为了不混淆,咱们正在查看 lock 脚本代码在 这个commit。
默认的 lock 脚本代码将循环遍历与自身具备相同 lock 脚本的全部的 input cell,并执行如下步骤:
注意,咱们在前面讨论了脚本和脚本代码之间的区别。每个不一样的公钥 hash 都会产生不一样的 lock 脚本,所以,若是一个交易的输入 cell 具备相同的默认 lock 脚本代码,但具备不一样的公钥 hash(所以具备不一样的 lock 脚本),将执行默认 lock 脚本代码的多个实例,每一个实例都有一组共享相同 lock 脚本的 cell。
如今咱们能够遍历默认 lock 脚本代码的不一样部分:
if (argc != 2) { return ERROR_WRONG_NUMBER_OF_ARGUMENTS; } secp256k1_context context; if (secp256k1_context_initialize(&context, SECP256K1_CONTEXT_VERIFY) == 0) { return ERROR_SECP_INITIALIZE; } len = BLAKE2B_BLOCK_SIZE; ret = ckb_load_tx_hash(tx_hash, &len, 0); if (ret != CKB_SUCCESS) { return ERROR_SYSCALL; }
当参数包含在 Script
数据结构的 args
部分, 它们经过 Unix 传统的arc
/argv
方式发送给实际运行的脚本程序。为了进一步保持约定,咱们在argv[0]
处插入一个伪参数,因此 第一个包含的参数从argv[1]
开始。在默认 lock 脚本代码的状况下,它接受一个参数,即从全部者的私钥生成的公钥 hash。
ret = ckb_load_input_by_field(NULL, &len, 0, index, CKB_SOURCE_GROUP_INPUT, CKB_INPUT_FIELD_SINCE); if (ret == CKB_INDEX_OUT_OF_BOUND) { return 0; } if (ret != CKB_SUCCESS) { return ERROR_SYSCALL; }
使用与胡萝卜这个例子相同的技术,咱们检查是否有更多的输入 cell 要测试。与以前的例子有两个不一样:
NULL
做为数据缓冲区,一个 len
变量的值是 0。经过这种方式,syscall 将跳过数据填充,只提供可用的数据长度和正确的返回码用于处理。
cell
命名为group
。 咱们可使用 CKB_SOURCE_GROUP_INPUT
代替 CKB_SOURCE_INPUT
, 来表示只计算同一组中的 cell,举个例子,即具备与当前 cell 相同的 lock 脚本的 cells。len = WITNESS_SIZE; ret = ckb_load_witness(witness, &len, 0, index, CKB_SOURCE_GROUP_INPUT); if (ret != CKB_SUCCESS) { return ERROR_SYSCALL; } if (len > WITNESS_SIZE) { return ERROR_WITNESS_TOO_LONG; } if (!(witness_table = ns(Witness_as_root(witness)))) { return ERROR_ENCODING; } args = ns(Witness_data(witness_table)); if (ns(Bytes_vec_len(args)) < 1) { return ERROR_WRONG_NUMBER_OF_ARGUMENTS; }
继续沿着这个路径,咱们正在加载当前输入的 witness。 对应的 witness 和输入具备相同的索引。如今 CKB 在 syscalls 中使用flatbuffer
做为序列化格式,因此若是你很好奇,flatcc的文档是你最好的朋友。
/* Load signature */ len = TEMP_SIZE; ret = extract_bytes(ns(Bytes_vec_at(args, 0)), temp, &len); if (ret != CKB_SUCCESS) { return ERROR_ENCODING; } /* The 65th byte is recid according to contract spec.*/ recid = temp[RECID_INDEX]; /* Recover pubkey */ secp256k1_ecdsa_recoverable_signature signature; if (secp256k1_ecdsa_recoverable_signature_parse_compact(&context, &signature, temp, recid) == 0) { return ERROR_SECP_PARSE_SIGNATURE; } blake2b_state blake2b_ctx; blake2b_init(&blake2b_ctx, BLAKE2B_BLOCK_SIZE); blake2b_update(&blake2b_ctx, tx_hash, BLAKE2B_BLOCK_SIZE); for (size_t i = 1; i < ns(Bytes_vec_len(args)); i++) { len = TEMP_SIZE; ret = extract_bytes(ns(Bytes_vec_at(args, i)), temp, &len); if (ret != CKB_SUCCESS) { return ERROR_ENCODING; } blake2b_update(&blake2b_ctx, temp, len); } blake2b_final(&blake2b_ctx, temp, BLAKE2B_BLOCK_SIZE);
witness 中的第一个参数是要加载的签名,而其他的参数(若是提供的话)被附加到用于 blake2b 操做的交易 hash 中。
secp256k1_pubkey pubkey; if (secp256k1_ecdsa_recover(&context, &pubkey, &signature, temp) != 1) { return ERROR_SECP_RECOVER_PUBKEY; }
而后使用哈希后的 blake2b 结果做为信息,进行 secp256 签名验证。
size_t pubkey_size = PUBKEY_SIZE; if (secp256k1_ec_pubkey_serialize(&context, temp, &pubkey_size, &pubkey, SECP256K1_EC_COMPRESSED) != 1 ) { return ERROR_SECP_SERIALIZE_PUBKEY; } len = PUBKEY_SIZE; blake2b_init(&blake2b_ctx, BLAKE2B_BLOCK_SIZE); blake2b_update(&blake2b_ctx, temp, len); blake2b_final(&blake2b_ctx, temp, BLAKE2B_BLOCK_SIZE); if (memcmp(argv[1], temp, BLAKE160_SIZE) != 0) { return ERROR_PUBKEY_BLAKE160_HASH; }
最后一样重要的是,咱们还须要检查可恢复签名中包含的 pubkey 确实是用于生成 lock 脚本参数中包含的 pubkey hash 的 pubkey。不然,可能会有人使用另外一个公钥生成的签名来窃取你的 token。
简而言之,默认 lock 脚本中使用的方案与如今比特币中使用的方案很是类似。
我相信你和我如今的感受同样: 咱们能够用 C 语言写合约,这很好,可是 C 语言老是让人以为有点乏味,并且,让咱们面对现实,它很危险。
有更好的方法吗?
固然! 咱们上面提到的 CKB VM 本质上是一台微型计算机,咱们能够探索不少解决方案。 咱们在这里作的一件事是,使用 JavaScript 编写 CKB 脚本代码。 是的,你说对了,简单的 ES5 (是的,我知道,但这只是一个例子,你可使用转换器) JavaScript。
这怎么可能呢? 因为咱们有 C 编译器,咱们只需为嵌入式系统使用一个 JavaScript 实现,在咱们的例子中,duktape 将它从 C 编译成 RISC-V 二进制文件,把它放在链上,咱们就能够在 CKB 上运行 JavaScript 了!由于咱们使用的是一台真正的微型计算机,因此没有什么能够阻止咱们将另外一个 VM 做为 CKB 脚本嵌入到 CKB VM 中,并在 VM 路径上探索这个 VM。
从这条路径展开,咱们能够经过 duktape 在 CKB 上使用 JavaScript,咱们也能够经过 mruby在 ckb 上使用 Ruby, 咱们甚至能够将比特币脚本或EVM放到链上,咱们只须要编译他们的虚拟机,并把它放在链上。这确保了 CKB VM 既能帮助咱们保存资产,又能构建一个多样化的生态系统。全部的语言都应该在 CKB 上被平等对待,自由应该掌握在区块链合约的开发者手中。
在这个阶段,你可能想问: 是的,这是可能的,可是 VM 之上的 VM 不会很慢吗? 我相信这取决于你的例子是否很慢。我坚信,基准测试没有任何意义,除非咱们将它放在具备标准硬件需求的实际用例中。 因此咱们须要有时间检验这是否真的会成为一个问题。 在我看来,高级语言更可能用于 type scripts 来保护 cell 转换,在这种状况下,我怀疑它会很慢。此外,咱们也在这个领域努力工做,以优化 CKB VM 和 VMs 之上的 CKB VM,使其愈来愈快,:P
要在 CKB 上使用 duktape,首先须要将 duktape 自己编译成 RISC-V 可执行二进制文件:
$ git clone https://github.com/nervosnetwork/ckb-duktape $ cd ckb-duktape $ sudo docker run --rm -it -v `pwd`:/code nervos/ckb-riscv-gnu-toolchain:xenial bash root@0d31cad7a539:~# cd /code root@0d31cad7a539:/code# make riscv64-unknown-elf-gcc -Os -DCKB_NO_MMU -D__riscv_soft_float -D__riscv_float_abi_soft -Iduktape -Ic -Wall -Werror c/entry.c -c -o build/entry.o riscv64-unknown-elf-gcc -Os -DCKB_NO_MMU -D__riscv_soft_float -D__riscv_float_abi_soft -Iduktape -Ic -Wall -Werror duktape/duktape.c -c -o build/duktape.o riscv64-unknown-elf-gcc build/entry.o build/duktape.o -o build/duktape -lm -Wl,-static -fdata-sections -ffunction-sections -Wl,--gc-sections -Wl,-s root@0d31cad7a539:/code# exit exit $ ls build/duktape build/duktape*
与 carrot 示例同样,这里的第一步是在 CKB cell 中部署 duktape 脚本代码:
pry(main)> data = File.read("../ckb-duktape/build/duktape") pry(main)> duktape_data.bytesize => 269064 pry(main)> duktape_tx_hash = wallet.send_capacity(wallet.address, CKB::Utils.byte_to_shannon(280000), CKB::Utils.bin_to_hex(duktape_data)) pry(main)> duktape_data_hash = CKB::Blake2b.hexdigest(duktape_data) pry(main)> duktape_out_point = CKB::Types::OutPoint.new(cell: CKB::Types::CellOutPoint.new(tx_hash: duktape_tx_hash, index: 0))
与 carrot 的例子不一样,duktape 脚本代码如今须要一个参数: 要执行的 JavaScript 源代码:
pry(main)> duktape_hello_type_script = CKB::Types::Script.new(code_hash: duktape_data_hash, args: [CKB::Utils.bin_to_hex("CKB.debug(\"I'm running in JS!\")")])
注意,使用不一样的参数,你能够为不一样的用例建立不一样的 duktape 支持的 type script:
pry(main)> duktape_hello_type_script = CKB::Types::Script.new(code_hash: duktape_data_hash, args: [CKB::Utils.bin_to_hex("var a = 1;\nvar b = a + 2;")])
这反映了上面提到的脚本代码与脚本之间的差别:这里 duktape 做为提供 JavaScript 引擎的脚本代码,而不一样的脚本利用 duktape 脚本代码在链上提供不一样的功能。
如今咱们能够建立一个 cell 与 duktape 的 type script 附件:
pry(main)> tx = wallet.generate_tx(wallet2.address, CKB::Utils.byte_to_shannon(200)) pry(main)> tx.deps.push(duktape_out_point.dup) pry(main)> tx.outputs[0].instance_variable_set(:@type, duktape_hello_type_script.dup) pry(main)> tx.witnesses[0].data.clear pry(main)> tx = tx.sign(wallet.key, api.compute_transaction_hash(tx)) pry(main)> api.send_transaction(tx) => "0x2e4d3aab4284bc52fc6f07df66e7c8fc0e236916b8a8b8417abb2a2c60824028"
咱们能够看到脚本执行成功,若是在ckb.toml
文件中将 ckb-script
日志模块的级别设置为debug
,你能够看到如下日志:
2019-07-15 05:59:13.551 +00:00 http.worker8 DEBUG ckb-script script group: c35b9fed5fc0dd6eaef5a918cd7a4e4b77ea93398bece4d4572b67a474874641 DEBUG OUTPUT: I'm running in JS!
如今您已经成功地在 CKB 上部署了一个 JavaScript 引擎,并在 CKB 上运行基于 JavaScript 的脚本!
你能够在这里尝试认识的 JavaScript 代码。
如今你已经熟悉了 CKB 脚本的基础知识,下面是一个思考:
在本文中,您已经看到了一个 always-success 的脚本是什么样子的,可是一个 always-failure 的脚本呢?一个 always-failure 脚本(和脚本代码)能有多小?
提示:这不是 gcc 优化比赛,这只是一个思考。
我知道这是一个很长的帖子,我但愿你已经尝试过,并成功地部署了一个脚本到 CKB。在下一篇文章中,咱们将介绍一个重要的主题:如何在 CKB 定义本身的用户定义 token(UDT)。CKB 上 udt 最好的部分是,每一个用户均可以将本身的 udt 存储在本身的 cell 中,这与 Ethereum 上的 ERC20 令牌不一样,在 Ethereum 上,每一个人的 token 都必须位于 token 发起者的单个地址中。全部这些均可以经过单独使用 type script 来实现。
若是你感兴趣,请继续关注 :)
Nervos Community 致力于成为最好的 Nervos 社区,咱们将持续地推广和普 及 Nervos 技术,深刻挖掘 Nervos 的内在价值,开拓 Nervos 的无限可能, 为每一位想要深刻了解 Nervos Network 的人提供一个优质的平台。
添加微信号:BitcoinDog 便可加入 Nervos Community,若是是程序员请备注,还会将您拉入开发者群。