Design of Contracts

说实话其实一开始压根没想做合约,就只是想做个chain。

但是后来发现你不做DAPP你的chain都没人看,那些做coin的也都在往DAPP靠。就有种被逼上梁山的感觉。不过还好现在有个去华山的路(WASM),可以考虑上了华山再往梁山飞。

其实我也不算是第一个选择华山飞梁山的,cosmos和eth2.0也选,不过人家有现成地图,我只能凭感觉乱飘。而且我们不是一个山头起飞的,他们选了更早被人发现的山头(wasmer),我选了个看起来粗大(?)一点的新山头(wasmtime)。

不瞎扯了,下面就开始分析对于合约的设计。

做ethereum开发的都知道Solidity,因此需求也是从这个上面靠拢。不过由于现在主要支持wasm的语言C和RUST都是比较成熟的所以直接就选择他们对具体功能开干。

±x÷是都没有问题,问题在于和链的互动,和rpc的互动,以及事件的侦听。

ngVM

格式

上传至链上的wasm需要为binary格式

数据类型 data type

  • i32: 返回code,>0为正常,<=异常
  • i64: 指针
  • u64: 账户
  • bytes: 底层mem

Imports

wasm只能import在ngVMI中指定的接口。

Debug

在Debug环境里提供一系列print的接口

Exports

合同必须完全具有两个export:

  • memory: 可供EEI写入的共享内存空间。
  • main: 没有参数和结果值的函数。

入口

导出为main的方法将由NGIN内部VM Manager执行。

如果成功执行,则正常返回。

如果由于故障而需要中止,则返回error,避免panic。

异常

如果执行wasm代码触发了wasm异常,则执行将失败并终止。

接口(ngVMI: NGIN VM Interface)

WIP

log::debug(msg_ptr i64, msg_len i64)

log::info(msg_ptr i64, msg_len i64)

log::error(msg_ptr i64, msg_len i64)

log::warn(msg_ptr i64, msg_len i64)

打印输出,默认关闭。

account::get_host() -> num i64

获取vm自身account num(uint64)

account::get_owner_size(account_num uint64) -> owner_ptr i64

获取vm自身owner’s address的长度(当前肯定是35)

account::get_owner(account_num uint64, ptr uint32) -> owner_ptr i64

获取vm自身owner’s address (length = 35 bytes),并将其写入ptr指针处。返回值为成功写入的字节数。

account::get_contract(len_ptr i64) -> contract_ptr i64

已删除

原:获取vm自身合约代码

现:由于获取全部二进制内容毫无意义,而且会占用较多内存,所以废弃此内容。
作为替代,将来提供get_contract_hash函数来帮助开发者做对线上内容的快速double check。
不能在内容里对内容hash……

如果是需要读取其他account下的contract内容,暂时感觉也没必要。

account::get_context(account_num u64, ptr u32) -> u32

获取可持续存储内容

可持续存储读取

context::alloc(len: u64)

为可持续存储分配空间
//TODO: 收费

context::write(src_ptr: i64, dst_ptr: i64, len: u64)

可持续存储写入

coin::transfer(to: u64, value_ptr: i64) -> i32

类似于eth里的seller.send(price)

原来叫create_transaction,表示由wasm_vm主动创建一个事务tx让chain通过处理tx方式修改state

通过此函数,vm能够提供例如bank/token的基础资金转移功能。

但是遇到一个之前忽略掉的问题——从vm里发出的交易该怎么sign?

根据ETH的方法,调用都会被算为是由tx的call触发,因此这些变化都是由原tx负责。

换句话说,变动的只有state,并不会出现由contract主动触发的tx。

因此这个tx摆在这里就不合适了,因为vm里的操作都是直接作用于state

所以这里就不应该使用transaction or tx来称呼这个操作,而用transfer来简化这个函数。

假如to的account不存在,那个需要触发trap

tx::get_convener() -> num u64

获取transaction的convener

tx::get_participants_len() -> len u64

获取transaction的参与人数量

tx::get_participant(index: u64) -> account_ptr i64

获取transaction的参与人,需要用account namespace的api处理

tx::get_values_len() -> len u64

获取transaction的参与人数量

tx::get_value(index: u64) -> value_ptr i64

获取transacation的对应value(uint256)

state::get_balance(addr: i64) -> balance_ptr i64

balance 长度 8*4 = 24

state::get_owner(num: u64) -> addr_ptr i64

addr 长度 33

Deploy

Assign & Append tx

Publish

WASM的发布分为两种: Web or Wasm.

Web

任何ngcore节点能够作为http server提供http服务(单页面),端口为52528。默认关闭,通过Flag打开

例如 http://app.ngin.sh:52528/#10086/path1/path2

服务运营者可以通过nginx反代限制app为

https://app.mydomain.com/path1/path2?param1=1&params2=2

发布Web应用需要将wasm binary写入Contract字段。在访问时会自动将字段作为文件拉取,并且通过导出的main函数进行执行

Wasm

Wasm可以作为网站后端运行,也可以纯提供链上事务或者作为其他Wasi运行库

Wasm要求Contract为wasm binary.

当前遇到的问题

Infinite loop

当前使用wasmtime,无法在运行前拦截vm中infinite loop

当loop执行,就会导致全网主机阻塞在处理该vm对应的tx上

感觉该看一下wasm的opcode

Fee charging

由于wasmtime只提供封闭的ffi,因此无法将opcode与fee进行挂钩

这里可以理解为我们需要自己写一个ngin专用的wasm runtime

找材料的时候发现了perlin-network的一个wasm方案,life.

钱多好办事哈,人家自己也折腾了个chain daemon叫wavelet,意思是小波浪

life里加gas是在compile时候,对每个Block做totalCost的计算(递增1,即每个instruction费用为1),然后给每个block的code前面都加上一个add_gas的instruction。

当然这个add_gas的ins当前还没用,也不知道他们准备加点啥。

compile是将wasm binary转换为host上的一个module,他们的api应该是参考的js interface