Skip to content

Commit

Permalink
Implement spawn syscalls (#15)
Browse files Browse the repository at this point in the history
* Update deps and clang-18
* Update ckb-debugger
* Implement spawn
* Implement spawn cell
* Update docs
  • Loading branch information
mohanson authored Oct 14, 2024
1 parent ab2045a commit a3d18ef
Show file tree
Hide file tree
Showing 15 changed files with 711 additions and 79 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ jobs:
make all-via-docker
- name: Install ckb-debugger
run: |
wget 'https://github.com/nervosnetwork/ckb-standalone-debugger/releases/download/v0.107.0/ckb-debugger-linux-x64.tar.gz'
wget 'https://github.com/nervosnetwork/ckb-standalone-debugger/releases/download/v0.118.0/ckb-debugger-linux-x64.tar.gz'
tar zxvf ckb-debugger-linux-x64.tar.gz
chmod +x ckb-debugger
cp ckb-debugger ~/.cargo/bin
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/llvm.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ jobs:
build:
strategy:
matrix:
llvm_version: [16]
llvm_version: [18]

runs-on: ubuntu-latest

Expand All @@ -19,7 +19,7 @@ jobs:
luaVersion: "5.4"
- name: Install ckb-debugger
run: |
wget 'https://github.com/nervosnetwork/ckb-standalone-debugger/releases/download/v0.107.0/ckb-debugger-linux-x64.tar.gz'
wget 'https://github.com/nervosnetwork/ckb-standalone-debugger/releases/download/v0.118.0/ckb-debugger-linux-x64.tar.gz'
tar zxvf ckb-debugger-linux-x64.tar.gz
chmod +x ckb-debugger
cp ckb-debugger ~/.cargo/bin
Expand Down
4 changes: 2 additions & 2 deletions Makefile.clang
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
CURRENT_DIR := $(dir $(abspath $(lastword $(MAKEFILE_LIST))))

ifeq ($(origin LLVM_VERSION),undefined)
LLVM_VERSION = 16
LLVM_VERSION = 18
endif

LLVM_SUFFIX = $(if $(LLVM_VERSION),-$(LLVM_VERSION),)
Expand All @@ -17,7 +17,7 @@ CFLAGS += -fno-builtin-printf -fno-builtin-memcmp \
-nostdinc -nostdlib\
-fdata-sections -ffunction-sections

CFLAGS += -I lualib -I lualib/c-stdlib -I include/ckb-c-stdlib -I include/ckb-c-stdlib/libc -I include/ckb-c-stdlib/molecule
CFLAGS += -I lualib -I lualib/c-stdlib -I include/ckb-c-stdlib -I include/ckb-c-stdlib/libc -I include/ckb-c-stdlib/molecule
CFLAGS += -I deps/compiler-rt-builtins-riscv/compiler-rt/lib/builtins
CFLAGS += -Wall -Werror -Wno-error=unused-command-line-argument -Wno-error=incompatible-library-redeclaration -Wno-error=invalid-noreturn -Wno-error=unused-function

Expand Down
2 changes: 1 addition & 1 deletion deps/compiler-rt-builtins-riscv
106 changes: 105 additions & 1 deletion docs/dylib.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ if err == nil then
end
```

Of course, this example is not enough for real world usage, but this article should be enough to show how to use ckb-lua-vm shared library from the main contract program. You can unleash the potential of Lua with your creativity afterward. For the curious, see here for [an implementation of sudt in lua](https://github.com/nervosnetwork/ckb-lua-vm/blob/4e5375eb2a866595f89826db5510bc9d1f8e9510/contracts/sudt.lua). Also note that, the sample code is written in c. As an alternative, you can write test code in rust. Both [loading shared library](https://docs.rs/ckb-std/latest/ckb_std/dynamic_loading/index.html) and mocking transaction to do unit tests are easy in rust.
Of course, this example is not enough for real world usage, but this article should be enough to show how to use ckb-lua-vm shared library from the main contract program. You can unleash the potential of Lua with your creativity afterward. For the curious, see here for [an implementation of sudt in lua](https://github.com/nervosnetwork/ckb-lua-vm/blob/4e5375eb2a866595f89826db5510bc9d1f8e9510/contracts/sudt.lua). Also note that, the sample code is written in c. As an alternative, you can write test code in rust. Both [loading shared library](https://docs.rs/ckb-std/latest/ckb_std/dynamic_loading/index.html) and mocking transaction to do unit tests are easy in rust.

I will now explain what this sample code does and how to run it.

Expand Down Expand Up @@ -389,6 +389,110 @@ return values: buf (the buffer that contains the header field), err (may be nil

see also: [`ckb_load_header_by_field` syscall](https://github.com/nervosnetwork/rfcs/blob/master/rfcs/0009-vm-syscalls/0009-vm-syscalls.md#load-header-by-field)

#### `ckb.spawn`
description: spawn

calling example: [`ckb.spawn(index, source, place, bounds, argv, fds)`](../examples/spawn.lua)

arguments: index (the index of the cell), source (the source of the cell), place (the place, 0: cell_dep, 1: witness), bounds (offset + length), argv (table of arguments), fds (file descriptors).

return values: pid (process id), err (may be nil object to represent possible error)

see also: [`spawn` syscall](https://github.com/nervosnetwork/rfcs/blob/d14c1598e1f932e9d56537c244bff0ebca0b533a/rfcs/0050-vm-syscalls-3/0050-vm-syscalls-3.md#spawn)

#### `ckb.spawn_cell`
description: spawn cell from cell_dep.

calling example: [`ckb.spawn(code_hash, hash_type, offset, length, argv, fds)`](../examples/spawn.lua)

arguments: code_hash, hash_type, offset, length, argv (table of arguments), fds (file descriptors).

return values: pid (process id), err (may be nil object to represent possible error)

see also: [`spawn` syscall](https://github.com/nervosnetwork/rfcs/blob/d14c1598e1f932e9d56537c244bff0ebca0b533a/rfcs/0050-vm-syscalls-3/0050-vm-syscalls-3.md#spawn)

#### `ckb.wait`
description: wait

calling example: [`ckb.wait(pid)`](../examples/spawn.lua)

arguments: pid

return values: exit code, err (may be nil object to represent possible error)

see also: [`wait` syscall](https://github.com/nervosnetwork/rfcs/blob/d14c1598e1f932e9d56537c244bff0ebca0b533a/rfcs/0050-vm-syscalls-3/0050-vm-syscalls-3.md#wait)

#### `ckb.process_id`
description: process id

calling example: [`ckb.process_id()`](../examples/spawn.lua)

arguments: None

return values: process id

see also: [`process_id` syscall](https://github.com/nervosnetwork/rfcs/blob/d14c1598e1f932e9d56537c244bff0ebca0b533a/rfcs/0050-vm-syscalls-3/0050-vm-syscalls-3.md#process-id)

#### `ckb.pipe`
description: pipe

calling example: [`ckb.pipe()`](../examples/spawn.lua)

arguments: None

return values: fds, err (may be nil object to represent possible error)

see also: [`pipe` syscall](https://github.com/nervosnetwork/rfcs/blob/d14c1598e1f932e9d56537c244bff0ebca0b533a/rfcs/0050-vm-syscalls-3/0050-vm-syscalls-3.md#pipe)

#### `ckb.read`
description: read

calling example: [`ckb.read(fd, size)`](../examples/spawn.lua)

arguments: fd, read_size

return values: buf, err (may be nil object to represent possible error)

see also: [`read` syscall](https://github.com/nervosnetwork/rfcs/blob/d14c1598e1f932e9d56537c244bff0ebca0b533a/rfcs/0050-vm-syscalls-3/0050-vm-syscalls-3.md#read)

#### `ckb.write`
description: write

calling example: [`ckb.write(fd, buf)`](../examples/spawn.lua)

arguments: fd, buf

return values: write_size, err (may be nil object to represent possible error)

see also: [`write` syscall](https://github.com/nervosnetwork/rfcs/blob/d14c1598e1f932e9d56537c244bff0ebca0b533a/rfcs/0050-vm-syscalls-3/0050-vm-syscalls-3.md#write)

#### `ckb.inherited_fds`
description: inherited_fds

calling example: [`ckb.inherited_fds()`](../examples/spawn.lua)

arguments: None

return values: fds, err (may be nil object to represent possible error)

see also: [`inherited_fds` syscall](https://github.com/nervosnetwork/rfcs/blob/d14c1598e1f932e9d56537c244bff0ebca0b533a/rfcs/0050-vm-syscalls-3/0050-vm-syscalls-3.md#inherited-file-descriptors)

#### `ckb.close`
description: close

calling example: [`ckb.close(fd)`](../examples/spawn.lua)

arguments: fd

return values: err (may be nil object to represent possible error)

see also: [`close` syscall](https://github.com/nervosnetwork/rfcs/blob/d14c1598e1f932e9d56537c244bff0ebca0b533a/rfcs/0050-vm-syscalls-3/0050-vm-syscalls-3.md#close)

#### `ckb.load_block_extension`
description: load block extension

see also: [`load_block_extension` syscall](https://github.com/nervosnetwork/rfcs/blob/d14c1598e1f932e9d56537c244bff0ebca0b533a/rfcs/0050-vm-syscalls-3/0050-vm-syscalls-3.md#load-block-extension)

#### `ckb.unpack_script`
description: unpack the buffer that contains the molecule structure `Script`

Expand Down
13 changes: 3 additions & 10 deletions docs/spawn.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +11,12 @@ when the availablity of spawn syscall changes.
There are quite a few benefits in running ckb-lua-vm as a subprocess to the main script.
- The main script's context is saved, and can continue to run when ckb-lua-vm exits.
- The ckb-lua-vm instances are called with command line arguments which can be used to differentiate different tasks.
- Ckb-lua may return data of any format by the function `ckb.set_content`.
- Ckb-lua may return data of any format by the function `ckb.write`.

To demostrate how to extend the capabilities of a main script with ckb-lua-vm, we provide
an example (an admittedly contrived one) that spawn a ckb-lua-vm subprocess which simply concatenate
the command line arguments and return the result to the main script.
the command line arguments and return the result to the main script.

[The main script of this example](../examples/spawn.c) is written in c.
This program can be built with `make all-via-docker`. Afterwards, you may run it
with `make -C tests/test_cases spawnexample` which requries [this branch of ckb-standalone-debugger](https://github.com/mohanson/ckb-standalone-debugger/tree/syscall_spawn).

The main script first reserves some memory for the sub-contract, and then invokes ckb-lua-vm with
the command line arguments `"-e" "local m = arg[2] .. arg[3]; ckb.set_content(m)" "hello" "world"`
which evaluates the lua code `local m = arg[2] .. arg[3]; ckb.set_content(m)`.

This Lua code will first concatenate the strings `hello` and `world`, and then return the result
to the main script with `ckb.set_content`. The main script may read the content on the subprocess exits.
with `make -C tests/test_cases spawnexample`.
75 changes: 50 additions & 25 deletions examples/spawn.c
Original file line number Diff line number Diff line change
Expand Up @@ -4,31 +4,56 @@
#include "ckb_syscalls.h"

int main() {
const char *argv[] = {"-e", "local m = arg[2] .. arg[3]; ckb.set_content(m)",
"hello", "world"};
uint8_t content[80] = {};
uint64_t content_length = 80;
int8_t exit_code = -1;
int err = 0;

spawn_args_t spgs = {
.memory_limit = 8,
.exit_code = &exit_code,
.content = content,
.content_length = &content_length,
};
uint64_t rw_pipe[2] = {0};
err = ckb_pipe(rw_pipe);
if (err != 0) {
return err;
}

int success = ckb_spawn(1, 3, 0, 4, argv, &spgs);
if (success != 0) {
return 1;
}
if (exit_code != 0) {
return 1;
}
if (strlen((char *)content) != 10) {
return 1;
}
if (strcmp((char *)content, "helloworld") != 0) {
return 1;
}
return 0;
const char *argv[] = {
"-e",
"local m = arg[2] .. arg[3]; \
local inherited_fds, err = ckb.inherited_fds(); \
local n, err = ckb.write(inherited_fds[1], m); \
ckb.close(inherited_fds[1]); \
assert(n == 10);\
local pid = ckb.process_id(); \
assert(pid == 1); \
",
"hello",
"world",
};
uint64_t pid = 0;
uint64_t inherited_fds[2] = {rw_pipe[1], 0};
spawn_args_t spgs = {
.argc = 4,
.argv = argv,
.process_id = &pid,
.inherited_fds = inherited_fds,
};

err = ckb_spawn(1, 3, 0, 0, &spgs);
if (err != 0) {
return err;
}
uint8_t buffer[80] = {0};
uint64_t length = 80;
err = ckb_read(rw_pipe[0], buffer, &length);
if (err != 0) {
return err;
}
int8_t exit = 0;
err = ckb_wait(pid, &exit);
if (err != 0) {
return err;
}
if (exit != 0) {
return 1;
}
if (strcmp((char *)buffer, "helloworld") != 0) {
return 1;
}
return 0;
}
27 changes: 27 additions & 0 deletions examples/spawn.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
local callee_code = [[
local m = arg[2] .. arg[3]
local inherited_fds, err = ckb.inherited_fds()
local n, err = ckb.write(inherited_fds[1], m)
ckb.close(inherited_fds[1])
assert(n == 10)
local pid = ckb.process_id()
assert(pid >= 1)
]]

local fds, err = ckb.pipe()
assert(err == nil)
local pid, err = ckb.spawn(0, ckb.SOURCE_CELL_DEP, 0, 0, {"-e", callee_code, "hello", "world"}, {fds[2]})
assert(err == nil)
local ret = ckb.read(fds[1], 10)
assert(ret == "helloworld")
ckb.wait(pid)

local code_hash, err = ckb.load_cell_by_field(0, ckb.SOURCE_CELL_DEP, ckb.CELL_FIELD_DATA_HASH)
assert(err == nil)
local fds, err = ckb.pipe()
assert(err == nil)
local pid, err = ckb.spawn_cell(code_hash, 4, 0, 0, {"-e", callee_code, "hello", "world"}, {fds[2]})
assert(err == nil)
local ret = ckb.read(fds[1], 10)
assert(ret == "helloworld")
ckb.wait(pid)
Loading

0 comments on commit a3d18ef

Please sign in to comment.