Unverified Commit 2983a997 authored by vipwzw's avatar vipwzw Committed by GitHub

Merge pull request #903 from yann-sjtu/gowasm

Gowasm
parents ff138022 0f956137
...@@ -28,6 +28,7 @@ require ( ...@@ -28,6 +28,7 @@ require (
github.com/miguelmota/go-solidity-sha3 v0.1.0 github.com/miguelmota/go-solidity-sha3 v0.1.0
github.com/mr-tron/base58 v1.2.0 github.com/mr-tron/base58 v1.2.0
github.com/pborman/uuid v1.2.0 github.com/pborman/uuid v1.2.0
github.com/perlin-network/life v0.0.0-20191203030451-05c0e0f7eaea
github.com/phoreproject/bls v0.0.0-20200525203911-a88a5ae26844 github.com/phoreproject/bls v0.0.0-20200525203911-a88a5ae26844
github.com/pkg/errors v0.9.1 github.com/pkg/errors v0.9.1
github.com/prometheus/client_golang v0.9.2 // indirect github.com/prometheus/client_golang v0.9.2 // indirect
......
This diff is collapsed.
...@@ -30,5 +30,6 @@ import ( ...@@ -30,5 +30,6 @@ import (
_ "github.com/33cn/plugin/plugin/dapp/trade" //auto gen _ "github.com/33cn/plugin/plugin/dapp/trade" //auto gen
_ "github.com/33cn/plugin/plugin/dapp/unfreeze" //auto gen _ "github.com/33cn/plugin/plugin/dapp/unfreeze" //auto gen
_ "github.com/33cn/plugin/plugin/dapp/valnode" //auto gen _ "github.com/33cn/plugin/plugin/dapp/valnode" //auto gen
_ "github.com/33cn/plugin/plugin/dapp/wasm" //auto gen
_ "github.com/33cn/plugin/plugin/dapp/x2ethereum" //auto gen _ "github.com/33cn/plugin/plugin/dapp/x2ethereum" //auto gen
) )
package commands
import (
"fmt"
"io/ioutil"
"os"
"github.com/33cn/chain33/rpc/jsonclient"
rpctypes "github.com/33cn/chain33/rpc/types"
"github.com/33cn/chain33/types"
wasmtypes "github.com/33cn/plugin/plugin/dapp/wasm/types"
"github.com/spf13/cobra"
)
// Cmd wasm 命令行
func Cmd() *cobra.Command {
cmd := &cobra.Command{
Use: "wasm",
Short: "Wasm management",
Args: cobra.MinimumNArgs(1),
}
cmd.AddCommand(
cmdCheckContract(),
cmdCreateContract(),
cmdCallContract(),
)
return cmd
}
func cmdCheckContract() *cobra.Command {
cmd := &cobra.Command{
Use: "check",
Short: "Check whether the contract with the given name exists or not.",
Run: checkContract,
}
cmd.Flags().StringP("name", "n", "", "contract name")
_ = cmd.MarkFlagRequired("name")
return cmd
}
func cmdCreateContract() *cobra.Command {
cmd := &cobra.Command{
Use: "create",
Short: "publish a new contract on chain33",
Run: createContract,
}
cmd.Flags().StringP("name", "n", "", "contract name")
cmd.Flags().StringP("path", "p", "", "path of the wasm file, such as ./test.wasm")
_ = cmd.MarkFlagRequired("name")
_ = cmd.MarkFlagRequired("path")
return cmd
}
func cmdCallContract() *cobra.Command {
cmd := &cobra.Command{
Use: "call",
Short: "call contract on chain33",
Run: callContract,
}
cmd.Flags().StringP("name", "n", "", "contract name")
cmd.Flags().StringP("method", "m", "", "method name")
cmd.Flags().IntSliceP("parameters", "p", nil, "parameters of the method which should be num")
_ = cmd.MarkFlagRequired("name")
_ = cmd.MarkFlagRequired("method")
return cmd
}
func checkContract(cmd *cobra.Command, args []string) {
rpcLaddr, _ := cmd.Flags().GetString("rpc_laddr")
name, _ := cmd.Flags().GetString("name")
params := rpctypes.Query4Jrpc{
Execer: wasmtypes.WasmX,
FuncName: "Check",
Payload: types.MustPBToJSON(&wasmtypes.QueryCheckContract{
Name: name,
}),
}
var resp types.Reply
ctx := jsonclient.NewRPCCtx(rpcLaddr, "Chain33.Query", params, &resp)
ctx.Run()
}
func createContract(cmd *cobra.Command, args []string) {
rpcLaddr, _ := cmd.Flags().GetString("rpc_laddr")
name, _ := cmd.Flags().GetString("name")
path, _ := cmd.Flags().GetString("path")
// Read WebAssembly *.wasm file.
code, err := ioutil.ReadFile(path)
if err != nil {
fmt.Fprintln(os.Stderr, err)
return
}
payload := wasmtypes.WasmCreate{
Name: name,
Code: code,
}
params := rpctypes.CreateTxIn{
Execer: wasmtypes.WasmX,
ActionName: "Create",
Payload: types.MustPBToJSON(&payload),
}
ctx := jsonclient.NewRPCCtx(rpcLaddr, "Chain33.CreateTransaction", params, nil)
ctx.RunWithoutMarshal()
}
func callContract(cmd *cobra.Command, args []string) {
rpcLaddr, _ := cmd.Flags().GetString("rpc_laddr")
name, _ := cmd.Flags().GetString("name")
method, _ := cmd.Flags().GetString("method")
parameters, _ := cmd.Flags().GetIntSlice("parameters")
var parameters2 []int64
for _, param := range parameters {
parameters2 = append(parameters2, int64(param))
}
payload := wasmtypes.WasmCall{
Contract: name,
Method: method,
Parameters: parameters2,
}
params := rpctypes.CreateTxIn{
Execer: wasmtypes.WasmX,
ActionName: "Call",
Payload: types.MustPBToJSON(&payload),
}
ctx := jsonclient.NewRPCCtx(rpcLaddr, "Chain33.CreateTransaction", params, nil)
ctx.RunWithoutMarshal()
}
### 合约开发
新建 cpp 和 hpp 文件,并导入 common.h 头文件,其中 common.h 中声明了 chain33 中的回调函数,是合约调用 chain33 系统方法的接口。
合约中的导出方法的所有参数都只能是数字类型,且必须有一个数字类型的返回值,其中非负值表示执行成功,负值表示执行失败。
### 合约编译
#### Emscripten 环境安装
```bash
git clone https://github.com/juj/emsdk.git
cd emsdk
./emsdk install latest
./emsdk activate latest
# 安装完之后需要配置临时环境变量,新建终端时需要重新执行以下命令
# on Linux or Mac OS X
source ./emsdk_env.sh
# on Windows
emsdk_env.bat
```
#### 使用 Emscripten 编译合约
```bash
em++ -o dice.wasm dice.cpp -s WASM=1 -O3 -s EXPORTED_FUNCTIONS="[_startgame, _deposit, _play, _draw, _stopgame]" -s ERROR_ON_UNDEFINED_SYMBOLS=0
```
- em++ 是 wasm 编译器,用来编译 c++ 代码,编译c代码可以用 emcc
- -o 指定输出合约文件名,格式为 .wasm
- dice.cpp 是源文件
- -s WASM=1 指定生成 wasm 文件
- -O3 优化等级,可以优化生成的 wasm 文件大小,可指定为 1~3
- -s EXPORTED_FUNCTIONS 指定导出方法列表,以逗号分隔,合约外部只可以调用导出方法,不能调用非导出方法,导出方法的函数名需要额外加一个 "_"
- -s ERROR_ON_UNDEFINED_SYMBOLS=0 忽略未定义错误,因为 common.h 中声明的方法没有具体的 c/c++ 实现,而是在 chain33 中用 go 实现,因此需要忽略该错误,否则将编译失败
参考文档:https://developer.mozilla.org/en-US/docs/WebAssembly
#### 安装 wabt(the WebAssembly Binary Toolkit)
```bash
git clone --recursive https://github.com/WebAssembly/wabt
cd wabt
mkdir build
cd build
cmake ..
cmake --build .
```
```bash
# 通过 wasm 文件生成接口abi,abi中可以通过import关键字找到外部导入方法,以及export关键字找到编译时指定的导出方法。
wabt/bin/wasm2wat dice.wasm
```
### 发布合约
```bash
./chain33-cli send wasm create -n 指定合约名 -p wasm合约路径 -k 用户私钥
```
### 调用合约
```bash
#其中参数为用逗号分隔的数字
./chain33-cli send wasm call -n 发布合约时指定的合约 -m 调用合约方法名 -p 参数 -k 用户私钥
```
### 转账及提款
```bash
#部分合约调用可能需要在合约中有余额,需要先转账到 wasm 合约
./chain33-cli send coins send_exec -e wasm -a 数量 -k 用户私钥
#提款
./chain33-cli send coins withdraw -e wasm -a 数量 -k 用户私钥
```
### RPC接口调用
```bash
#构造调用合约的交易
curl http://localhost:8801 -ksd '{"method":"wasm.CallContract", "params":[{"contract":"dice","method":"play","parameters":[1000000000, 10]}]}'
#签名
curl http://localhost:8801 -ksd '{"method":"Chain33.SignRawTx","params":[{"privkey":"0x4257d8692ef7fe13c68b65d6a52f03933db2fa5ce8faf210b5b8b80c721ced01","txhex":"0x0a047761736d1218180212140a04646963651204706c61791a068094ebdc030a20a08d0630ec91c19ede9ef4d1693a22314b3732554137393845775a66427855546b4265686864766b656f3277377446344c","expire":"300s"}]}'
#发送
curl http://localhost:8801 -ksd '{"method":"Chain33.SendTransaction","params":[{"data":"0a047761736d1218180212140a04646963651204706c61791a068094ebdc030a1a6d080112210320bbac09528e19c55b0f89cb37ab265e7e856b1a8c388780322dbbfd194b52ba1a46304402201dc04e89da9220e42b2768a23cd2e6a7c452b2bfd30e0799f5c6f1b035151d1402201160929f74feb26be4205cf4432bdf377eb775f189db2883556cedc31c4fb01920a08d0628b2cb90fb0530ec91c19ede9ef4d1693a22314b3732554137393845775a66427855546b4265686864766b656f3277377446344c"}]}'
```
\ No newline at end of file
#ifdef __cplusplus
extern "C" {
#endif
#include <stddef.h>
#include <stdint.h>
void setStateDB(const char* key, size_t k_len, const char* value, size_t v_len);
size_t getStateDBSize(const char* key, size_t k_len);
size_t getStateDB(const char* key, size_t k_len, char* value, size_t v_len);
void setLocalDB(const char* key, size_t k_len, const char* value, size_t v_len);
size_t getLocalDBSize(const char* key, size_t k_len);
size_t getLocalDB(const char* key, size_t k_len, char* value, size_t v_len);
int64_t getBalance(const char* addr, size_t addr_len, const char* exec_addr, size_t exec_len);
int64_t getFronzen(const char* addr, size_t addr_len, const char* exec_addr, size_t exec_len);
void execAddress(const char* name, size_t name_len, const char* addr, size_t addr_len);
int transfer(const char* from_addr, size_t from_len, const char* to_addr, size_t to_len, int64_t amount);
int transferToExec(const char* from_addr, size_t from_len, const char* exec_addr, size_t exec_len, int64_t amount);
int transferWithdraw(const char* from_addr, size_t from_len, const char* exec_addr, size_t exec_len, int64_t amount);
int execFrozen(const char* addr, size_t addr_len, int64_t amount);
int execActive(const char* addr, size_t addr_len, int64_t amount);
int execTransfer(const char* from_addr, size_t from_len, const char* to_addr, size_t to_len, int64_t amount);
int execTransferFrozen(const char* from_addr, size_t from_len, const char* to_addr, size_t to_len, int64_t amount);
void getFrom(const char* from_addr, size_t from_len);
int64_t getHeight();
int64_t getRandom();
void sha256(const char* data, size_t data_len, char* sum, size_t sum_len);
void printlog(const char* log, size_t len);
void printint(int64_t n);
#ifdef __cplusplus
}
#endif
inline size_t string_size(const char* s) {
size_t l = 0;
for (const char* tmp=s;*tmp!='\0';tmp++) {
l++;
}
return l;
}
#include "../common.h"
#include "dice.hpp"
#include <string.h>
#define STATUS "dice_status\0"
#define ROUND_KEY_PREFIX "roundinfo:"
#define ROUND_KEY_PREFIX_LEN 10
#define OK 0
int startgame(int64_t amount) {
char from[34]={0};
getFrom(from, 34);
printlog(from, 34);
gamestatus status = get_status();
if (status.is_active) {
const char info[] = "active game\0";
printlog(info, string_size(info));
return -1;
}
if ((status.height != 0) && (strncmp(from, status.game_creator, 34) != 0)) {
const char info[] = "game can only be restarted by the creator\0";
printlog(info, string_size(info));
return -1;
}
if (amount <= 0) {
return -1;
}
if (OK != execFrozen(from, 34, amount)) {
const char info[] = "frozen coins failed\0";
printlog(info, string_size(info));
return -1;
}
status.height = getHeight();
status.is_active = true;
status.deposit = amount;
strcpy(status.game_creator, from);
status.game_balance = amount;
set_status(status);
const char info[] = "call contract success\0";
printlog(info, string_size(info));
return 0;
}
int deposit(int64_t amount) {
gamestatus status = get_status();
set_status(status);
if (!status.is_active) {
const char info[] = "inactive game\0";
printlog(info, string_size(info));
return -1;
}
char from[34]={0};
getFrom(from, 34);
printlog(from, 34);
printlog(status.game_creator, 34);
if (strncmp(from, status.game_creator, 34) != 0) {
const char info[] = "game can only be deposited by the creator\0";
printlog(info, string_size(info));
return -1;
}
if (amount<=0) {
return -1;
}
if (OK != execFrozen(from, 34, amount)) {
const char info[] = "frozen coins failed\0";
printlog(info, string_size(info));
return -1;
}
status.deposit += amount;
status.game_balance += amount;
set_status(status);
return 0;
}
int play(int64_t amount, int64_t number) {
gamestatus status = get_status();
if (!status.is_active) {
const char info[] = "inactive game\0";
printlog(info, string_size(info));
return -1;
}
if (number<2 || number>97) {
const char info[] = "number must be within range of [2,97]\0";
printlog(info, string_size(info));
return -1;
}
if (amount<=0) {
return -1;
}
//最大投注额为奖池的0.5%
if (amount*200>status.game_balance) {
const char info[] = "amount is too big\0";
printlog(info, string_size(info));
return -1;
}
char from[34]={0};
getFrom(from, 34);
if (OK != execFrozen(from, 34, amount)) {
const char info[] = "frozen coins failed\0";
printlog(info, string_size(info));
return -1;
}
status.current_round++;
status.total_bets += amount;
set_status(status);
roundinfo info;
info.round = status.current_round;
info.height = getHeight();
strcpy(info.player, from);
info.amount = amount;
info.guess_num = number;
set_roundinfo(info);
return 0;
}
int draw() {
gamestatus status = get_status();
if (!status.is_active) {
const char info[] = "inactive game\0";
printlog(info, string_size(info));
return -1;
}
char from[34]={0};
getFrom(from, 34);
if (strncmp(from, status.game_creator, 34) != 0) {
const char info[] = "game can only be drawn by the creator\0";
printlog(info, string_size(info));
return -1;
}
if (status.current_round == status.finished_round) {
//没有待开奖记录
return 0;
}
int64_t height = getHeight();
status.height = height;
int64_t random = getRandom() % 100;
printint(random);
int64_t round=status.finished_round+1;
for (int64_t round=status.finished_round+1;round<=status.current_round;round++) {
roundinfo info = get_roundinfo(round);
if (info.height == status.height) {
break;
}
if (random < info.guess_num) {
int64_t probability = info.guess_num;
int64_t payout = info.amount *(100 - probability) / probability;
if (OK != execTransferFrozen(status.game_creator, 34, info.player, 34, payout-info.amount)) {
const char info[] = "transfer frozen coins from game creator failed\0";
printlog(info, string_size(info));
return -1;
}
if (OK != execActive(info.player, 34, info.amount)) {
const char info[] = "active frozen coins failed\0";
printlog(info, string_size(info));
return -1;
}
status.total_player_win += payout;
status.game_balance -= (payout - info.amount);
info.player_win = true;
} else {
if (OK != execTransferFrozen(info.player, 34, status.game_creator, 34, info.amount)) {
const char info[] = "transfer frozen coins from player failed\0";
printlog(info, string_size(info));
return -1;
}
if (OK != execFrozen(status.game_creator, 34, info.amount)) {
const char info[] = "frozen coins failed\0";
printlog(info, string_size(info));
return -1;
}
info.player_win = false;
status.game_balance += info.amount;
}
info.rand_num = random;
info.is_finished = true;
status.finished_round++;
set_roundinfo(info);
set_status(status);
}
return 0;
}
int stopgame() {
gamestatus status = get_status();
char from[34]={0};
getFrom(from, 34);
if (strncmp(from, status.game_creator, 34) != 0) {
const char info[] = "game can only be stopped by the creator\0";
printlog(info, string_size(info));
return -1;
}
if (status.finished_round != status.current_round) {
// const char info[] = "inactive game\0";
// printlog(info, string_size(info));
return -1;
}
if (!status.is_active) {
const char info[] = "inactive game\0";
printlog(info, string_size(info));
return -1;
}
if (OK != execActive(from, 34, status.game_balance)) {
const char info[] = "active frozen coins failed\0";
printlog(info, string_size(info));
return -1;
}
status.is_active = false;
status.deposit = 0;
status.game_balance = 0;
set_status(status);
return 0;
}
gamestatus get_status() {
char status_key[] = STATUS;
gamestatus status;
getStateDB(status_key, string_size(status_key), (char*)(&status), sizeof(status));
return status;
}
void set_status(gamestatus status) {
char status_key[] = STATUS;
setStateDB(status_key, string_size(status_key), (char*)(&status), sizeof(status));
}
roundinfo get_roundinfo(int64_t round) {
char round_key[32];
gen_roundinfo_key(round_key, round);
roundinfo info;
getStateDB(round_key, string_size(round_key), (char*)(&info), sizeof(info));
return info;
}
void set_roundinfo(roundinfo info) {
char round_key[32];
gen_roundinfo_key(round_key, info.round);
setStateDB(round_key, string_size(round_key), (char*)(&info), sizeof(info));
}
void gen_roundinfo_key(char* round_key, int64_t round) {
strcpy(round_key, ROUND_KEY_PREFIX);
char round_str[20] = {0};
int index;
for (index=0;;index++) {
round_str[index] = char(round%10) + '0';
round/=10;
if (round==0) {
break;
}
}
for (int i=0;i<=index;i++) {
round_key[ROUND_KEY_PREFIX_LEN+i] = round_str[index-i];
}
round_key[ROUND_KEY_PREFIX_LEN+index+1] = '\0';
}
struct roundinfo {
int64_t round;
int64_t amount;
int64_t height;
int64_t guess_num;
int64_t rand_num;
char player[34];
bool player_win;
bool is_finished;
};
struct addrinfo {
int64_t betting_times;
int64_t betting_amount;
int64_t earnings;
};
struct gamestatus {
char game_creator[34];
int64_t deposit;
int64_t height;
int64_t game_balance;
int64_t current_round;
int64_t finished_round;
int64_t total_bets;
int64_t total_player_win;
bool is_active;
};
#ifdef __cplusplus //而这一部分就是告诉编译器,如果定义了__cplusplus(即如果是cpp文件,
extern "C" { //因为cpp文件默认定义了该宏),则采用C语言方式进行编译
#endif
int startgame(int64_t amount);
int deposit(int64_t amount);
int play(int64_t amount, int64_t number);
int draw();
int stopgame();
#ifdef __cplusplus
}
#endif
//void withdraw(char* creator);
gamestatus get_status();
void set_status(gamestatus status);
//void update_addrinfo(char* addr, int64_t amount, int64_t earnings);
void set_roundinfo(roundinfo info);
roundinfo get_roundinfo(int64_t round);
void gen_roundinfo_key(char* round_key, int64_t round);
//bool is_active();
\ No newline at end of file
package executor
import (
"github.com/33cn/chain33/common"
"github.com/33cn/chain33/common/address"
"github.com/33cn/chain33/types"
types2 "github.com/33cn/plugin/plugin/dapp/wasm/types"
)
//stateDB wrapper
func setStateDB(key, value []byte) {
wasmCB.stateKVC.Add(key, value)
}
func getStateDBSize(key []byte) int {
value, err := getStateDB(key)
if err != nil {
return 0
}
return len(value)
}
func getStateDB(key []byte) ([]byte, error) {
return wasmCB.stateKVC.Get(key)
}
//localDB wrapper
func setLocalDB(key, value []byte) {
wasmCB.localCache = append(wasmCB.localCache, &types2.LocalDataLog{
Key: append(calcLocalPrefix(wasmCB.contractName), key...),
Value: value,
})
}
func getLocalDBSize(key []byte) int {
value, err := getLocalDB(key)
if err != nil {
return 0
}
return len(value)
}
func getLocalDB(key []byte) ([]byte, error) {
newKey := append(calcLocalPrefix(wasmCB.contractName), key...)
// 先查缓存,再查数据库
for _, kv := range wasmCB.localCache {
if string(newKey) == string(kv.Key) {
return kv.Value, nil
}
}
return wasmCB.GetLocalDB().Get(newKey)
}
//account wrapper
func getBalance(addr, execer string) (balance, frozen int64, err error) {
accounts, err := wasmCB.GetCoinsAccount().GetBalance(wasmCB.GetAPI(), &types.ReqBalance{
Addresses: []string{addr},
Execer: execer,
})
if err != nil {
return -1, -1, err
}
return accounts[0].Balance, accounts[0].Frozen, nil
}
func transfer(from, to string, amount int64) error {
receipt, err := wasmCB.GetCoinsAccount().Transfer(from, to, amount)
if err != nil {
return err
}
wasmCB.receiptLogs = append(wasmCB.receiptLogs, receipt.Logs...)
return nil
}
func transferToExec(addr, execaddr string, amount int64) error {
receipt, err := wasmCB.GetCoinsAccount().TransferToExec(addr, execaddr, amount)
if err != nil {
return err
}
wasmCB.receiptLogs = append(wasmCB.receiptLogs, receipt.Logs...)
return nil
}
func transferWithdraw(addr, execaddr string, amount int64) error {
receipt, err := wasmCB.GetCoinsAccount().TransferWithdraw(addr, execaddr, amount)
if err != nil {
return err
}
wasmCB.receiptLogs = append(wasmCB.receiptLogs, receipt.Logs...)
return nil
}
func execFrozen(addr string, amount int64) error {
receipt, err := wasmCB.GetCoinsAccount().ExecFrozen(addr, wasmCB.execAddr, amount)
if err != nil {
log.Error("execFrozen", "error", err)
return err
}
wasmCB.kvs = append(wasmCB.kvs, receipt.KV...)
wasmCB.receiptLogs = append(wasmCB.receiptLogs, receipt.Logs...)
return nil
}
func execActive(addr string, amount int64) error {
receipt, err := wasmCB.GetCoinsAccount().ExecActive(addr, wasmCB.execAddr, amount)
if err != nil {
return err
}
wasmCB.kvs = append(wasmCB.kvs, receipt.KV...)
wasmCB.receiptLogs = append(wasmCB.receiptLogs, receipt.Logs...)
return nil
}
func execTransfer(from, to string, amount int64) error {
receipt, err := wasmCB.GetCoinsAccount().ExecTransfer(from, to, wasmCB.execAddr, amount)
if err != nil {
return err
}
wasmCB.kvs = append(wasmCB.kvs, receipt.KV...)
wasmCB.receiptLogs = append(wasmCB.receiptLogs, receipt.Logs...)
return nil
}
func execTransferFrozen(from, to string, amount int64) error {
receipt, err := wasmCB.GetCoinsAccount().ExecTransferFrozen(from, to, wasmCB.execAddr, amount)
if err != nil {
return err
}
wasmCB.kvs = append(wasmCB.kvs, receipt.KV...)
wasmCB.receiptLogs = append(wasmCB.receiptLogs, receipt.Logs...)
return nil
}
func execAddress(name string) string {
return address.ExecAddress(name)
}
func getFrom() string {
return wasmCB.tx.From()
}
func getHeight() int64 {
return wasmCB.GetHeight()
}
func getRandom() int64 {
req := &types.ReqRandHash{
ExecName: "ticket",
BlockNum: 5,
Hash: wasmCB.GetLastHash(),
}
hash, err := wasmCB.GetExecutorAPI().GetRandNum(req)
if err != nil {
return -1
}
var rand int64
for _, c := range hash {
rand = rand*256 + int64(c)
}
if rand < 0 {
return -rand
}
return rand
}
func printlog(s string) {
wasmCB.customLogs = append(wasmCB.customLogs, s)
}
func sha256(data []byte) []byte {
return common.Sha256(data)
}
package executor
import (
"github.com/33cn/chain33/types"
types2 "github.com/33cn/plugin/plugin/dapp/wasm/types"
)
// "mavl-wasm-code-{name}"
func contractKey(name string) []byte {
return append([]byte("mavl-"+types2.WasmX+"-code-"), []byte(name)...)
}
// "mavl-wasm-{contract}-"
func calcStatePrefix(contract string) []byte {
var prefix []byte
prefix = append(prefix, types.CalcStatePrefix([]byte(types2.WasmX))...)
prefix = append(prefix, []byte(contract)...)
prefix = append(prefix, '-')
return prefix
}
// "LODB-wasm-{contract}-"
func calcLocalPrefix(contract string) []byte {
var prefix []byte
prefix = append(prefix, types.CalcLocalPrefix([]byte(types2.WasmX))...)
prefix = append(prefix, []byte(contract)...)
prefix = append(prefix, '-')
return prefix
}
func (w *Wasm) contractExist(name string) bool {
_, err := w.GetStateDB().Get(contractKey(name))
if err != nil && err != types.ErrNotFound {
panic(err)
}
return err == nil
}
package executor
import (
"encoding/hex"
"github.com/33cn/chain33/common/address"
"github.com/33cn/chain33/system/dapp"
"github.com/33cn/chain33/types"
types2 "github.com/33cn/plugin/plugin/dapp/wasm/types"
"github.com/perlin-network/life/compiler"
"github.com/perlin-network/life/exec"
validation "github.com/perlin-network/life/wasm-validation"
)
var wasmCB *Wasm
func (w *Wasm) userExecName(name string, local bool) string {
execer := "user." + types2.WasmX + "." + name
if local {
cfg := w.GetAPI().GetConfig()
execer = cfg.ExecName(execer)
}
return execer
}
func (w *Wasm) checkTxExec(txExec string, execName string) bool {
cfg := w.GetAPI().GetConfig()
return txExec == cfg.ExecName(execName)
}
func (w *Wasm) Exec_Create(payload *types2.WasmCreate, tx *types.Transaction, index int) (*types.Receipt, error) {
if payload == nil {
return nil, types.ErrInvalidParam
}
if !w.checkTxExec(string(tx.Execer), types2.WasmX) {
return nil, types.ErrExecNameNotMatch
}
name := payload.Name
if !validateName(name) {
return nil, types2.ErrInvalidContractName
}
code := payload.Code
if len(code) > types2.MaxCodeSize {
return nil, types2.ErrCodeOversize
}
if err := validation.ValidateWasm(code); err != nil {
return nil, types2.ErrInvalidWasm
}
kvc := dapp.NewKVCreator(w.GetStateDB(), nil, nil)
_, err := kvc.GetNoPrefix(contractKey(name))
if err == nil {
return nil, types2.ErrContractExist
}
if err != types.ErrNotFound {
return nil, err
}
kvc.AddNoPrefix(contractKey(name), code)
receiptLog := &types.ReceiptLog{
Ty: types2.TyLogWasmCreate,
Log: types.Encode(&types2.CreateContractLog{
Name: name,
Code: hex.EncodeToString(code),
}),
}
return &types.Receipt{
Ty: types.ExecOk,
KV: kvc.KVList(),
Logs: []*types.ReceiptLog{receiptLog},
}, nil
}
func (w *Wasm) Exec_Call(payload *types2.WasmCall, tx *types.Transaction, index int) (*types.Receipt, error) {
log.Info("into wasm Exec_Call...")
if payload == nil {
return nil, types.ErrInvalidParam
}
if !w.checkTxExec(string(tx.Execer), types2.WasmX) {
return nil, types.ErrExecNameNotMatch
}
w.stateKVC = dapp.NewKVCreator(w.GetStateDB(), calcStatePrefix(payload.Contract), nil)
code, err := w.stateKVC.GetNoPrefix(contractKey(payload.Contract))
if err != nil {
return nil, err
}
vm, err := exec.NewVirtualMachine(code, exec.VMConfig{
DefaultMemoryPages: 128,
DefaultTableSize: 128,
DisableFloatingPoint: true,
GasLimit: uint64(tx.Fee),
}, new(Resolver), &compiler.SimpleGasPolicy{GasPerInstruction: 1})
if err != nil {
return nil, err
}
// Get the function ID of the entry function to be executed.
entryID, ok := vm.GetFunctionExport(payload.Method)
if !ok {
return nil, types2.ErrInvalidMethod
}
w.contractName = payload.Contract
w.tx = tx
w.execAddr = address.ExecAddress(string(types.GetRealExecName(tx.Execer)))
wasmCB = w
defer func() {
wasmCB = nil
}()
// Run the WebAssembly module's entry function.
ret, err := vm.RunWithGasLimit(entryID, int(tx.Fee), payload.Parameters...)
if err != nil {
return nil, err
}
var kvs []*types.KeyValue
kvs = append(kvs, w.kvs...)
kvs = append(kvs, w.stateKVC.KVList()...)
var logs []*types.ReceiptLog
logs = append(logs, &types.ReceiptLog{Ty: types2.TyLogWasmCall, Log: types.Encode(&types2.CallContractLog{
Contract: payload.Contract,
Method: payload.Method,
Result: ret,
})})
logs = append(logs, w.receiptLogs...)
logs = append(logs, &types.ReceiptLog{Ty: types2.TyLogCustom, Log: types.Encode(&types2.CustomLog{
Info: w.customLogs,
})})
for _, log := range w.localCache {
logs = append(logs, &types.ReceiptLog{
Ty: types2.TyLogLocalData,
Log: types.Encode(log),
})
}
receipt := &types.Receipt{
Ty: types.ExecOk,
KV: kvs,
Logs: logs,
}
if ret < 0 {
receipt.Ty = types.ExecPack
}
return receipt, nil
}
func validateName(name string) bool {
if !types2.NameReg.MatchString(name) || len(name) < 4 || len(name) > 20 {
return false
}
return true
}
package executor
import (
"github.com/33cn/chain33/types"
types2 "github.com/33cn/plugin/plugin/dapp/wasm/types"
)
func (w *Wasm) ExecDelLocal_Create(payload *types2.WasmCreate, tx *types.Transaction, receipt *types.ReceiptData, index int) (*types.LocalDBSet, error) {
return &types.LocalDBSet{}, nil
}
func (w *Wasm) ExecDelLocal_Call(payload *types2.WasmCall, tx *types.Transaction, receipt *types.ReceiptData, index int) (*types.LocalDBSet, error) {
localExecer := w.userExecName(payload.Contract, true)
kvs, err := w.DelRollbackKV(tx, []byte(localExecer))
if err != nil {
return nil, err
}
return &types.LocalDBSet{KV: kvs}, nil
}
package executor
import (
"github.com/33cn/chain33/types"
types2 "github.com/33cn/plugin/plugin/dapp/wasm/types"
)
func (w *Wasm) ExecLocal_Create(payload *types2.WasmCreate, tx *types.Transaction, receipt *types.ReceiptData, index int) (*types.LocalDBSet, error) {
return &types.LocalDBSet{}, nil
}
func (w *Wasm) ExecLocal_Call(payload *types2.WasmCall, tx *types.Transaction, receipt *types.ReceiptData, index int) (*types.LocalDBSet, error) {
if receipt.Ty != types.ExecOk {
return &types.LocalDBSet{}, nil
}
localExecer := w.userExecName(payload.Contract, true)
var KVs []*types.KeyValue
for _, item := range receipt.Logs {
if item.Ty == types2.TyLogLocalData {
var data types2.LocalDataLog
err := types.Decode(item.Log, &data)
if err != nil {
return nil, err
}
KVs = append(KVs, &types.KeyValue{
Key: data.Key,
Value: data.Value,
})
}
}
return &types.LocalDBSet{KV: w.AddRollbackKV(tx, []byte(localExecer), KVs)}, nil
}
package executor
import (
"github.com/33cn/chain33/types"
types2 "github.com/33cn/plugin/plugin/dapp/wasm/types"
)
func (w *Wasm) Query_Check(query *types2.QueryCheckContract) (types.Message, error) {
if query == nil {
return nil, types.ErrInvalidParam
}
return &types.Reply{IsOk: w.contractExist(query.Name)}, nil
}
This diff is collapsed.
package executor
import (
"github.com/33cn/chain33/common/log/log15"
"github.com/33cn/chain33/system/dapp"
drivers "github.com/33cn/chain33/system/dapp"
"github.com/33cn/chain33/types"
types2 "github.com/33cn/plugin/plugin/dapp/wasm/types"
)
var driverName = types2.WasmX
var log = log15.New("module", "execs."+types2.WasmX)
func Init(name string, cfg *types.Chain33Config, sub []byte) {
if name != driverName {
panic("system dapp can not be rename")
}
drivers.Register(cfg, name, newWasm, cfg.GetDappFork(name, "Enable"))
initExecType()
}
func initExecType() {
ety := types.LoadExecutorType(driverName)
ety.InitFuncList(types.ListMethod(&Wasm{}))
}
type Wasm struct {
drivers.DriverBase
tx *types.Transaction
stateKVC *dapp.KVCreator
localCache []*types2.LocalDataLog
kvs []*types.KeyValue
receiptLogs []*types.ReceiptLog
customLogs []string
execAddr string
contractName string
}
func newWasm() drivers.Driver {
d := &Wasm{}
d.SetChild(d)
d.SetExecutorType(types.LoadExecutorType(driverName))
return d
}
// GetName 获取执行器别名
func GetName() string {
return newWasm().GetName()
}
func (w *Wasm) GetDriverName() string {
return driverName
}
This diff is collapsed.
package wasm
import (
"github.com/33cn/chain33/pluginmgr"
"github.com/33cn/plugin/plugin/dapp/wasm/commands"
"github.com/33cn/plugin/plugin/dapp/wasm/executor"
"github.com/33cn/plugin/plugin/dapp/wasm/rpc"
"github.com/33cn/plugin/plugin/dapp/wasm/types"
)
func init() {
pluginmgr.Register(&pluginmgr.PluginBase{
Name: types.WasmX,
ExecName: executor.GetName(),
Exec: executor.Init,
Cmd: commands.Cmd,
RPC: rpc.Init,
})
}
all:
sh ./create_protobuf.sh
#!/bin/sh
chain33_path=$(go list -f '{{.Dir}}' "github.com/33cn/chain33")
protoc --go_out=plugins=grpc:../types ./*.proto --proto_path=. --proto_path="${chain33_path}/types/proto/"
syntax = "proto3";
package types;
message wasmAction {
oneof value {
wasmCreate create = 1;
wasmCall call = 2;
}
int32 ty = 3;
}
message wasmCreate {
string name = 1;
bytes code = 2;
}
message wasmCall {
string contract = 1;
string method = 2;
repeated int64 parameters = 3;
}
message queryCheckContract {
string name = 1;
}
message customLog {
repeated string info = 1;
}
message createContractLog {
string name = 1;
string code = 2;
}
message callContractLog {
string contract = 1;
string method = 2;
int64 result = 3;
}
message localDataLog {
bytes key = 1;
bytes value = 2;
}
package rpc
import (
"github.com/33cn/chain33/common"
"github.com/33cn/chain33/types"
types2 "github.com/33cn/plugin/plugin/dapp/wasm/types"
)
func (c *channelClient) check(in *types2.QueryCheckContract) (*types.Reply, error) {
if in == nil {
return nil, types2.ErrInvalidParam
}
m, err := c.Query(types2.WasmX, "Check", in)
if err != nil {
return nil, err
}
if reply, ok := m.(*types.Reply); ok {
return reply, nil
}
return nil, types2.ErrUnknown
}
func (j *Jrpc) CheckContract(param *types2.QueryCheckContract, result *interface{}) error {
res, err := j.cli.check(param)
if err != nil {
return err
}
if res != nil {
*result = res.IsOk
} else {
*result = false
}
return nil
}
func (j *Jrpc) CreateContract(param *types2.WasmCreate, result *interface{}) error {
if param == nil {
return types2.ErrInvalidParam
}
cfg := types.LoadExecutorType(types2.WasmX).GetConfig()
data, err := types.CallCreateTx(cfg, cfg.ExecName(types2.WasmX), "Create", param)
if err != nil {
return err
}
*result = common.ToHex(data)
return nil
}
func (j *Jrpc) CallContract(param *types2.WasmCall, result *interface{}) error {
if param == nil {
return types2.ErrInvalidParam
}
cfg := types.LoadExecutorType(types2.WasmX).GetConfig()
data, err := types.CallCreateTx(cfg, cfg.ExecName(types2.WasmX), "Call", param)
if err != nil {
return err
}
*result = common.ToHex(data)
return nil
}
package rpc
import (
"io/ioutil"
"strings"
"testing"
"github.com/33cn/chain33/client/mocks"
rpctypes "github.com/33cn/chain33/rpc/types"
"github.com/33cn/chain33/types"
types2 "github.com/33cn/plugin/plugin/dapp/wasm/types"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
)
var cfg *types.Chain33Config
func init() {
cfg = types.NewChain33Config(strings.Replace(types.GetDefaultCfgstring(), "Title=\"local\"", "Title=\"chain33\"", 1))
}
func TestJrpc_CheckContract(t *testing.T) {
api := new(mocks.QueueProtocolAPI)
api.On("Query", types2.WasmX, "Check", mock.Anything).Return(&types.Reply{}, nil)
jrpc := &Jrpc{
cli: &channelClient{
rpctypes.ChannelClient{
QueueProtocolAPI: api,
},
},
}
var result interface{}
err := jrpc.CheckContract(&types2.QueryCheckContract{Name: "dice"}, &result)
assert.Nil(t, err, "CheckContract error not nil")
assert.Equal(t, false, result.(bool))
}
func TestJrpc_CreateContract(t *testing.T) {
jrpc := &Jrpc{}
code, err := ioutil.ReadFile("../contracts/dice/dice.wasm")
assert.Nil(t, err, "read wasm file error")
var result interface{}
err = jrpc.CreateContract(&types2.WasmCreate{Name: "dice", Code: code}, &result)
assert.Nil(t, err, "create contract error")
t.Log(result)
}
func TestJrpc_CallContract(t *testing.T) {
jrpc := &Jrpc{}
var result interface{}
err := jrpc.CallContract(&types2.WasmCall{Contract: "dice", Method: "play"}, &result)
assert.Nil(t, err, "call contract error")
t.Log(result)
}
// Copyright Fuzamei Corp. 2018 All Rights Reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package rpc
import (
"github.com/33cn/chain33/rpc/types"
)
// Jrpc json rpc struct
type Jrpc struct {
cli *channelClient
}
// Grpc grpc struct
type Grpc struct {
*channelClient
}
type channelClient struct {
types.ChannelClient
}
// Init init grpc param
func Init(name string, s types.RPCServer) {
cli := &channelClient{}
grpc := &Grpc{channelClient: cli}
cli.Init(name, s, &Jrpc{cli: cli}, grpc)
}
package types
import "errors"
var (
ErrContractExist = errors.New("contract exist")
ErrInvalidWasm = errors.New("invalid wasm code")
ErrCodeOversize = errors.New("code oversize")
ErrInvalidMethod = errors.New("invalid method")
ErrInvalidContractName = errors.New("invalid contract name")
ErrInvalidParam = errors.New("invalid parameters")
ErrUnknown = errors.New("unknown error")
)
package types
import (
"reflect"
"regexp"
"github.com/33cn/chain33/types"
)
var NameReg *regexp.Regexp
const (
WasmX = "wasm"
NameRegExp = "^[a-z0-9]+$"
//TODO: max size to define
MaxCodeSize = 1 << 20
)
// action for executor
const (
WasmActionCreate = iota + 1
WasmActionCall
)
// log ty for executor
const (
TyLogWasmCreate = iota + 100
TyLogWasmCall
TyLogCustom
TyLogLocalData
)
func init() {
types.AllowUserExec = append(types.AllowUserExec, []byte(WasmX))
types.RegFork(WasmX, InitFork)
types.RegExec(WasmX, InitExecutor)
NameReg, _ = regexp.Compile(NameRegExp)
}
func InitFork(cfg *types.Chain33Config) {
cfg.RegisterDappFork(WasmX, "Enable", 0)
}
func InitExecutor(cfg *types.Chain33Config) {
types.RegistorExecutor(WasmX, NewType(cfg))
}
type WasmType struct {
types.ExecTypeBase
}
func NewType(cfg *types.Chain33Config) *WasmType {
c := &WasmType{}
c.SetChild(c)
c.SetConfig(cfg)
return c
}
func (t *WasmType) GetPayload() types.Message {
return &WasmAction{}
}
func (t *WasmType) GetTypeMap() map[string]int32 {
return map[string]int32{
"Create": WasmActionCreate,
"Call": WasmActionCall,
}
}
func (t *WasmType) GetLogMap() map[int64]*types.LogInfo {
return map[int64]*types.LogInfo{
TyLogWasmCreate: {Ty: reflect.TypeOf(CreateContractLog{}), Name: "LogWasmCreate"},
TyLogWasmCall: {Ty: reflect.TypeOf(CallContractLog{}), Name: "LogWasmCall"},
TyLogCustom: {Ty: reflect.TypeOf(CustomLog{}), Name: "LogWasmCustom"},
TyLogLocalData: {Ty: reflect.TypeOf(LocalDataLog{}), Name: "LogWasmLocalData"},
}
}
This diff is collapsed.
...@@ -14,9 +14,10 @@ NOC='\033[0m' ...@@ -14,9 +14,10 @@ NOC='\033[0m'
# 出错退出前拷贝日志文件 # 出错退出前拷贝日志文件
function exit_cp_file() { function exit_cp_file() {
set -x
# shellcheck disable=SC2116 # shellcheck disable=SC2116
# dirNameFa=$(echo ~) dirNameFa=$(echo ~)
dirName="/x2ethereumlogs" dirName="${dirNameFa}/x2ethereumlogs"
if [ ! -d "${dirName}" ]; then if [ ! -d "${dirName}" ]; then
# shellcheck disable=SC2086 # shellcheck disable=SC2086
...@@ -26,18 +27,22 @@ function exit_cp_file() { ...@@ -26,18 +27,22 @@ function exit_cp_file() {
for name in a b c d; do for name in a b c d; do
# shellcheck disable=SC2154 # shellcheck disable=SC2154
docker cp "${dockerNamePrefix}_ebrelayer${name}_1":/root/logs/x2Ethereum_relayer.log "${dirName}/ebrelayer${name}.log" docker cp "${dockerNamePrefix}_ebrelayer${name}_1":/root/logs/x2Ethereum_relayer.log "${dirName}/ebrelayer${name}.log"
docker exec "${dockerNamePrefix}_ebrelayer${name}_1" tail -n 1000 /root/logs/x2Ethereum_relayer.log
done done
docker cp "${dockerNamePrefix}_chain33_1":/root/logs/chain33.log "${dirName}/chain33.log" docker cp "${dockerNamePrefix}_chain33_1":/root/logs/chain33.log "${dirName}/chain33.log"
docker logs "${dockerNamePrefix}_chain33_1" | tail -n 1000
exit 1 exit 1
} }
function copyErrLogs() { function copyErrLogs() {
set -x
if [ -n "$CASE_ERR" ]; then if [ -n "$CASE_ERR" ]; then
# /var/lib/jenkins # /var/lib/jenkins
# shellcheck disable=SC2116 # shellcheck disable=SC2116
# dirNameFa=$(echo ~) dirNameFa=$(echo ~)
dirName="/x2ethereumlogs" dirName="${dirNameFa}/x2ethereumlogs"
if [ ! -d "${dirName}" ]; then if [ ! -d "${dirName}" ]; then
# shellcheck disable=SC2086 # shellcheck disable=SC2086
...@@ -47,8 +52,10 @@ function copyErrLogs() { ...@@ -47,8 +52,10 @@ function copyErrLogs() {
for name in a b c d; do for name in a b c d; do
# shellcheck disable=SC2154 # shellcheck disable=SC2154
docker cp "${dockerNamePrefix}_ebrelayer${name}_rpc_1":/root/logs/x2Ethereum_relayer.log "${dirName}/ebrelayer${name}_rpc.log" docker cp "${dockerNamePrefix}_ebrelayer${name}_rpc_1":/root/logs/x2Ethereum_relayer.log "${dirName}/ebrelayer${name}_rpc.log"
docker exec "${dockerNamePrefix}_ebrelayer${name}_rpc_1" tail -n 1000 /root/logs/x2Ethereum_relayer.log
done done
docker cp "${dockerNamePrefix}_chain33_1":/root/logs/chain33.log "${dirName}/chain33_rpc.log" docker cp "${dockerNamePrefix}_chain33_1":/root/logs/chain33.log "${dirName}/chain33_rpc.log"
docker logs "${dockerNamePrefix}_chain33_1" | tail -n 1000
fi fi
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment