cosmwasm&wasmd —— 智能合约、合约计费规则、合约与世界状态交互

一、前言

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主要功能如图所示

wasm智能合约生命周期

图中的irita网络是基于cosmos-sdk写的区块链网络,导入了wasmd模块,所以irita网络对于智能合约相关逻辑是与wasmd一致的,关于irita这里不做过多介绍。
图中0-4步骤都是以RESTful API的方式执行,使用时需遵循cosmos-sdk规范。

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 合约计费规则
  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
    }
}
  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清单
在这里插入图片描述
当然以太坊gas消耗是可以优化的,具体见此处

  1. 执行智能合约时 gasUsed 无法提前预知, 这样存在一个风险,当用户的交易涉及一个恶意的智能合约,该合约执行将消耗无限的燃料, 这样会导致交易方的余额全部消耗(恶意的智能合约有可能是程序Bug,如合约执行陷入一个死循环)。为了避免合约中的错误引起不可预计的燃料消耗,用户需要在发送交易时设定允许消耗的燃料上限,即 gasLimit。 这样不管合约是否良好,最坏情况也只是消耗 gasLimit 量的燃料。gasLimit只是一个gas使用限制,gas在合约执行过程中每步都计算并累积gas的使用,否则无法触发gasLimit限制,比如死循环计算加。那么vm虚拟机中是如何计算gas使用情况的呢?见代码:cosmwasm/packages/vm/src/instance.rs -> create_gas_report()
    /// This is a snapshot and multiple reports can be created during the lifetime of
    /// an instance.
    pub fn create_gas_report(&self) -> GasReport {
    	// 从state中返回gas使用记录.
        let state = self.env.with_gas_state(|gas_state| gas_state.clone());
        let gas_left = self.env.get_gas_left();
        GasReport {
            limit: state.gas_limit,
            remaining: gas_left,
            used_externally: state.externally_used_gas,
            // 这里触发gasLimit
            // If externally_used_gas exceeds the gas limit, this will return 0.
            // no matter how much gas was used internally. But then we error with out of gas
            // anyways, and it does not matter much anymore where gas was spend.
            used_internally: state
                .gas_limit
                .saturating_sub(state.externally_used_gas)
                .saturating_sub(gas_left),
        }
    }

这个函数将在合约的生命周期中调用,见代码:wasmvm/src/calls.rs -> do_call_3_args(),此代码最终会编译成so动态库供goAPI调用,因为Go无法直接调用rust,但是可以调用CGo

fn do_call_3_args(
    vm_fn: VmFn3Args, // 合约需要执行的函数
    cache: &mut Cache<GoApi, GoStorage, GoQuerier>,
    checksum: ByteSliceView,
    arg1: ByteSliceView,
    arg2: ByteSliceView,
    arg3: ByteSliceView,
    db: DB,		// state
    api: GoApi, // 兼容CGo
    querier: GoQuerier,
    gas_limit: u64,
    print_debug: bool,
    gas_used: Option<&mut u64>,
) -> Result<Vec<u8>, Error> {
    let gas_used = gas_used.ok_or_else(|| Error::empty_arg(GAS_USED_ARG))?;
   
    // ...... 此处省略部分代码
   
    // We only check this result after reporting gas usage and returning the instance into the cache.
    let res = vm_fn(&mut instance, arg1, arg2, arg3);
    *gas_used = instance.create_gas_report().used_internally;
    instance.recycle();
    Ok(res?)
}
  1. 如果交易尚未执行完成,而gas已用完,那么交易回滚,用完的gas不会返还。

  2. 即使交易失败,也必须为已占用的计算资源支付手续费。

3.2 合约与世界状态交互
先来看世界状态的定义:所有节点从同一个创世状态开始,依次运行达成共识的区块内的交易,驱动各个节点的状态按照相同操作序列(增加,删除,
修改)不断变化,实现所有节点在执行完相同编号区块内的交易后,状态完全一致,把这个状态称之为世界状态。

状态变化

 合约与世界状态的交互实际上是合约调用底层区块链的读写接口,各节点执行相同的合约从而使得数据达成一致。
 只有在每个交易和区块处理过后,并且每个节点达到相同状态,智能合约才能正常运行。

来看合约对存储,验签的操作,如代码(cosmwasm/packages/std/src/imports.rs)所示,extern "C"是为了编译成动态库与go进行交互,具体实现见 cosmwasm/packages/vm/src/imports.rs -> do_read(),此接口将在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来实现的,见下图。
账本中世界状态进行交互

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值