[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的任何问题,请添加本人的微信号

EOS 是由 Block.one 公司开发的一个新的区块链软件系统,它的目标是将一切去中心化(decentralize everything)。从 2017 年年中开始,经过一年的代币众筹后,它于 2018 年 6 月 15 通过由数十个区块生产者(block producer,BP,又称超级节点)组成的社区上线了主网,EOS 主网这条主要的区块链开始正式运转。 通过 EOS Tracker 可以查看 EOS 区块链网络(EOS 主网)的情况。 要注意,虽然有一个 EOS 主网,但实际情况要复杂得多,任何人都可以用 EOS 提供的 EOSIO 开源软件建立自己的一条链,且 EOS 鼓励开发者这么做。接下来,我们分几个主题介绍 EOS 这个基础公链它的应用开发EOS 这个基础公链可说是为应用而生的。EOS VS 以太坊了解 EOS 的方式之一是拿它与以太坊、比特币进行比较。 从开发目标上来讲,比特币、以太坊、EOS 是渐进的,分别是区块链 1.0、区块链 2.0、区块链 3.0,重心分别是货币、合约、应用。以太坊在实际应用中是以通证为主的。以太坊、EOS 均是借鉴与延续之前的思路重新开发,以太坊是比特币的改进,EOS 是以太坊的改进。 这里先用比喻的方式来对比比特币、以太坊、EOS,见下图。 比特币的设计思路类似于黄金。在数字世界中,按工作量证明共识机制,挖矿节点进行加密计算,获得比特币形式的挖矿奖励。挖矿节点也可以获得交易费收益,不过,虽然在比特币网络中的资产价值高,但交易并不频繁,交易费收益目前在矿工收益中的占比并不高。 以太坊的设计思路类似于高速公路。在这条收费高速公路上,车辆行驶需要付费。它早期募集资金,建设“高速公路”,早期投资者享有“高速公路”的主要权益。之后,一起建设与维护“高速公路”的挖矿节点也可以获得挖矿奖励与交易费收益。在以太坊网络中,由于各类项目已经基于它生成了大量的通证,以太坊网络的交易量相对较多,挖矿节点获得的交易费收益占比高于比特币。 EOS 的设计思路则类似于房地产开发。Block.one 公司在将土地售卖出去之后,逻辑上它用获得的资金进行基础的开发,此后每年再以类似填海造田的方式增加 5% 的土地出来。 EOS 的繁荣主要取决于,已经竞购得到大量土地的开发商是不是开发经营好自己的地块?EOS 网络要依靠超级节点(即区块生产者)来各自建设、共同运营,按现在的设计,这些节点共同获得每年 1% 新增发的 EOS 作为回报。 与以太坊不同,EOS 网络的设计是不再收取网络交易费,持有 EOS 通证则拥有对应的网络使用权利。但是,如果一个应用的开发者不持有足够的 EOS 通证,可能就要从市场中购买付费租用,以获得使用主网的权利。类比来看,这种设计类似于购买或租用办公楼。 当然,以上用比喻的方式讨论只是为了便于理解。EOS 实际的情况是:Block.one 公司募集资金开发一个名为 EOSIO 的开源软件。EOS 社区用这个软件来运行 EOS 主网,且从逻辑上来讲,这个主网并非由 Block.one 公司运行,而是由社区运行的。另外,其他人也用 EOSIO 这个开源软件建立替代网(altnet)。 替代网(altnet)是一个模仿替代币(altcoin)而创造出来的新词。在社区运行的 EOS 主网(mainnet)之外,EOS 鼓励其他人用 EOSIO 开源软件架设新的区块链网络,这些区块链网络是类似于替代币的替代网。比特币的替代币是与比特币的价值无关的,类似地,替代网也与 EOS 主网无关。EOS 是对以太坊的改进,各个用 EOSIO 开源软件运行起来的区块链网络,可以做几乎所有以太坊能做的事,如编写智能合约、发行通证。为什么 EOS 有超级节点竞选EOS 所采用的共识机制是 DPOS(委托权益证明),即一些节点在获得足够多的投票支持后,成为见证人(witness)节点或 EOS 中所说的区块生产者(BP,也称超级节点),负责区块链的区块生成。 对于比特币系统,任何人都可以接入网络,以算力竞争记账权利,生成区块。而对于 EOS,只有超级节点才有资格生产区块。这是因为两者所采用的共识机制不同:比特币以太坊采用的是工作量证明共识机制,而 EOS 采用的是 DPOS(委托权益证明)共识机制。 围绕 POW 与 DPOS 的比较,讨论主要集中在能源消耗、效率、安全等方面。但我们也可以从去中心网络形成的角度来看,为什么 DPOS 是一种可行的选择。 基于区块链的思路开发的软件系统有以下三个关键要求:一是性能。它的去中心网络的整体性能能否支撑大量应用?
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值