[EOS源码分析] EOS智能合约开发实践之数据库持久化

2018-06-26 11:15:46

以太坊智能合约定义的全局变量的值是持久性的,就相当于智能合约一直在运行着。而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_name, record >
   > 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<table_name, record,
        indexed_by<index_name, index_func>,
        indexed_by<index_name_1, index_func_1>,
        ….>
   > table( code, scope );

和纯数据表相比,就多了index_by索引定义
索引的定义格式是:indexed_by<索引名,索引键值函数>,比如这个实例:

eosio::multi_index<N(orders), limit_order,
        indexed_by< N(byexp), const_mem_fun<limit_order, uint64_t, &limit_order::get_expiration> >,
        indexed_by< N(byprice), const_mem_fun<limit_order, uint128_t, &limit_order::get_price> >
                     > orders( N(multitest), N(multitest) );

<N(orders), limit_order,这部分和纯数据表是一样的,第一个参数是表名,第二个参数是表行对象。这里有两个索引对象。分别是
indexed_by< N(byexp), const_mem_fun<limit_order, uint64_t, &limit_order::get_expiration>>
indexed_by< N(byprice), const_mem_fun<limit_order, uint128_t, &limit_order::get_price> >
N(byprice):索引名称是'byprice'
const_mem_fun<limit_order, uint128_t,&limit_order::get_price>中的const_mem_fun定义了一个索引键值函数,这个其实是通过boost::bind函数将对象的const成员函数转化为函数指针.其格式是<ObjectType, indexType(索引类型), &ObjectType::function>

索引查找数据

  auto priceidx = orders.get_index<N(byprice)>();
    print("Items sorted by price:\n");
    for( const auto& item : priceidx ) {
       print(" ID=", item.id, ", price=", item.price, ", owner=", name{item.owner}, "\n");
    }

二级索引类型限制

  • idx64 - 原始的64位无符号整数密钥.
  • idx128 - 原始128位无符号整数密钥.
  • idx256 - 256位固定大小的字典键.
  • idx_double - 双精度浮点键.
  • idx_long_double - 四倍精度浮点键.

查看数据表数据

$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<N(contact), contact, indexed_by< N(byphone), const_mem_fun<contact, uint64_t, &contact::get_phone> >
                  > 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<N(byphone)>();
            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 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

源码一键实践

https://github.com/itleaks/eos-contract/tree/master/table-exp

转载自:http://blog.csdn.net/itleaks
当前页面是本站的「Baidu MIP」版。发表评论请点击:完整版 »