以太坊智能合约定义的全局变量的值是持久性的,就相当于智能合约一直在运行着。而EOS的智能合约更加接近我们平时使用的程序,每次执行action都相当于启动智能合约的一个新实例,一旦执行完,代码定义的变量就释放了,不会影响下一次执行环境。但是智能合约肯定需要有持久化存储的需求,比如永久保存智能合约代币的状态,不能代币转账执行完,代币的balance余额信息和转账前一样吧。这个持久化存储就是数据库存储数据。EOS允许智能合约定义自己的私有数据库表。比如下图,Apply Context的内容都是一次性的,一次action执行完成,对象就释放了,只有存储到EOSIO database的才被保存。
这个私有数据表是通过multi_index来访问和交互的。EOS的multi_index类似boost的multi_index,即多索引容器。有了多级索引,智能合约就具备了操作类似数据库模块的功能。eosio::multi_index支持主键,但是必须是唯一的无符号64位整数。eosio::multi_index中的对象容器按主键索引按无符号64位整数主键的升序排序。
定义一个multi_index对象即可
multi_index
> table( code, scope );
这里的code必须是contract的account_name, 因为这个数据表的操作api都有check,比如'modify' 函数.
void modify( const T& obj, uint64_t payer, Lambda&& updater ) {
eosio_assert( _code == current_receiver(), "cannot modify objects in table of another contract" ); // Quick fix for mutating db using multi_index that shouldn't allow mutation. Real fix can come in RC2.
}
scope参数,这个参数的初衷是用来实现交易action并行执行的。我们知道,action之间可能是存在依赖的,比如alice转给bob 5块钱(action1),alice转给james 5块钱(action2), action1和action2就有依赖关系。因为如果Alice只有6块钱,那么这两个action中必然有一个action是失败的。如果这两个action并行执行那这两个都会成功,明显是不合理的。你可能会说可以使用互斥锁解决冲突啊?事实上市不行的,申请互斥锁中的两段代码的执行顺序是随机的,所以导致action1和action2的执行顺序是随机的,这就违背了区块链的基本要求---确定性和可重复性,每个节点按相同顺序执行一个action列表,结果应该是一致的。所以为了解决这个问题就提出了scope的。假设这个balance的数据保存在‘balance’数据表里,scope为'balance_scope', 所以在提交action1时就会传入一个scope参数'balance_scope‘,这样系统根据action1和action的scope很容易知道action1, action2都会操作'balance_scope'对应的'balance'表,就不会让这两个action并行执行。其实,这里的核心问题是action操作的数据表的信息,如果提交的action不告知系统,系统必须要执行完action才能知道操作了哪个数据表,这自然没法实现并行。当然,目前版本scope基本没有起作用,action并行老版本有实现过的框架(但不起作用),最新的版本框架甚至已经删除了相关逻辑,看来action的并行化路漫慢而修远兮。
eosio.token合约
struct currency_stats {
asset supply;
asset max_supply;
ccount_name issuer;
//必须有该函数
uint64_t primary_key()const { return supply.symbol.name(); }
};
typedef eosio::multi_index<N(stat), currency_stats> stats;
纯数据表只能通过table.find函数以主键为参数查找数据
stats statstable( _self, sym.name() );
auto existing = statstable.find( sym.name() );
multi_index
indexed_by
, indexed_by
, ….>
> table( code, scope );
和纯数据表相比,就多了index_by索引定义
索引的定义格式是:indexed_by<索引名,索引键值函数>,比如这个实例:
eosio::multi_index
indexed_by< N(byexp), const_mem_fun
>, indexed_by< N(byprice), const_mem_fun
> > orders( N(multitest), N(multitest) );
'<N(orders), limit_order,'这部分和纯数据表是一样的,第一个参数是表名,第二个参数是表行对象。这里有两个索引对象。分别是
indexed_by< N(byexp), const_mem_fun
> indexed_by< N(byprice), const_mem_fun
>
N(byprice):索引名称是'byprice'
const_mem_fun这部分定义了一个索引键值函数,这个其实是通过boost::bind函数将对象的const成员函数转化为函数指针.其格式是
auto priceidx = orders.get_index
(); print("Items sorted by price:\n");
for( const auto& item : priceidx ) {
print(" ID=", item.id, ", price=", item.price, ", owner=", name{item.owner}, "\n");
}
$cleos get table cleos get table [OPTIONS] contract scope table
class tabletest : public eosio::contract {
private:
/// @abi table
struct contact {
account_name name;
uint64_t phone;
auto primary_key()const { return name; }
uint64_t get_phone()const { return phone; }
EOSLIB_SERIALIZE( contact, (name)(phone))
};
typedef eosio::multi_index
> > contacts;
public:
using contract::contract;
/// @abi action
void add(account_name owner, account_name name, uint64_t phone ) {
//新增数据项
contacts contacttable( _self, owner );//(code, scope)
//(player, item),player域指由谁来支付这个数据存储费用
contacttable.emplace( _self, [&]( auto& o ) {
o.name = name;
o.phone = phone;
});
}
/// @abi action
void remove(account_name owner, account_name contact_name) {
//删除数据项
contacts contacttable( _self, owner );
name n{contact_name};
auto item = contacttable.find( contact_name );
if( item == contacttable.end() ) {
print_f("Not found name:%sn", n.to_string().data());
} else {
contacttable.erase(item);
};
}
/// @abi action
void modify(account_name owner, account_name contact_name, uint64_t phone) {
//修改数据项
contacts contacttable( _self, owner );
name n{contact_name};
auto item = contacttable.find( contact_name );
if( item == contacttable.end() ) {
print_f("Not found name:%sn", n.to_string().data());
} else {
contacttable.modify( item, _self, [&]( auto& o ) {
o.phone = phone;
});
};
}
/// @abi action
void findbyname(account_name owner, account_name contact_name) {
//根据name主键查找数据项
contacts contacttable( _self, owner );
name n{contact_name};
auto item = contacttable.find( contact_name );
if( item == contacttable.end() ) {
print_f("Not found name:%s\n", n.to_string().data());
} else {
n = name{item->name};
print_f("Found phone:%, for %s\n", item->phone, n.to_string().data());
};
}
/// @abi action
void findbyphone(account_name owner, uint64_t phone) {
//根据phone索引查找数据项
contacts contacttable( _self, owner );
auto phoneinx = contacttable.get_index
(); auto item = phoneinx.find(phone);
if( item == phoneinx.end() ) {
print_f("Not found phone:%\n", phone);
} else {
name n{item->name};
print_f("Found name:%s for phone:%\n", n.to_string().data(), item->phone);
};
}
};
EOSIO_ABI( tabletest, (add) (remove) (modify) (findbyname) (findbyphone))
请参考我的博文【EOS编写HelloWorld智能合约】
//cleos create account只适合本地私有链,其他网络需要使用cleos system newaccount命令
$cleos create account eosio table.code $KEY_PUB_1 $KEY_PUB_1
$cleos set contract table.code ./table -p table.code
$cleos create account eosio itleaks $KEY_PUB_2 $KEY_PUB_2
$cleos push action table.code add '[ "itleaks", "jackma", "13456" ]' -p itleaks
$cleos get table table.code itleaks contact
从以下github网页下载源码,即可一键实践执行该智能合约
https://github.com/itleaks/eos-contract/tree/master/table-exp/
/******************************************
* 本文来自CSDN博主"爱踢门"
* 转载请标明出处:http://blog.csdn.net/itleaks
******************************************/
如果你喜欢我的文章,请文首点击关注
如果你对EOS,ETH技术及开发感兴趣,请关注本公众号,一起探索区块链未来
有任何疑问或者遇到EOS,ETH的任何问题,请添加本人的微信号