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

     以太坊智能合约定义的全局变量的值是持久性的,就相当于智能合约一直在运行着。而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只适合本地私有链,其他网络需要使用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的任何问题,请添加本人的微信号

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值