1绊率、前提概要
關(guān)于如何編譯EOS源碼垂睬、編譯docker鏡像媳荒,搭建節(jié)點(diǎn)等等,官方都有相關(guān)文檔羔飞,新的特性也在之前文章中大體介紹了EOS Dawn 3.0整理
2肺樟、本次合約介紹
之前在eos dawn 2.0版本,有個示例合約 simpledb逻淌,公司需求的智能合約就是實現(xiàn)了類似合約么伯,所以,之前我實現(xiàn)的版本就是根據(jù)這個修改的卡儒。而且田柔,該合約主要使用的是2.0版中的 db.h頭文件中的函數(shù) store_str
俐巴,以字符串為索引,保存結(jié)構(gòu)體的功能硬爆;
但是欣舵,在3.0版本中,該功能暫時刪除缀磕,只能暫時使用其他功能代替缘圈,目前,決定使用multi_index容器袜蚕;
3糟把、multi_index
eos dawn 3.0中的multi_index,使用方法和boost中的multi_index牲剃,非常類似遣疯,就是多重索引容器,假如清楚其使用方法的話凿傅,應(yīng)該對這個比較熟悉缠犀;
聲明
typedef eosio::multi_index< tablename, typename> table( code, scope);
其中,需要的幾個參數(shù)聪舒,如其命名含義:
- tablename:該table的名稱辨液;
- typename: 該容器存儲的結(jié)構(gòu)體;
- code:本合約的名稱过椎,例如 N(tests)室梅;
- scope:數(shù)據(jù)存儲的賬戶名;
多級索引
聲明時候疚宇,可以使用以下方式聲明二級索引亡鼠,或多級索引:
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的內(nèi)建類型) 具體如使用榜揖,會在后續(xù)程序里面講解勾哩;
方法
聲明完成后,可以使用以下功能:
-
table.emplace(scope, [&]( auto& g ) { ... })
添加數(shù)據(jù)举哟; -
table.find(primary_key)
用關(guān)鍵字查找思劳; -
table.modify(itr, scope, [&]( auto& g ) { ... })
修改數(shù)據(jù); -
table.erase(itr)
刪除 -
table.begin()
數(shù)據(jù)起始 -
table.end()
數(shù)據(jù)末尾 - ...
4妨猩、更符合面向?qū)ο蟮男潞霞s
Dawn3.0的新合約編寫方式見:新格式
該合約要實現(xiàn)以下功能:
- 每個用戶能夠上傳自己的blog文章潜叛;
- 其他用戶能夠?qū)徍嗽撚脩舻奈恼率欠裾_;
- 作者可以查詢文章狀態(tài)和數(shù)量;
結(jié)構(gòu)定義如下威兜,即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": []
}
總結(jié)如下:
- table: account(存儲用戶blog數(shù)量)销斟、blog(保存用戶blog);
- action:
- upload 上傳blog信息;
- reviewing 審核員開始審核椒舵;
- approved 通過審核蚂踊;
- disapprove 未通過審核;
- remove 用戶刪除自己blog笔宿;
5犁钟、代碼
/**
* @file
* @copyright defined in eos/LICENSE.txt
* @auther: redbutterfly
* @createtime: 2018-04-16
*/
#include <eosiolib/eosio.hpp>
#include <eosiolib/multi_index.hpp>
#include <eosiolib/contract.hpp>
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關(guān)系措伐,用于之后通過ID查詢producer
idlists.emplace(_self, [&]( auto& g ) {
g.ID = nowID;
g.producer = producer;
});
//blog數(shù)量+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};
/**
* 用于保存用戶信息,保存用戶文章數(shù)量
* @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和用戶關(guān)系粪躬,假如不保存担败,則審核員每次都要提交文章作者;
*/
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) )
要點(diǎn)簡單總結(jié):
- 智能合約的開發(fā)镰官,主要是要摒棄之前開發(fā)習(xí)慣提前,因為要在一定的限制下開發(fā)需要的功能;比如泳唠,在審查員審查blog的時候狈网,在blog_index結(jié)構(gòu)中,用scope作為主分類笨腥,然后使用ID作為primary_key拓哺,這樣就要求每次要查詢primary_key的時候,首先要知道scope脖母,但是士鸥,雖然審查員可以每次都傳入producer,但是太過麻煩谆级,這種時候烤礁,就要在生成一個結(jié)構(gòu)體idlist,用于存儲這種關(guān)系肥照,因為idlist的scope是合約本身脚仔,這就可以避免不知道scope的情況;
- 在進(jìn)行upload時候舆绎,blog的approve_status屬性是string鲤脏,我將其初始化為一個64字符的字符串。此處是因為亿蒸,假如我先設(shè)置空值凑兰,或短字符串的時候掌桩,當(dāng)審核員要修改此string,并超過原先值的時候姑食,就需要producer的權(quán)限波岛,正常來說,審核員要提交了音半,還需要上傳者的權(quán)限则拷,這就不對了。所以曹鸠,此處設(shè)置一個長字符串煌茬,并要求審核員設(shè)置不要超過64;
其他彻桃,就沒有什么難點(diǎn)或者問題了坛善。
6、執(zhí)行
使用eosiocpp編譯完后邻眷,執(zhí)行上傳:使用tester作為上傳者眠屎,先上傳兩個blog:
使用get table查看上傳結(jié)果:
審核員yanyan先開始審核第二個,發(fā)送開始審核action:
審核通過后肆饶,發(fā)送通過的action:
審核第一個(略過reviewing改衩,同上),但是驯镊,發(fā)現(xiàn)問題葫督,執(zhí)行審核不通過:
用戶看到審核不通,則刪除不通過的合約:
以上板惑,就是該智能合約的執(zhí)行過程橄镜。
7、其他
當(dāng)然洒放,該合約還有很多可以改進(jìn)的地方蛉鹿,比如blog增加第二個key,更加方便的排重往湿,或者添加統(tǒng)計未審核blog的table妖异,方便審核員查找等等。
代碼見:我的GitHub