一、前言
1.1 项目版本
1. cosmwasm:orgin/main
2. wasmd:origin/master
1.2 简单介绍
1. cosmwasm主要功能:
- cosmwasm-template:提供编写智能合约(以下简称合约)的模板
- cosmwasm-examples:提供合约样例
- cosmwasm-storage:存储合约
- cosmwasm-vm:使用wasmer引擎执行给定的智能合约,还包含合约计费、存储和缓存wasm组件的功能
- go-cosmwasm:现已改名为wasmvm,调用cosmwasm-vm中的部署、实例化和执行合约的接口,使用它的优化和缓存
2. wasmd主要功能:
- 基于cosmos-sdk写的app,集群智能合约,导入了wasmvm
先从简单的说起
二、wasmd
wasmd主要功能如图所示
图中的irita网络是基于cosmos-sdk写的区块链网络,导入了wasmd模块,所以irita网络对于智能合约相关逻辑是与wasmd一致的,关于irita这里不做过多介绍。
0. 编译:根据cosmwasm-template,参考cosmwasm-examples,使用rust编写合约,然后用cli命令编译成wasm文件。也可以用go来编写,
但是暂时没有找到操作区块链存储、世界状态的接口样例。
1. 部署:将wasm原始字节,经base64标准编码后作为参数传给wasmd,或者使用gzip算法压缩原始字节后base64编码,wasmd会将合约存储并返回
合约编号。若编译好的Wasm字节码文件比较大,则部署到链上需要的存储空间会比较多,费用也会比较高,但是可以使用ontio-Wasm-build工具将 Wasm 字节码减小。
2. 初始化:也就是实例化,将合约编号作为参数传给wasmd,wasmd会根据合约编号生成合约账号,然后将合约账号、合约字节内容经base64编码返回,
可自行验证本地编译的代码是否与上传的代码散列相匹配。
3. 执行:将合约账号作为参数传给wasmd,wasmd调用wasmvm提供的接口执行合约(其实初始化也会调用),返回执行后的数据
4. 升降级:首先新合约按照步骤1执行,然后将旧合约账号与新合约编号作为参数传给wasmd,wasmd会重新建立合约账号与合约的绑定,也就是代理合约,
返回新合约字节内容。
三、cosmwasm
3.1 合约计费规则
-
cosmwasm定义了合约在加密验签的gas消耗,如代码所示
定位代码:cosmwasm/packages/vm/src/environment.rs -> GasConfig:
impl GasConfig {
// 底层加密验签消耗gas:1000(cosmos-sdk定义的)* 100(cosmwasm定义的)
const BASE_CRYPTO_COST: u64 = 100_000;
// secp256k1 算法验签消耗gas的因子
const SECP256K1_VERIFY_FACTOR: (u64, u64) = (154, 154); // ~154 us in crypto benchmarks
// secp256k1 算法恢复公钥消耗gas的因子
const SECP256K1_RECOVER_PUBKEY_FACTOR: (u64, u64) = (162, 154); // 162 us / 154 us ~ 1.05
// ed25519 算法验签消耗gas的因子
const ED25519_VERIFY_FACTOR: (u64, u64) = (63, 154); // 63 us / 154 us ~ 0.41
// ed25519 算法批量验签消耗gas的因子
const ED255219_BATCH_VERIFY_FACTOR: (u64, u64) = (
GasConfig::ED25519_VERIFY_FACTOR.0,
GasConfig::ED25519_VERIFY_FACTOR.1 * 2,
); // 0.41 / 2. ~ 0.21
// ed25519 算法单公钥验签消耗gas的因子
const ED255219_BATCH_VERIFY_ONE_PUBKEY_FACTOR: (u64, u64) = (
GasConfig::ED25519_VERIFY_FACTOR.0,
GasConfig::ED25519_VERIFY_FACTOR.1 * 4,
); // 0.41 / 4. ~ 0.1
// 计算加密验签消耗的gas
fn calc_crypto_cost(factor: (u64, u64)) -> u64 {
(GasConfig::BASE_CRYPTO_COST * factor.0) / factor.1
}
}
-
cosmwasm对于存储的读、写、修改、删除的gas的计费规则由gas_limit控制,如代码所示,但是对加、乘算法等操作没有定义。
这里只展示部分代码,定位代码:cosmwasm/packages/vm/src/environment.rs -> Environment
pub fn new(api: A, gas_limit: u64, print_debug: bool) -> Self {
Environment {
api,
print_debug,
gas_config: GasConfig::default(),
data: Arc::new(RwLock::new(ContextData::new(gas_limit))),
}
}
// 其他函数与此函数类似,就不贴代码了
// FnOnce匿名函数,可访问外部变量S、Q
fn with_context_data_mut<C, R>(&self, callback: C) -> R
where
C: FnOnce(&mut ContextData<S, Q>) -> R,
{
// 根据new函数:self.data的值来自gasLimit
let mut guard = self.data.as_ref().write().unwrap();
let context_data = guard.borrow_mut();
callback(context_data)
}
以太坊对于gas的消耗就非常详细,可参考下图,具体见以太坊黄皮书第20页,
并且以太坊对每个操作指令都有明文规定的Gas消耗量,可参考下图,具体见Gas清单 ,
当然以太坊gas消耗是可以优化的,具体见此处。
-
执行智能合约时 gasUsed 无法提前预知, 这样存在一个风险,当用户的交易涉及一个恶意的智能合约,该合约执行将消耗无限的燃料, 这样会导致交易方的余额全部消耗(恶意的智能合约有可能是程序Bug,如合约执行陷入一个死循环)。为了避免合约中的错误引起不可预计的燃料消耗,用户需要在发送交易时设定允许消耗的燃料上限,即 gasLimit。 这样不管合约是否良好,最坏情况也只是消耗 gasLimit 量的燃料。
-
如果交易尚未执行完成,而gas已用完,那么交易回滚,用完的gas不会返还。
-
即使交易失败,也必须为已占用的计算资源支付手续费。
3.2 合约与世界状态交互
先来看世界状态的定义:所有节点从同一个创世状态开始,依次运行达成共识的区块内的交易,驱动各个节点的状态按照相同操作序列(增加,删除,
修改)不断变化,实现所有节点在执行完相同编号区块内的交易后,状态完全一致,把这个状态称之为世界状态。
合约与世界状态的交互实际上是合约调用底层区块链的读写接口,各节点执行相同的合约从而使得数据达成一致。
只有在每个交易和区块处理过后,并且每个节点达到相同状态,智能合约才能正常运行。
来看合约对存储的操作,如代码(cosmwasm/packages/std/src/imports.rs)所示,此接口通过C/C++编写的动态库操作存储、验签,此接口将在vm中用到,见 cosmwasm/packages/vm/src/instance.rs -> Instance::from_module() 所示代码
extern "C" {
// 增删改接口
fn db_read(key: u32) -> u32;
fn db_write(key: u32, value: u32);
fn db_remove(key: u32);
// scan creates an iterator, which can be read by consecutive next() calls
// 迭代数据接口
#[cfg(feature = "iterator")]
fn db_scan(start_ptr: u32, end_ptr: u32, order: i32) -> u32;
#[cfg(feature = "iterator")]
fn db_next(iterator_id: u32) -> u32;
// 可视化账号
fn canonicalize_address(source_ptr: u32, destination_ptr: u32) -> u32;
fn humanize_address(source_ptr: u32, destination_ptr: u32) -> u32;
// 算法验证
fn secp256k1_verify(message_hash_ptr: u32, signature_ptr: u32, public_key_ptr: u32) -> u32;
fn secp256k1_recover_pubkey(
message_hash_ptr: u32,
signature_ptr: u32,
recovery_param: u32,
) -> u64;
fn ed25519_verify(message_ptr: u32, signature_ptr: u32, public_key_ptr: u32) -> u32;
fn ed25519_batch_verify(messages_ptr: u32, signatures_ptr: u32, public_keys_ptr: u32) -> u32;
fn debug(source_ptr: u32);
// 查询链上数据
/// Executes a query on the chain (import). Not to be confused with the
/// query export, which queries the state of the contract.
fn query_chain(request: u32) -> u32;
}
Fabric中智能合约与账本中世界状态进行交互的方法:利用PutState和GetState这两个API来实现的,见下图。