# WASM 详细设计文档
## 模块组成
`WASM`(WebAssembly)是一种新的字节码格式,是一种全新的底层二进制语法,经编译器编译后的指令代码。
其体积小、可移植、加载快并兼容WEB的全新格式,是一种以安全有效的方式运行可移植程序的新技术。
`WASM`模块可以通过启动`WASM`虚拟机来执行`.wasm`字节码。`WASM`合约可以使用`Golang`, `Rust` 和 `C++` 语言编写, 通过编译工具最终编译成可供`WASM`虚拟机运行的`.wasm`字节码。
外部程序引用wasmvm的功能入口主要在 github.com/cosmwasm/wasmvm/lib.go 中的相关方法,主要方法有:NewVM,Create,Instantiate,Execute,Query 等涉及合约完整生命周期的操作。
## 详细流程设计
WASM的模块处理流程设计如下:
```mermaid
graph TD
start([客户端])--发送请求--> chain(区块链网络)
chain --解析消息--> router(路由器)
router --路由消息--> wasm(WASM模块)
wasm --Tx交易--> TX_Handler(TX Handler)
wasm --Query请求--> Query_Handler(Query Handler)
```
客户端发起交易会由链路由到`WASM`模块,`WASM`再根据交易类型来判断交给哪个处理器进行处理,如果是Tx交易的话会交由TX Handler进行处理,如果是查询交易的话会由Query Handler进行处理。
一笔合约交易的执行时序图如下:
```mermaid
sequenceDiagram
participant Client
participant Handler
participant MsgServer
participant Keeper
participant WasmerEngine
participant KVStore
Client->>Handler:Txs
Handler->>MsgServer:ExecuteContract
MsgServer->>Keeper:Execute
Keeper->>WasmerEngine:Execute
WasmerEngine->>WasmerEngine:Execute
WasmerEngine->>KVStore:Update
```
### 上传合约代码
```mermaid
graph TD
start([开始]) --> check(合约创建者不能为空且有创建合约的权限)
check --> work(解压缩合约代码<br>调用VM创建合约代码<br>分析合约代码格式<br>增加当前合约ID索引)
work --> store(创建合约信息并存储到数据库)
store --> returns(返回合约代码ID)
```
1. 用户发起上传编译好的合约代码交易
2. 检查交易:合约创建者不能为空且有创建合约的权限
3. 解压缩合约代码,调用VM创建合约代码,分析合约代码格式
4. 创建合约信息并存储到数据库
5. 返回合约代码ID
### 实例化合约
```mermaid
graph TD
start([开始]) --> createAddr(根据合约代码ID创建合约地址)
createAddr --> createAcc(根据合约地址创建合约账户并将其存到账户系统)
createAcc --> getCode(从数据库获取合约代码)
getCode --> params(使用示例化参数和合约代码封装成合约示例化交易)
params --> instantiate(使用VM处理示例化交易)
instantiate --> store(创建合约信息结构并存储到数据库)
store --> returns(返回合约地址和实例化合约的返回值)
```
1. 用户发起示例化合约的交易,参数包括合约代码ID,交易发起者,实例化合约的参数
2. 根据合约代码ID创建合约地址,并检查合约地址是否已经存在链上了,如果存在则报错
3. 根据合约地址创建合约账户并将其存到账户系统
4. 从数据库获取合约代码
5. 使用示例化参数和合约代码封装成合约示例化交易
6. 使用VM处理示例化交易
7. 创建合约信息结构并存储到数据库
8. 返回合约地址和实例化合约的返回值
### 调用合约函数
```mermaid
graph TD
start([开始]) --> getCodeInfo(根绝合约地址获取合约代码信息)
getCodeInfo --> params(封装合约执行参数<br>包括合约执行者,合约执行参数,合约地址,合约代码哈希)
params --> execute(调用VM执行合约)
execute --> returns(返回合约执行信息)
```
1. 用户发起执行合约的交易,参数包括合约地址和执行合约的参数
2. 根绝合约地址获取合约代码信息
3. 封装合约执行参数,包括合约执行者,合约执行参数,合约地址,合约代码哈希
4. 调用VM执行合约
5. 返回合约执行信息
### 合约查询
```mermaid
graph TD
start([开始]) --> params(根绝请求类型和参数拼接查询路径)
params --> returns(根绝查询路径查询链上合约信息)
```
1. 用户发起查询请求,查询请求参数
2. 根绝请求类型和参数拼接查询路径
3. 根绝查询路径查询链上合约信息
## 数据结构设计
### Keeper数据结构
`Keeper`用来管理数据的存储:
```golang=
// Keeper will have a reference to Wasmer with it's own data directory.
type Keeper struct {
storeKey sdk.StoreKey
cdc codec.BinaryCodec
accountKeeper types.AccountKeeper
//bank CoinTransferrer
bank types.BankKeeper
portKeeper types.PortKeeper
capabilityKeeper types.CapabilityKeeper
wasmVM types.WasmerEngine
wasmVMQueryHandler WasmVMQueryHandler
wasmVMResponseHandler WasmVMResponseHandler
messenger Messenger
// queryGasLimit is the max wasmvm gas that can be spent on executing a query with a contract
queryGasLimit uint64
paramSpace paramtypes.Subspace
}
```
### 消息数据结构
#### 上传合约代码消息
发送交易消息:
```go=
// MsgStoreCode submit Wasm code to the system
type MsgStoreCode struct {
// Sender is the that actor that signed the messages
Sender string `protobuf:"bytes,1,opt,name=sender,proto3" json:"sender,omitempty"`
// WASMByteCode can be raw or gzip compressed
WASMByteCode []byte `protobuf:"bytes,2,opt,name=wasm_byte_code,json=wasmByteCode,proto3" json:"wasm_byte_code,omitempty"`
// InstantiatePermission access control to apply on contract creation,
// optional
InstantiatePermission *AccessConfig `protobuf:"bytes,5,opt,name=instantiate_permission,json=instantiatePermission,proto3" json:"instantiate_permission,omitempty"`
}
```
交易返回:
```go=
// MsgStoreCodeResponse returns store result data.
type MsgStoreCodeResponse struct {
// CodeID is the reference to the stored WASM code
CodeID uint64 `protobuf:"varint,1,opt,name=code_id,json=codeId,proto3" json:"code_id,omitempty"`
}
}
```
#### 实例化链码消息
发送交易消息:
```go=
// MsgInstantiateContract create a new smart contract instance for the given
// code id.
type MsgInstantiateContract struct {
// Sender is the that actor that signed the messages
Sender string `protobuf:"bytes,1,opt,name=sender,proto3" json:"sender,omitempty"`
// Admin is an optional address that can execute migrations
Admin string `protobuf:"bytes,2,opt,name=admin,proto3" json:"admin,omitempty"`
// CodeID is the reference to the stored WASM code
CodeID uint64 `protobuf:"varint,3,opt,name=code_id,json=codeId,proto3" json:"code_id,omitempty"`
// Label is optional metadata to be stored with a contract instance.
Label string `protobuf:"bytes,4,opt,name=label,proto3" json:"label,omitempty"`
// Msg json encoded message to be passed to the contract on instantiation
Msg RawContractMessage `protobuf:"bytes,5,opt,name=msg,proto3,casttype=RawContractMessage" json:"msg,omitempty"`
// Funds coins that are transferred to the contract on instantiation
Funds github_com_loong_projects_erhai_core_types.Coins `protobuf:"bytes,6,rep,name=funds,proto3,castrepeated=github.com/loong-projects/erhai-core/types.Coins" json:"funds"`
}
```
交易返回:
```go=
// MsgInstantiateContractResponse return instantiation result data
type MsgInstantiateContractResponse struct {
// Address is the bech32 address of the new contract instance.
Address string `protobuf:"bytes,1,opt,name=address,proto3" json:"address,omitempty"`
// Data contains base64-encoded bytes to returned from the contract
Data []byte `protobuf:"bytes,2,opt,name=data,proto3" json:"data,omitempty"`
}
```
#### 执行合约消息
发送交易消息:
```go=
// MsgExecuteContract submits the given message data to a smart contract
type MsgExecuteContract struct {
// Sender is the that actor that signed the messages
Sender string `protobuf:"bytes,1,opt,name=sender,proto3" json:"sender,omitempty"`
// Contract is the address of the smart contract
Contract string `protobuf:"bytes,2,opt,name=contract,proto3" json:"contract,omitempty"`
// Msg json encoded message to be passed to the contract
Msg RawContractMessage `protobuf:"bytes,3,opt,name=msg,proto3,casttype=RawContractMessage" json:"msg,omitempty"`
// Funds coins that are transferred to the contract on execution
Funds github_com_loong_projects_erhai_core_types.Coins `protobuf:"bytes,5,rep,name=funds,proto3,castrepeated=github.com/loong-projects/erhai-core/types.Coins" json:"funds"`
}
```
消息返回:
```go=
// MsgExecuteContractResponse returns execution result data.
type MsgExecuteContractResponse struct {
// Data contains base64-encoded bytes to returned from the contract
Data []byte `protobuf:"bytes,1,opt,name=data,proto3" json:"data,omitempty"`
}
```
#### 查询合约信息请求
发送请求:
```go=
// QueryContractInfoRequest is the request type for the Query/ContractInfo RPC
// method
type QueryContractInfoRequest struct {
// address is the address of the contract to query
Address string `protobuf:"bytes,1,opt,name=address,proto3" json:"address,omitempty"`
}
```
请求返回:
```go=
// MsgExecuteContractResponse returns execution result data.
type MsgExecuteContractResponse struct {
// Data contains base64-encoded bytes to returned from the contract
Data []byte `protobuf:"bytes,1,opt,name=data,proto3" json:"data,omitempty"`
}
```
#### 查询合约历史请求
发送请求:
```go=
// QueryContractHistoryRequest is the request type for the Query/ContractHistory
// RPC method
type QueryContractHistoryRequest struct {
// address is the address of the contract to query
Address string `protobuf:"bytes,1,opt,name=address,proto3" json:"address,omitempty"`
// pagination defines an optional pagination for the request.
Pagination *query.PageRequest `protobuf:"bytes,2,opt,name=pagination,proto3" json:"pagination,omitempty"`
}
```
请求返回:
```go=
// QueryContractHistoryResponse is the response type for the
// Query/ContractHistory RPC method
type QueryContractHistoryResponse struct {
Entries []ContractCodeHistoryEntry `protobuf:"bytes,1,rep,name=entries,proto3" json:"entries"`
// pagination defines the pagination in the response.
Pagination *query.PageResponse `protobuf:"bytes,2,opt,name=pagination,proto3" json:"pagination,omitempty"`
}
```
#### 查询合约代码请求
发送请求:
```go=
// QueryContractsByCodeRequest is the request type for the Query/ContractsByCode
// RPC method
type QueryContractsByCodeRequest struct {
CodeId uint64 `protobuf:"varint,1,opt,name=code_id,json=codeId,proto3" json:"code_id,omitempty"`
// pagination defines an optional pagination for the request.
Pagination *query.PageRequest `protobuf:"bytes,2,opt,name=pagination,proto3" json:"pagination,omitempty"`
}
```
请求返回:
```go=
// QueryContractsByCodeResponse is the response type for the
// Query/ContractsByCode RPC method
type QueryContractsByCodeResponse struct {
// contracts are a set of contract addresses
Contracts []string `protobuf:"bytes,1,rep,name=contracts,proto3" json:"contracts,omitempty"`
// pagination defines the pagination in the response.
Pagination *query.PageResponse `protobuf:"bytes,2,opt,name=pagination,proto3" json:"pagination,omitempty"`
}
```
#### 查询所有合约状态请求
发送请求:
```go=
// QueryAllContractStateRequest is the request type for the
// Query/AllContractState RPC method
type QueryAllContractStateRequest struct {
// address is the address of the contract
Address string `protobuf:"bytes,1,opt,name=address,proto3" json:"address,omitempty"`
// pagination defines an optional pagination for the request.
Pagination *query.PageRequest `protobuf:"bytes,2,opt,name=pagination,proto3" json:"pagination,omitempty"`
}
```
请求返回:
```go=
// QueryAllContractStateResponse is the response type for the
// Query/AllContractState RPC method
type QueryAllContractStateResponse struct {
Models []Model `protobuf:"bytes,1,rep,name=models,proto3" json:"models"`
// pagination defines the pagination in the response.
Pagination *query.PageResponse `protobuf:"bytes,2,opt,name=pagination,proto3" json:"pagination,omitempty"`
}
```
#### 查询合约原始数据请求
发送请求:
```go=
// QueryRawContractStateRequest is the request type for the
// Query/RawContractState RPC method
type QueryRawContractStateRequest struct {
// address is the address of the contract
Address string `protobuf:"bytes,1,opt,name=address,proto3" json:"address,omitempty"`
QueryData []byte `protobuf:"bytes,2,opt,name=query_data,json=queryData,proto3" json:"query_data,omitempty"`
}
```
请求返回:
```go=
// QueryRawContractStateResponse is the response type for the
// Query/RawContractState RPC method
type QueryRawContractStateResponse struct {
// Data contains the raw store data
Data []byte `protobuf:"bytes,1,opt,name=data,proto3" json:"data,omitempty"`
}
```
#### 查询智能合约状态请求
发送请求:
```go=
// QuerySmartContractStateRequest is the request type for the
// Query/SmartContractState RPC method
type QuerySmartContractStateRequest struct {
// address is the address of the contract
Address string `protobuf:"bytes,1,opt,name=address,proto3" json:"address,omitempty"`
// QueryData contains the query data passed to the contract
QueryData RawContractMessage `protobuf:"bytes,2,opt,name=query_data,json=queryData,proto3,casttype=RawContractMessage" json:"query_data,omitempty"`
}
```
请求返回:
```go=
// QuerySmartContractStateResponse is the response type for the
// Query/SmartContractState RPC method
type QuerySmartContractStateResponse struct {
// Data contains the json data returned from the smart contract
Data RawContractMessage `protobuf:"bytes,1,opt,name=data,proto3,casttype=RawContractMessage" json:"data,omitempty"`
}
```
## 数据存储设计
### 存储数据类型
#### 代码信息
代码信息用来存储上传的合约代码的信息:
```go=
// CodeInfo is data for the uploaded contract WASM code
type CodeInfo struct {
// CodeHash is the unique identifier created by wasmvm
CodeHash []byte `protobuf:"bytes,1,opt,name=code_hash,json=codeHash,proto3" json:"code_hash,omitempty"`
// Creator address who initially stored the code
Creator string `protobuf:"bytes,2,opt,name=creator,proto3" json:"creator,omitempty"`
// InstantiateConfig access control to apply on contract creation, optional
InstantiateConfig AccessConfig `protobuf:"bytes,5,opt,name=instantiate_config,json=instantiateConfig,proto3" json:"instantiate_config"`
}
```
#### 合约信息
合约信息用来存储WASM合约实例:
```go=
// ContractInfo stores a WASM contract instance
type ContractInfo struct {
// CodeID is the reference to the stored Wasm code
CodeID uint64 `protobuf:"varint,1,opt,name=code_id,json=codeId,proto3" json:"code_id,omitempty"`
// Creator address who initially instantiated the contract
Creator string `protobuf:"bytes,2,opt,name=creator,proto3" json:"creator,omitempty"`
// Label is optional metadata to be stored with a contract instance.
Label string `protobuf:"bytes,3,opt,name=label,proto3" json:"label,omitempty"`
// Created Tx position when the contract was instantiated.
// This data should kept internal and not be exposed via query results. Just
// use for sorting
Created *AbsoluteTxPosition `protobuf:"bytes,4,opt,name=created,proto3" json:"created,omitempty"`
// Extension is an extension point to store custom metadata within the
// persistence model.
Extension *types.Any `protobuf:"bytes,5,opt,name=extension,proto3" json:"extension,omitempty"`
}
```
## 接口设计
### Keeper接口
`Keeper`是对外暴露的有关模块的操作接口:
```golang=
// decoratedKeeper contains a subset of the wasm keeper that are already or can be guarded by an authorization policy in the future
type decoratedKeeper interface {
create(ctx sdk.Context,
creator sdk.AccAddress,
wasmCode []byte,
instantiateAccess *types.AccessConfig,
authZ AuthorizationPolicy) (codeID uint64, err error)
instantiate(ctx sdk.Context, codeID uint64,
creator, admin sdk.AccAddress, initMsg []byte,
label string, deposit sdk.Coins,
authZ AuthorizationPolicy) (sdk.AccAddress, []byte, error)
execute(ctx sdk.Context, contractAddress sdk.AccAddress,
caller sdk.AccAddress, msg []byte, coins sdk.Coins) ([]byte, error)
setContractInfoExtension(ctx sdk.Context, contract
sdk.AccAddress, extra types.ContractInfoExtension) error
}
```
### 合约引擎接口
合约的执行引擎的接口如下:
```go=
// WasmerEngine defines the WASM contract runtime engine.
type WasmerEngine interface {
// Create will compile the wasm code, and store the resulting pre-compile
// as well as the original code. Both can be referenced later via CodeID
// This must be done one time for given code, after which it can be
// instatitated many times, and each instance called many times.
//
// For example, the code for all ERC-20 contracts should be the same.
// This function stores the code for that contract only once, but it can
// be instantiated with custom inputs in the future.
Create(code wasmvm.WasmCode) (wasmvm.Checksum, error)
// AnalyzeCode will statically analyze the code.
// Currently just reports if it exposes all IBC entry points.
AnalyzeCode(checksum wasmvm.Checksum) (*wasmvmtypes.AnalysisReport, error)
// Instantiate will create a new contract based on the given codeID.
// We can set the initMsg (contract "genesis") here, and it then receives
// an account and address and can be invoked (Execute) many times.
//
// Storage should be set with a PrefixedKVStore that this code can safely access.
//
// Under the hood, we may recompile the wasm, use a cached native compile, or even use a cached instance
// for performance.
Instantiate(
checksum wasmvm.Checksum,
env wasmvmtypes.Env,
info wasmvmtypes.MessageInfo,
initMsg []byte,
store wasmvm.KVStore,
goapi wasmvm.GoAPI,
querier wasmvm.Querier,
gasMeter wasmvm.GasMeter,
gasLimit uint64,
deserCost wasmvmtypes.UFraction,
) (*wasmvmtypes.Response, uint64, error)
// Execute calls a given contract. Since the only difference between contracts with the same CodeID is the
// data in their local storage, and their address in the outside world, we need no ContractID here.
// (That is a detail for the external, sdk-facing, side).
//
// The caller is responsible for passing the correct `store` (which must have been initialized exactly once),
// and setting the env with relevant info on this instance (address, balance, etc)
Execute(
code wasmvm.Checksum,
env wasmvmtypes.Env,
info wasmvmtypes.MessageInfo,
executeMsg []byte,
store wasmvm.KVStore,
goapi wasmvm.GoAPI,
querier wasmvm.Querier,
gasMeter wasmvm.GasMeter,
gasLimit uint64,
deserCost wasmvmtypes.UFraction,
) (*wasmvmtypes.Response, uint64, error)
// Query allows a client to execute a contract-specific query. If the result is not empty, it should be
// valid json-encoded data to return to the client.
// The meaning of path and data can be determined by the code. Path is the suffix of the abci.QueryRequest.Path
Query(
code wasmvm.Checksum,
env wasmvmtypes.Env,
queryMsg []byte,
store wasmvm.KVStore,
goapi wasmvm.GoAPI,
querier wasmvm.Querier,
gasMeter wasmvm.GasMeter,
gasLimit uint64,
deserCost wasmvmtypes.UFraction,
) ([]byte, uint64, error)
// GetCode will load the original wasm code for the given code id.
// This will only succeed if that code id was previously returned from
// a call to Create.
//
// This can be used so that the (short) code id (hash) is stored in the iavl tree
// and the larger binary blobs (wasm and pre-compiles) are all managed by the
// rust library
GetCode(code wasmvm.Checksum) (wasmvm.WasmCode, error)
```
`WasmerEngine`接口定义了完整的合约生命周期。
`WasmerEngine` 是WASM合约执行引擎,对上层模块`Keeper`提供接口,上层对合约的调用最终都会调用`WasmerEngine`对应接口进行操作。
由于`WASM VM`是由`Rust`代码编写的,`Go`无法直接调用`Rust`的代码,且虚拟机如果要使用`Go`中定义`KV Store`更新合约数据也需要调用`Go`中的代码。所以需要一个粘合剂来将两者连接起来,这个粘合剂就是`C`语言。
`Rust`编写的`WASM VM`会被编译成静态链接库文件,向外暴露虚拟机的接口实现,同时需要用`C`语言来定义这些接口,然后`Go`语言在调用虚拟机的接口时就可以直接调用`C`语言中定义的接口,它会自动链接到静态链接库文件中,从而实现了`Go`语言调用`Rust`语言实现的功能。
`C`中定义`Rust`中的接口如下:
```go=
// api.h
struct cache_t *init_cache(struct ByteSliceView data_dir,
struct ByteSliceView supported_features,
uint32_t cache_size,
uint32_t instance_memory_limit,
struct UnmanagedVector *error_msg);
struct UnmanagedVector save_wasm(struct cache_t *cache,
struct ByteSliceView wasm,
struct UnmanagedVector *error_msg);
struct UnmanagedVector instantiate(struct cache_t *cache,
struct ByteSliceView checksum,
struct ByteSliceView env,
struct ByteSliceView info,
struct ByteSliceView msg,
struct Db db,
struct GoApi api,
struct GoQuerier querier,
uint64_t gas_limit,
bool print_debug,
uint64_t *gas_used,
struct UnmanagedVector *error_msg);
struct UnmanagedVector execute(struct cache_t *cache,
struct ByteSliceView checksum,
struct ByteSliceView env,
struct ByteSliceView info,
struct ByteSliceView msg,
struct Db db,
struct GoApi api,
struct GoQuerier querier,
uint64_t gas_limit,
bool print_debug,
uint64_t *gas_used,
struct UnmanagedVector *error_msg);
struct AnalysisReport analyze_code(struct cache_t *cache,
struct ByteSliceView checksum,
struct UnmanagedVector *error_msg);
```
### 合约数据存储接口
虚拟机在执行合约的过程中需要更新数据库,数据库是在`Go`中定义的,所以`Rust`需要调用`Go`中的代码,和`Go`调用`Rust`的过程正好相反。用`C`语言编写调用`Go`中数据库的接口,同时`Rust`中将调用数据库的函数定义成一个外部`C`语言实现的函数,这样`Rust`在调用数据库读写函数时首先调用外部`C`语言定义的接口,改接口的实现实际上是在`Go`代码中,这就完成了`Rust`调用`Go`代码的功能。
`C`语言定义数据库读写的接口如下:
```go=
// Gateway functions (db)
GoResult cGet_cgo(db_t *ptr, gas_meter_t *gas_meter,
uint64_t *used_gas, U8SliceView key,
UnmanagedVector *val, UnmanagedVector *errOut) {
return cGet(ptr, gas_meter, used_gas, key, val, errOut);
}
GoResult cSet_cgo(db_t *ptr, gas_meter_t *gas_meter,
uint64_t *used_gas, U8SliceView key,
U8SliceView val, UnmanagedVector *errOut) {
return cSet(ptr, gas_meter, used_gas, key, val, errOut);
}
GoResult cDelete_cgo(db_t *ptr, gas_meter_t *gas_meter,
uint64_t *used_gas, U8SliceView key,
UnmanagedVector *errOut) {
return cDelete(ptr, gas_meter, used_gas, key, errOut);
}
GoResult cScan_cgo(db_t *ptr, gas_meter_t *gas_meter,
uint64_t *used_gas, U8SliceView start, U8SliceView end,
int32_t order, GoIter *out, UnmanagedVector *errOut) {
return cScan(ptr, gas_meter, used_gas, start, end, order, out, errOut);
}
```