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¶ms2=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