Xuejie 是 CKB-VM 的核心开发者,他在自己的博客「Less is More」中,创作了一系列讲解 CKB 脚本编程的文章,用作补足白皮书中撰写 CKB 脚本所需的所有缺陷的细节构建。本文是该系列的第二篇,详尽介绍了如何将脚本代码部署到 CKB 网络上,慢来查阅吧在上一篇文章里,我讲解了当前 CKB 的检验模型。这一篇不会更为有意思一点,我们要向大家展出如何将脚本代码确实部署到 CKB 网络上去。
我期望在你看完了本文后,你可以有能力自行去探寻 CKB 的世界并按照你自己的意愿去撰写新的脚本代码。但是请注意,尽管我坚信目前的 CKB 的编程模型早已比较平稳了,但是研发仍在展开中,因此未来还可能会有一些变化。我将竭力保证本文一直正处于近期的状态,但是如果在过程到任何困惑,本文以此版本下的 CKB 作为依据:https://github.com/nervosnetwork/ckb/commit/80b51a9851b5d535625c5d144e1accd38c32876bTips:这是一篇很长的文章,因为我想要为下周更加有意思的话题获取充裕的内容。
所以如果你时间不过于不够,你不用立刻读过。我会试着把它分为几个独立国家的部分,这样你就可以一次读书其中一部分。
语法在开始之前,我们再行来区分两个术语:脚本(Script)和脚本代码(Script Code)。在本文以及整个系列文章内,我们将区分脚本和脚本代码。脚本代码实质上是所指你撰写和编译器并在 CKB 上运营的程序。
而脚本,实质上是指 CKB 中用于的脚本数据结构,它不会比脚本代码略为多一点点:pub struct Script {pub args: VecBytes,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。请注意,在大多数情况下,脚本和脚本代码是可以交换用于的,但是如果你在某些地方深感了疑惑,那么你有可能有适当考虑一下两者间的区别。
一个大于的 CKB 脚本代码有可能你之前听闻过,CKB-VM 是基于开源的 RISC-V 指令集撰写的。但这究竟意味著什么呢?用我自己的话来说,这意味著我们(在或许上)在 CKB 中映射了一台确实的微型计算机,而不是一台虚拟机。
一台确实的计算机的益处是,你可以用任何语言撰写任何你想写的逻辑。在这里,网卓新闻网,我们不会再行展出几个用 C 语言撰写的例子,以维持非常简单性(我是说道工具链中的非常简单性,而不是语言),之后我们还不会转换到基于 JavaScript 的脚本代码,并期望在本系列中展出更好的语言。
忘记,在 CKB 上有无限的有可能!正如我们提及的,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 PCB在了一个更容易用于的头文件中,十分欢迎您在这里查阅这个文件,理解如何构建 syscalls:https://github.com/nervosnetwork/ckb-system-scripts/blob/66d7da8ec72dffaa7e9c55904833951eca2422a9/c/ckb_syscalls.h最重要的是,您可以只提供这个头文件并用于纸盒函数来创立您想的系统调用。现在有了 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;}以下几点必须解释一下:1. 由于 C 语言的怪癖,len 字段必须标记为 volatile。我们不会同时用于它作为输出和输入参数,CKB-VM 不能在它还留存在内存中时,才可以把它设置为输入参数。
而 volatile 可以保证 C 编译器将它留存为基于 RISC-V 内存的变量。2. 在用于 syscall 时,我们必须获取以下功能:一个缓冲区来留存 syscall 获取的数据;一个 len 字段,来回应系统调用回到的缓冲区长度和能用数据长度;一个输出数据缓冲区中的偏移量,以及几个我们在交易中必须提供的清楚字段的参数。
下文请参阅我们的 RFC:https://github.com/nervosnetwork/rfcs/blob/master/rfcs/0009-vm-syscalls/0009-vm-syscalls.md3. 为了确保仅次于的灵活性,CKB 用于系统调用的返回值来回应数据捕捉状态:0 (or CKB_SUCCESS) 意味著顺利,1(or CKB_INDEX_OUT_OF_BOUND)意味著您早已通过一种方式提供了所有的索引,2 (or CKB_ITEM_MISSING) 意味著不不存在一个实体,比如从一个不包括该 Type 脚本的 Cell 中提供该 Type 的脚本。总结一下,这个脚本将循环迭代交易中的所有输入 Cell,读取每个 Cell Data 的前 6 个字节,并测试这些字节否和 carrot 给定。如果寻找给定,脚本将回到 -1,回应错误状态;如果没寻找给定,脚本将回到 0 解散,回应继续执行顺利。
为了继续执行该循环,该脚本将留存一个 index 变量,在每次循环递归中,它将企图让 Syscall 提供 Cell 中目前使用的 index 值,如果 Syscall 回到 CKB_INDEX_OUT_OF_BOUND,这意味著脚本早已迭代所有的 Cell,之后不会解散循环;否则,循环将之后,每测试 Cell Data 一次,index 变量就不会递减一次。这是第一个简单的 CKB 脚本代码!在下一节中,我们将展出如何将其部署到 CKB 中并运营它。将脚本部署到 CKB 上首先,我们必须编译器上面写出的关于胡萝卜的源代码。
由于 GCC 早已获取了 RISC-V 的反对,您可以用于官方的 GCC 来创立脚本代码。或者你也可以用于我们打算的 Docker 镜像(https://hub.docker.com/r/nervos/ckb-riscv-gnu-toolchain)来防止编译器 GCC 的困难:$ lscarrot.c ckb_consts.h ckb_syscalls.h$ sudo docker run --rm -it -v `pwd`:/code nervos/ckb-riscv-gnu-toolchain:xenial bash[emailprotected]:/# cd /code[emailprotected]:/code# riscv64-unknown-elf-gcc -Os carrot.c -o carrot[emailprotected]:/code# exitexit$ lscarrot* carrot.c ckb_consts.h ckb_syscalls.h就像上面展出的这样,CKB 可以必要用于 GCC 编译器的可执行文件作为链上的脚本,需要更进一步处置。
我们现在可以在链上部署它了。留意,我将用于 CKB 的 Ruby SDK,因为我曾多次是一名 Ruby 程序员,所以 Ruby 对我来说是最习惯的(但不一定是最差的)。
如何设置请求参照官方 Readme 文件:https://github.com/nervosnetwork/ckb-sdk-ruby/blob/develop/README.md要将脚本部署到 CKB,我们只需创立一个新的 Cell,把脚本代码另设为 Cell Data 部分:pry(main) data = File.read("carrot")pry(main) data.bytesize= 6864pry(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: VecBytes,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.clearpry(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 脚本的脚本,我们必须做到的是:1. 将脚本编译器为 RISC-V 可继续执行的二进制文件;2. 在 Cell 的 Data 部分部署二进制文件;3. 创立一个 Type 脚本数据结构,用于二进制文件的 Blake2b 散列作为 code hash,补足 args 部分中脚本代码的必须的参数;4. 用分解的 Cell 中设置的 Type 脚本创立一个新的交易;5. 将包括脚本代码的 Cell 的 Outpoint 载入到一笔交易的 Deps 中去。虽然在这里我们只辩论了 Type 脚本,但是 Lock 脚本的工作方式完全相同。你唯一必须忘记的是,当你用于特定的 Lock 脚本创立 Cell 时,Lock 脚本会在这里运营,它只在你用于 Cell 时运营。
因此, Type 脚本可以用作结构创立 Cell 时运营的逻辑,而 Lock 脚本用作结构封存 Cell 时运营的逻辑。考虑到这一点,请求保证你的 Lock 脚本是准确的,否则你可能会在以下场景中遗失 Token:您的 Lock 脚本有一个其他人也可以关卡你的 Cell 的 Bug。您的 Lock 脚本有一个 任何人(还包括您)都无法关卡你的 Cell 的 Bug。
在这里我可以获取一个技巧,一直将您的脚本作为一个 Type 脚本可选到你交易的一个 Output Cell 中去展开测试,这样,再次发生错误时,您可以立刻告诉,并且您的 Token 可以始终保持安全性。分析配置文件 Lock 脚本代码根据早已掌控的科学知识,让我们想到 CKB 中包括的配置文件的 Lock 脚本代码。为了防止误解,我们可以在这个 Commit 里查阅 Lock 脚本代码:https://github.com/nervosnetwork/ckb-system-scripts/blob/66e2b3fc4fa3e80235e4b4f94a16e81352a812f7/c/secp256k1_blake160_sighash_all.c配置文件的 Lock 脚本代码将循环迭代与自身具备完全相同 Lock 脚本的所有的 Input Cell,并继续执行以下步骤:它通过获取的 Syscall 提供当前的交易 Hash它提供适当的 Witness 数据作为当前输出对于配置文件 Lock 脚本,假设 Witness 中的第一个参数包括由 Cell 所有者亲笔签名的可恢复亲笔签名,其余参数是用户获取的附加参数配置文件的 Lock 脚本运营由交易 Hash 链接的二进制程序的 Blake2b Hash, 还有所有用户获取的参数(如果不存在的话)将 Blake2b Hash 结果用于 Secp256k1 亲笔签名检验的消息部分。留意,Witness 数据结构中的第一个参数获取了实际的亲笔签名。
如果亲笔签名检验告终,脚本解散并回到错误码。否则它将之后下一个递归。留意,我们在前面辩论了脚本和脚本代码之间的区别。每一个有所不同的公钥哈希都会产生有所不同的 Lock 脚本,因此,如果一个交易的输出 Cell 具备完全相同的配置文件 Lock 脚本代码,但具备有所不同的公钥哈希(因此具备有所不同的 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 要测试。
与之前的例子有两个有所不同:如果我们只想告诉一个 Cell 否不存在并且不必须任何数据,我们只必须起源于 NULL 作为数据缓冲区,一个 len 变量的值是 0。通过这种方式,Syscall 将跳过数据填满,只获取能用的数据长度和准确的回到码用作处置。在这个胡萝卜的例子中,我们循环迭代交易中的所有输出,但这里我们只关心具备完全相同 Lock 脚本的输出 Cell。CKB 将具备完全相同瞄准(或类型)脚本的 Cell 命名为 group。
我们可以用于 CKB_SOURCE_GROUP_INPUT 替换 CKB_SOURCE_INPUT, 来回应只计算出来同一组中的 Cell,荐个例子,即具备与当前 Cell 完全相同的 Lock 脚本的 Cell。len = WITNESS_SIZE;ret = ckb_load_witness(witness, len, 0, index, CKB_SOURCE_GROUP_INPUT);if (ret != CKB_SUCCESS) {return ERROR_SYSCALL;}if (lenWITNESS_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 的文档(https://github.com/dvidelabs/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; ins(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 脚本中用于的方案与现在比特币中用于的方案十分相近。讲解 Duktape我坚信你和我现在的感觉一样:我们可以用 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,使其更加将要在 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[emailprotected]:~# cd /code[emailprotected]:/code# makeriscv64-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.oriscv64-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.oriscv64-unknown-elf-gcc build/entry.o build/duktape.o -o build/duktape -lm -Wl,-static -fdata-sections -ffunction-sections -Wl,--gc-sections -Wl,-s[emailprotected]:/code# exitexit$ ls build/duktapebuild/duktape*与胡萝卜的示例一样,这里的第一步是在 CKB Cell 中部署 Duktape 脚本代码:pry(main) data = File.read("../ckb-duktape/build/duktape")pry(main) duktape_data.bytesize= 269064pry(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))但是,和和胡萝卜例子有所不同的是,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.clearpry(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 来构建。
本文关键词:8688,体育,官网,CKB,8688体育官网,脚本,编程,简介,第二,弹
本文来源:8688体育官网-www.eventsbysm.com