概述
一个EOSIO智能合约由一组活动(action)和类型定义组成。活动定义和实现了合约的行为方式,类型定义了所需的内容和结构。EOSIO的活动操作模式是基于消息的通信架构。我们可以通过cleos客户端通过发消息给nodeos或者使用EOSIO“send”方法(也就是“eosio::action::send”)来发起一个活动。活动的代码将整体执行,并将结果传递给下一个活动。
活动与事务交易
活动代表着一个单独的操作,而事务交易是一系列的单独或组合的活动。账号通过活动与合约进行交互。活动可以被单独发送或者以组合的方式发送(如果它们被设计为整体执行的话)。带有一个活动的事务如下所示。
{
"expiration": "2018-04-01T15:20:44",
"region": 0,
"ref_block_num": 42580,
"ref_block_prefix": 3987474256,
"net_usage_words": 21,
"kcpu_usage": 1000,
"delay_sec": 0,
"context_free_actions": [],
"actions": [{
"account": "eosio.token",
"name": "issue",
"authorization": [{
"actor": "eosio",
"permission": "active"
}
],
"data": "00000000007015d640420f000000000004454f5300000000046d656d6f"
}
],
"signatures": [
""
],
"context_free_data": []
}
带有多活动的事务,这些活动在全都执行成功的前提下事务才算执行成功。
{
"expiration": "...",
"region": 0,
"ref_block_num": ...,
"ref_block_prefix": ...,
"net_usage_words": ..,
"kcpu_usage": ..,
"delay_sec": 0,
"context_free_actions": [],
"actions": [{
"account": "...",
"name": "...",
"authorization": [{
"actor": "...",
"permission": "..."
}
],
"data": "..."
}, {
"account": "...",
"name": "...",
"authorization": [{
"actor": "...",
"permission": "..."
}
],
"data": "..."
}
],
"signatures": [
""
],
"context_free_data": []
}
事务确认
事务完成时将产生事务回执。收到事务的散列值仅仅意味着节点成功的接收到了事务信息,并且其他的产生者将大概率的接受它(但并不意味着该事务已经被确认)。当事务被确认时,我们将会在回执中携带的区块编号所指示的区块信息中查到该交易的历史记录。
活动名称的约定
活动的类型为base32编码的64位的整型。这意味着活动名称被限定为字符"a-z"、"1-5“、以及小数点“.”作为起始的12个字符。如果存在第13个字符,那么它将限制为前16个字符(“a-p”以及小数点".")中的一个。
活动处理句柄以及活动“申请”上下文
智能合约提供了活动处理句柄用来处理请求的活动。当一个活动在执行时,该活动通过合约中的“apply”方法被申请调用;当活动在执行时,EOSIO在合约实例中创建了申请方法的上下文信息。下图暂时了活动“申请”的上下文中的关键要素。
从EOSIO区块链的全局来看,EOSIO网络中的每个节点都有任何一份合约及活动的拷贝,并且可以执行它们。有一部分节点执行合约,而其他的节点则提供事务区块的验证工作。对于合约运行而言,明确合约的身份以及运行环境中的上下文是非常重要的。活动上下文提供了相关鉴别信息,这些信息包括如图所示的接收者(receiver)、编码(code)和活动(action)。接收者是当前处理活动的账号;编码是审核该活动的账号;活动则携带当前运行活动的ID。
在事务中相互交换的活动,当事务失败时,所有的活动都必须被回滚。活动上下文中一个关键要素是当前事务数据,该数据由事务头部(事务中所有原始活动的矢量结构体)、一个可删节的上下文空余数据的集合(blobs矢量结构体),该信息在合约的代码中定义,以及索引指向blobs矢量结构体。
在处理一个活动之前,EOSIO为活动清理好工作空间内存(供活动实例的内存变量使用)。即便在同一个事务中的不同活动之间,活动的运行内存仅能被自己访问。一个执行活动修改的变量,无法在另一个活动的上下文中访问。在活动中传递状态的唯一方法就是将其持久存储,然后通过EOSIO数据库中进行检索(详见持久性API中的有关持续性服务的说明)。
一个活动可能存在的附带效果包括:
- 改变了EOSIO持续性存储中的状态;
- 发出当前事务的凭据通知;
- 在代码内对一个新接收者发出活动请求;
- 产生新的(延时执行)的一系列事务;
- 取消现存(正在执行)延迟事务(比如,取消已经交付的延迟事务请求)。
通信模型与任务流
EOSIO智能合约可以相互沟通,列如让另一份合约执行某些与完成当前事务相关的操作,或者触发当前事务范围之外的未来事务。
EOSIO支持两种通信模型:内联通信(inline)与延迟通信(deferred)。在当前事务中执行一个操作,既是一个内联通信的例子;而在事务中触发一个未来执行的操作,则是一个延时通信的例子。
合约之间的通信应该是异步执行的。资源限制算法用来解决因异步的通信导致的垃圾数据(spam)。
内联通信
内联通信,往往是作为执行活动中的一部分被调用的。内联活动使用同一个事务中的相同的作用域和授权,并且确保与(调用者处于)同一个事务中执行。在一个调用的事务中,内联通信有效的视做内嵌事务。通过调用内联活动,不管执行成功或失败,都不会对外部其他事务产生通知信息(即,内联通信仅仅在本事务上下文的作用域起作用)。
延迟通信
延迟通信是通过向延时事务发送通知的形式来工作的。延时活动,根据发布者的设定,最好在之后的某个时间点被调度执行。但延迟活动并非能确保一定被执行。
从创建事务(即创建延时事务的事务)的角度来看,它只能确定创建请求被提交成功还是提交失败(如果失败,则立即反馈失败)。延期事务拥有执行延时合约的权利。事务也可以取消一个延时事务。
(崩溃了~~~续写了一部分后,不知道是因为csdn还是chrome崩溃了,导致博文丢失....这心情....暂时没勇气和动力了....T_T)
(走过伤心地....继续下面的....)
调测智能合约
为方便进行合约的调测,我们需要搭建一个本地的nodeos节点。本地节点需是一个私网或一个测试网的一部分。
在第一次创建智能合约的时候,我们建议在私网的测试节点中进行测试,这样我们就可以对整个区块链拥有完全的控制。这样在测试过程中的手续费将不受限,而且我们可以随意重启节点来重置区块链状态。当完成测试准备发布时,我们可以通过将nodeos切换到主网上实现主网的调测。测试过程中,我们可以在本地的nodeos中查阅到运行日志。
方法(Method)
在调测智能合约中主要的方法是Caveman调测法,就是使用print语句来检查变量值以及合约的工作流。在智能合约中打印是通过执行Print API来完成。C++API是C API的一种包装,在大多数情况下我们直接使用C++的API。
Print API
Print C API支持以下不同数据类型的打印:
- prints:使用null结尾的字符串
- prints_l:给定长度的字符串数组
- printi:64位无符号整型
- printi128:128位无符号整型
- printd:双精度浮点
- printn:64位无符号整型编码下的base32字符串
- printhex:指定数据和大小的十六进制
Print C++API通过重载print()函数封装了C API,这样封装了一些数据特殊类型细节,减少这些数据在print使用上的一些顾虑。C++ API支持:
字符串:使用null结尾的字符串
整型:32位、64位、128位带符号或无符号整型
使用64位无符号整型封装的base32格式的字符串
携带print方法的数据结构体
还有一个print_f函数可用来调测,使用带有%符号的格式字符串可以将参数填入合适的位置中。
auto count = 5;
auto some_name = "this is the name";
print_f("Count = % and some_name = %\n", count, some_name);
示例
我们编写一个新的叫做“debug”的合约作为调测的示例。
$ eosiocpp -n debug
$ cd debug
debug.cpp
修改已创建的debug.cpp文件如下所示。
#include <eosiolib/eosio.hpp>
class debug : public eosio::contract {
public:
using contract::contract;
// @abi action
void debugfunc( account_name from, account_name to, uint64_t amount ) {
auto header = "======== debugfunc ========";
eosio::print( header, "\n" );
eosio::print( "from = ", from, " to = ", to, " amount = ", amount, "\n" );
eosio::print( "from = ", eosio::name{from}, " to = ", eosio::name{to}, " amount = ", amount, "\n" );
}
};
EOSIO_ABI( debug, (debugfunc) )
debug.wast
使用以下命令生成debug.wast文件
$ eosiocpp -o debug.wast debug.cpp
debug.abi
使用以下命令生成debug.abi文件
$ eosiocpp -g debug.abi debug.cpp
部署并执行活动
现在我们可以部署并且向活动发起请求。这里假设我们已经创建了debug账号,并且已经将密钥纳入了钱包管理。
$ cleos set contract debug debug.wast debug.abi
$ cleos push action debug debugfunc '["fred", "barney", 200 ]' -p debug
我们可以在本地nodeos节点日志中查阅到上述活动被触发的日志。
======== debugfunc ========
from = 6761187270264356864 to = 4156599770598604800 amount = 200
from = fred to = barney amount = 200
使用这种技术,我们可以按自己期望的方式来进行调测。有时候在日志中会看到两次或更多次的消息,这是因为在验证、块生成和块应用不同阶段都会执行到这些事务。
摘自 Smart Contract,翻译 by Kevin.