Deploy a VM and run code on the VM
Since we are working with a virtualized mini computer in CKB VM, there’s nothing stopping us from embedding another VM as a CKB script that runs on CKB VM. In this article we will explore this VM on top of VM path.
All languages are treated equal on CKB, giving freedom to blockchain contract developers to build on top of CKB however they feel is best.
To use duktape on CKB, first you need to compile the duktape VM itself into a RISC-V executable binary:
$ git clone https://github.com/xxuejie/ckb-duktape $ cd ckb-duktape $ git submodule init $ git submodule update $ 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*
Here we use the ruby SDK to interact with CKB, please refer to the official README for how to set it up. Then deploy the duktape script code in a CKB cell:
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_cell_dep = CKB::Types::CellDep.new(out_point: CKB::Types::OutPoint.new(tx_hash: duktape_tx_hash, index: 0))
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!\")"))
Notice that with a different argument, you can create a different duktape powered type script for a different use case:
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;"))
Now we can create a cell with the duktape type script attached:
pry(main)> tx = wallet.generate_tx(wallet2.address, CKB::Utils.byte_to_shannon(200)) pry(main)> tx.cell_deps.push(duktape_out_point.dup) pry(main)> tx.outputs.type = duktape_hello_type_script.dup pry(main)> tx.witnesses = "0x" pry(main)> tx = tx.sign(wallet.key, api.compute_transaction_hash(tx)) pry(main)> api.send_transaction(tx) => "0x2e4d3aab4284bc52fc6f07df66e7c8fc0e236916b8a8b8417abb2a2c60824028"
We can see that the script executes successfully and if you have the ckb-script module’s log level set to debug in your ckb.toml file, you will also notice the following log:
2019-07-15 05:59:13.551 +00:00 http.worker8 DEBUG ckb-script script group: c35b9fed5fc0dd6eaef5a918cd7a4e4b77ea93398bece4d4572b67a474874641 DEBUG OUTPUT: I'm running in JS!
There are two dynamic linking functions implemented in nervosnetwork/ckb-c-stdlib, which are
ckb_dlopen() loads the dynamic library from a cell by its data hash and returns an opaque "handle" for the dynamic library.
ckb_dlsym() takes a "handle" of a dynamic library returned by
ckb_dlopen() and the symbol name, and returns the address where that symbol is loaded into memory.
nervosnetwork/ckb-miscellaneous-scripts has a simple example for using these two functions.
int ckb_dlopen(const uint8_t *dep_cell_data_hash, uint8_t *aligned_addr, size_t aligned_size, void **handle, size_t *consumed_size); void *ckb_dlsym(void *handle, const char *symbol);
How dependencies work
cell_deps allow scripts in the transaction to access (read-only) referenced live cells.
header_deps allow scripts in the transaction to access (read-only) data of referenced past block headers of the blockchain.
Please refer to the CKB Transaction Structure RFC for more details.