# 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); } ```