redbutterfly avatar

EOS 的智能合约 -- blog_view

redbutterfly

Published: 08 Jun 2018 › Updated: 08 Jun 2018EOS 的智能合约 -- blog_view

EOS 的智能合约 -- blog_view

1、概要
关于如何编译EOS源码、编译docker镜像,搭建节点等等,官方都有相关文档,

2、本次合约介绍
之前在eos dawn 2.0版本,有个示例合约 simpledb,公司需求的智能合约就是实现了类似合约,所以,之前我实现的版本就是根据这个修改的。而且,该合约主要使用的是2.0版中的 db.h头文件中的函数 store_str,以字符串为索引,保存结构体的功能;
但是,在3.0版本中,该功能暂时删除,只能暂时使用其他功能代替,目前,决定使用multi_index容器;

3、multi_index
eos dawn 3.0中的multi_index,使用方法和boost中的multi_index,非常类似,就是多重索引容器,假如清楚其使用方法的话,应该对这个比较熟悉;

声明
typedef eosio::multi_index< tablename, typename> table( code, scope);
其中,需要的几个参数,如其命名含义:

tablename:该table的名称;
typename: 该容器存储的结构体;
code:本合约的名称,例如 N(tests);
scope:数据存储的账户名;
多级索引
声明时候,可以使用以下方式声明二级索引,或多级索引:

typedef eosio::multi_index< tablename, typename,
index_by< scope, const_mem_fun<typename, index_type, typename::method> >

table( code, scope);
但是,目前二级索引,只支持uint64_t、uint128_t、 key256(eosio的内建类型) 具体如使用,会在后续程序里面讲解;

方法
声明完成后,可以使用以下功能:

table.emplace(scope, [&]( auto& g ) { ... }) 添加数据;
table.find(primary_key) 用关键字查找;
table.modify(itr, scope, [&]( auto& g ) { ... }) 修改数据;
table.erase(itr) 删除
table.begin() 数据起始
table.end() 数据末尾
...
4、更符合面向对象的新合约
Dawn3.0的新合约编写方式见:新格式
该合约要实现以下功能:

每个用户能够上传自己的blog文章;
其他用户能够审核该用户的文章是否正确;
作者可以查询文章状态和数量;
结构定义如下,即abi文件:

{
"____comment": "This file was generated by eosio-abigen. DO NOT EDIT - 2018-04-16T07:24:15",
"types": [],
"structs": [{
"name": "account",
"base": "",
"fields": [{
"name": "owner",
"type": "account_name"
},{
"name": "blognum",
"type": "uint32"
}
]
},{
"name": "blog",
"base": "",
"fields": [{
"name": "ID",
"type": "uint64"
},{
"name": "status",
"type": "uint8"
},{
"name": "approve_status",
"type": "string"
},{
"name": "producer",
"type": "account_name"
},{
"name": "reviewer",
"type": "account_name"
},{
"name": "content",
"type": "string"
}
]
},{
"name": "upload",
"base": "",
"fields": [{
"name": "producer",
"type": "account_name"
},{
"name": "content",
"type": "string"
}
]
},{
"name": "reviewing",
"base": "",
"fields": [{
"name": "ID",
"type": "uint64"
},{
"name": "reviewer",
"type": "account_name"
}
]
},{
"name": "approved",
"base": "",
"fields": [{
"name": "ID",
"type": "uint64"
}
]
},{
"name": "disapprove",
"base": "",
"fields": [{
"name": "ID",
"type": "uint64"
},{
"name": "reason",
"type": "string"
}
]
},{
"name": "remove",
"base": "",
"fields": [{
"name": "ID",
"type": "uint64"
}
]
}
],
"actions": [{
"name": "upload",
"type": "upload",
"ricardian_contract": ""
},{
"name": "reviewing",
"type": "reviewing",
"ricardian_contract": ""
},{
"name": "approved",
"type": "approved",
"ricardian_contract": ""
},{
"name": "disapprove",
"type": "disapprove",
"ricardian_contract": ""
},{
"name": "remove",
"type": "remove",
"ricardian_contract": ""
}
],
"tables": [{
"name": "account",
"index_type": "i64",
"key_names": [
"owner"
],
"key_types": [
"account_name"
],
"type": "account"
},{
"name": "blog",
"index_type": "i64",
"key_names": [
"ID"
],
"key_types": [
"uint64"
],
"type": "blog"
}
],
"clauses": []
}
总结如下:

table: account(存储用户blog数量)、blog(保存用户blog);
action:
upload 上传blog信息;
reviewing 审核员开始审核;
approved 通过审核;
disapprove 未通过审核;
remove 用户删除自己blog;
5、代码
/**

using eosio::indexed_by;
using eosio::const_mem_fun;
using std::string;

class blog_view : public eosio::contract {
public:
using contract::contract;
blog_view(account_name self)
:eosio::contract(self),
accounts(_self, _self),
idlists(_self, _self),
init_status(std::string(64,'0'))
{}

    /// @abi action
    void upload(const account_name producer, const std::string content) {
        require_auth(producer);
        
        blog_index upload_blogs(_self, producer);
        //获取ID
        uint32_t nowID = get_ID();

        //TODO: add the dedup
        upload_blogs.emplace(producer, [&]( auto& g ) {
            g.ID = nowID;
            g.status = Status::s_uploaded;
            g.producer = producer;
            g.content = content;
            g.approve_status = init_status;
        });

        //在idlist添加,ID--producer关系,用于之后通过ID查询producer
        idlists.emplace(_self, [&]( auto& g ) {
            g.ID = nowID;
            g.producer = producer;
        });

        //blog数量+1
        blognum_op(producer, '+');
    }

    /// @abi action
    void reviewing(const uint64_t ID, const account_name reviewer) {
        require_auth(reviewer);

        //先通过idlist查询ID,获取用户名,然后才能用mutil_index查询具体用户的blog,下同
        auto itrid = idlists.find(ID);
        eosio_assert(itrid != idlists.end(), "this blog doesn't exists!\n");

        blog_index review_blogs(_self, itrid->producer);

        auto itr = review_blogs.find( ID );
        eosio_assert(itr != review_blogs.end(), "this blog doesn't exists!\n");
        eosio_assert(itr->producer != reviewer, "you can't review youself!\n");
        eosio_assert(itr->status == Status::s_uploaded, "this blog is reviewing or reviewed!\n");
        
        review_blogs.modify(itr, itrid->producer, [&](auto& g){
            g.status = Status::s_reviewing;
            g.reviewer = reviewer;
        });
    }

    /// @abi action
    void approved(const uint64_t ID) {

        auto itrid = idlists.find(ID);
        eosio_assert(itrid != idlists.end(), "this blog doesn't exists!\n");
        blog_index approve_blogs(_self, itrid->producer);

        auto itr = approve_blogs.find( ID );
        eosio_assert(itr != approve_blogs.end(), "this blog doesn't exists!\n");
        eosio_assert(itr->status == Status::s_reviewing, "this blog is reviewed!\n");
        require_auth(itr->reviewer);
        
        approve_blogs.modify(itr, itrid->producer, [&](auto& g){
            g.status = Status::s_approved;
            g.approve_status = std::string("approved");
        });
    }

    /// @abi action
    void disapprove(const uint64_t ID, std::string reason) {

        auto itrid = idlists.find(ID);
        eosio_assert(itrid != idlists.end(), "this blog doesn't exists!\n");
        blog_index disappr_policys(_self, itrid->producer);

        auto itr = disappr_policys.find( ID );
        eosio_assert(itr != disappr_policys.end(), "this blog doesn't exists!\n");
        eosio_assert(itr->status == Status::s_reviewing, "this blog is reviewed!\n");
        require_auth(itr->reviewer);
        
        disappr_policys.modify(itr, itrid->producer, [&](auto& g){
            g.status = Status::s_disapprove;
            g.approve_status = reason;
        });
    }

    /// @abi action
    void remove(const uint64_t ID) {
        auto itrid = idlists.find(ID);
        eosio_assert(itrid != idlists.end(), "this blog doesn't exists!\n");
        blog_index remove_policys(_self, itrid->producer);

        auto itr = remove_policys.find( ID );
        eosio_assert(itr != remove_policys.end(), "this blog doesn't exists!\n");
        require_auth(itr->producer);
        
        //使用erase删除
        remove_policys.erase(itr);
        blognum_op(itr->producer, '-');
    }


private:
    enum Status {s_uploaded,s_reviewing,s_approved,s_disapprove};

    /**
     * 用于保存用户信息,保存用户文章数量
     * @abi table account i64
     */
    struct account {
        account( account_name o = account_name() ):owner(o){}

        account_name owner;
        uint32_t     blognum = 0;

        bool is_empty()const { return !blognum; }

        uint64_t primary_key()const { return owner; }

        EOSLIB_SERIALIZE( account, (owner)(blognum) )
    };

    typedef eosio::multi_index< N(account), account> account_index;

    /**
     * 保存文章ID和用户关系,假如不保存,则审核员每次都要提交文章作者;
     */
    struct idlist {
        uint64_t ID;
        account_name producer;

        uint64_t primary_key()const { return ID; }

        EOSLIB_SERIALIZE( idlist, (ID)(producer) )
    };
    typedef eosio::multi_index< N(idlist), idlist> idlist_index;

    //@abi table blog i64
    struct blog {
        uint64_t ID;
        uint8_t status;
        std::string approve_status;
        account_name producer;
        account_name reviewer;
        std::string content;

        auto primary_key() const { return ID; }

        EOSLIB_SERIALIZE( blog, (ID)(status)(approve_status)(producer)(reviewer)(content) )
    }; 

    typedef eosio::multi_index< N(blog), blog> blog_index;

    account_index accounts;
    idlist_index idlists;
    std::string init_status;

    // get the code's policynum
    uint32_t get_ID() {
        auto itr = accounts.find( _self );
        if ( itr == accounts.end() ) {
            return 0;
        } else {
            return itr->blognum;
        }
    }

    /**
     * to operate the account's policynum
     * op : '+','-'
     */
    void blognum_op(account_name name, char op) {
        auto itr = accounts.find( name );
        if ( itr == accounts.end() ) {
            accounts.emplace(name, [&]( auto& g ) {
                g.owner = name;
                g.blognum = 1;
            });
        } else {
            if( op == '+' ) {
                accounts.modify(itr, itr->owner, []( auto& g ) {g.blognum += 1;});
            } else if( op == '-' ) {
                accounts.modify(itr, itr->owner, []( auto& g ) {g.blognum -= 1;});
            }
        }

        itr = accounts.find( _self );
        if ( itr == accounts.end() ) {
            accounts.emplace(_self, [&]( auto& g ) {
                g.owner = _self;
                g.blognum = 1;
            });
        } else {
            if( op == '+' ) {
                accounts.modify(itr, itr->owner, []( auto& g ) {g.blognum += 1;});
            }
        }
    }

};

EOSIO_ABI( blog_view, (upload)(reviewing)(approved)(disapprove)(remove) )
要点简单总结:

智能合约的开发,主要是要摒弃之前开发习惯,因为要在一定的限制下开发需要的功能;比如,在审查员审查blog的时候,在blog_index结构中,用scope作为主分类,然后使用ID作为primary_key,这样就要求每次要查询primary_key的时候,首先要知道scope,但是,虽然审查员可以每次都传入producer,但是太过麻烦,这种时候,就要在生成一个结构体idlist,用于存储这种关系,因为idlist的scope是合约本身,这就可以避免不知道scope的情况;
在进行upload时候,blog的approve_status属性是string,我将其初始化为一个64字符的字符串。此处是因为,假如我先设置空值,或短字符串的时候,当审核员要修改此string,并超过原先值的时候,就需要producer的权限,正常来说,审核员要提交了,还需要上传者的权限,这就不对了。所以,此处设置一个长字符串,并要求审核员设置不要超过64;
其他,就没有什么难点或者问题了。

6、执行
使用eosiocpp编译完后,执行上传:
image.png

使用tester作为上传者,先上传两个blog:
image.png

使用get table查看上传结果:
image.png

审核员yanyan先开始审核第二个,发送开始审核action:
image.png

审核通过后,发送通过的action:
image.png
审核第一个(略过reviewing,同上),但是,发现问题,执行审核不通过:

image.png

用户看到审核不通,则删除不通过的合约:
image.png

以上,就是该智能合约的执行过程。
7、其他
当然,该合约还有很多可以改进的地方,比如blog增加第二个key,更加方便的排重,或者添加统计未审核blog的table,方便审核员查找等等。

Leave EOS 的智能合约 -- blog_view to:

Written by

Read more #eos posts


Best Posts From redbutterfly

We have not curated any of redbutterfly's posts yet. But you can encourage our curation team to review posts by visiting them regularly and by referring other readers. Because we give priority to frequently read content.

More Posts From redbutterfly