EOS将多索引表添加到智能合约

在本教程中,我们将在你的智能合约中按步骤创建和使用多索引表。

建多索引表是一种为了在RAM快速访问的方法,主要用来来缓存状态和数据。多索引表支持创建、读取、更新和删除(CRUD) 业务,区块链不行(它只支持创建和读取)。

多索引表提供了快速访问数据存储接口,是一种存储智能合同中使用的数据的实用的方法。在区块链记录交易信息,你应该使用多索引表存储应用程序数据。

使用多索引表,因为他们支持为使用的数据建立多个索引,主索引必须是uint64_t类型和唯一的,但其他的索引,可以有重复的,你可以使用多达16个,类型可以是uint64_t, uint128_t, uint256_t, double or long double

如果你想你需要使用一个字符串做索引,需要转换成一个整数型,将结果存储在随后索引的字段中。

1.创建一个结构

创建一个可以存储在多索引表中的结构,并在要索引的字段上定义getter

请记住,这些getter中必须有一个命名为primary_key(),如果没有这个,编译器eosiocpp将产生一个错误…”it can’t find the field to use as the primary key”即它找不到任何一个字段被作为主键。

如果你想要有一个以上的索引,(最多允许16个),然后为你想要索引的任何字段定义一个getter,这时这个名称就不那么重要了,因为你会把getter名称传递给typedef

1
2
3
4
5
6
7
8
9
10
11
/// @abi table
struct mystruct
{
uint64_t key;
uint64_t secondid;
std::string name;
std::string account;

uint64_t primary_key() const { return key; } // getter for primary key
uint64_t by_id() const {return secondid; } // getter for additional key
};

这里还要注意两件事:

1.注释:

1
/// @abi table

编译器需要使用eosiocpp来识别要通过ABI公开该表并使其在智能合约之外可见。

2.结构名称少于12个字符,而且所有的字符都要小写字母。

2.多索引表和定义索引

定义多索引表将使用mystruct,告诉它要索引什么,以及如何获取正在索引的数据。主键将自动创建的,所以使用struct后,如果我想要一个只有一个主键的多索引表,我可以定义它为:

1
typedef eosio::multi_index<N(mystruct), mystruct> datastore;

这定义了多个索引通过表名N(mystruct)和结构名mystructN(mystruct)会对结构名编译转换到uint64_t,使用uint64_t来标识属于多索引表的数据。

若要添加附加索引或辅助索引,则使用indexed_by模板作为参数,因此定义变为:

1
typedef eosio::multi_index<N(mystruct), mystruct, indexed_by<N(secondid), const_mem_fun<mystruct, uint64_t, &mystruct::by_id>>> datastore;

注意:

1
indexed_by<N(secondid), const_mem_fun<mystruct, uint64_t, &mystruct::by_id>>

参数:

  • 字段的名称转换为整数,N(secondid)
  • 一个用户定义的密钥调用接口,const_mem_fun<mystruct, uint64_t, &mystruct::by_id>

来看看有三个索引的情况。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/// @abi table
struct mystruct
{
uint64_t key;
uint64_t secondid;
uint64_t anotherid;
std::string name;
std::string account;

uint64_t primary_key() const { return key; }
uint64_t by_id() const {return secondid; }
uint64_t by_anotherid() const {return anotherid; }
};

typedef eosio::multi_index<N(mystruct), mystruct, indexed_by<N(secondid), const_mem_fun<mystruct, uint64_t, &mystruct::by_id>>, indexed_by<N(anotherid), const_mem_fun<mystruct, uint64_t, &mystruct::by_anotherid>>> datastore;

更多的就不列了。

这里要注意的一个重要事项是,结构名与表名的匹配,并且将出现在ABI文件中的名称遵循规则(12个字符,所有都是小写的字母)。如果它们没有遵循这个规则,则表不会通过ABI可见(当然可以通过编辑ABI文件来绕过这一点)。

3.创建定义类型的局部变量

1
2
3
// local instances of the multi indexes
pollstable _polls;
votes _votes;

现在我已经定义了一个带有两个索引的多索引表,我可以在我的智能合约中使用它。

如下是一个智能合约使用两个索引的多索引表的例子。在这里你可以看到如何遍历表,如何在同一合约中使用两个表,我们未来将增加额外的教程,利用多索引表。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
#include <eosiolib/eosio.hpp>

using namespace eosio;

class youvote : public contract {
public:
youvote(account_name s):contract(s), _polls(s, s), _votes(s, s)
{}

// public methods exposed via the ABI
// on pollsTable

/// @abi action
void version()
{
print("YouVote version 0.01");

};

/// @abi action
void addpoll(account_name s, std::string pollName)
{
//require_auth(s);

print("Add poll ", pollName);

// update the table to include a new poll
_polls.emplace(get_self(), [&](auto& p)
{
p.key = _polls.available_primary_key();
p.pollId = _polls.available_primary_key();
p.pollName = pollName;
p.pollStatus = 0;
p.option = "";
p.count = 0;
});
};


/// @abi action
void rmpoll(account_name s, std::string pollName)
{
//require_auth(s);

print("Remove poll ", pollName);

std::vector<uint64_t> keysForDeletion;
// find items which are for the named poll
for(auto& item : _polls)
{
if (item.pollName == pollName)
{
keysForDeletion.push_back(item.key);
}
}

// now delete each item for that poll
for (uint64_t key : keysForDeletion)
{
print("remove from _polls ", key);
auto itr = _polls.find(key);
if (itr != _polls.end())
{
_polls.erase(itr);
}
}


// add remove votes ... don't need it the actions are permanently stored on the block chain

std::vector<uint64_t> keysForDeletionFromVotes;
// find items which are for the named poll
for(auto& item : _votes)
{
if (item.pollName == pollName)
{
keysForDeletionFromVotes.push_back(item.key);
}
}

// now delete each item for that poll
for (uint64_t key : keysForDeletionFromVotes)
{
print("remove from _votes ", key);
auto itr = _votes.find(key);
if (itr != _votes.end())
{
_votes.erase(itr);
}
}


};

/// @abi action
void status(std::string pollName)
{
print("Change poll status ", pollName);

std::vector<uint64_t> keysForModify;
// find items which are for the named poll
for(auto& item : _polls)
{
if (item.pollName == pollName)
{
keysForModify.push_back(item.key);
}
}

// now get each item and modify the status
for (uint64_t key : keysForModify)
{

print("modify _polls status", key);
auto itr = _polls.find(key);
if (itr != _polls.end())
{
_polls.modify(itr, get_self(), [&](auto& p)
{
p.pollStatus = p.pollStatus + 1;
});
}
}
};

/// @abi action
void statusreset(std::string pollName)
{
print("Reset poll status ", pollName);

std::vector<uint64_t> keysForModify;
// find all poll items
for(auto& item : _polls)
{
if (item.pollName == pollName)
{
keysForModify.push_back(item.key);
}
}

// update the status in each poll item
for (uint64_t key : keysForModify)
{
print("modify _polls status", key);
auto itr = _polls.find(key);
if (itr != _polls.end())
{
_polls.modify(itr, get_self(), [&](auto& p)
{
p.pollStatus = 0;
});
}
}
};


/// @abi action
void addpollopt(std::string pollName, std::string option)
{
print("Add poll option ", pollName, "option ", option);

// find the pollId, from _polls, use this to update the _polls with a new option
for(auto& item : _polls)
{
if (item.pollName == pollName)
{
// can only add if the poll is not started or finished
if(item.pollStatus == 0)
{
_polls.emplace(get_self(), [&](auto& p)
{
p.key = _polls.available_primary_key();
p.pollId = item.pollId;
p.pollName = item.pollName;
p.pollStatus = 0;
p.option = option;
p.count = 0;
});
}
else
{
print("Can not add poll option ", pollName, "option ", option, " Poll has started or is finished.");
}

break; // so you only add it once
}
}
};

/// @abi action
void rmpollopt(std::string pollName, std::string option)
{
print("Remove poll option ", pollName, "option ", option);

std::vector<uint64_t> keysForDeletion;
// find and remove the named poll
for(auto& item : _polls)
{
if (item.pollName == pollName)
{
keysForDeletion.push_back(item.key);
}
}


for (uint64_t key : keysForDeletion)
{
print("remove from _polls ", key);
auto itr = _polls.find(key);
if (itr != _polls.end())
{
if (itr->option == option)
{
_polls.erase(itr);
}
}
}
};


/// @abi action
void vote(std::string pollName, std::string option, std::string accountName)
{
print("vote for ", option, " in poll ", pollName, " by ", accountName);

// is the poll open
for(auto& item : _polls)
{
if (item.pollName == pollName)
{
if (item.pollStatus != 1)
{
print("Poll ",pollName, " is not open");
return;
}

break; // only need to check status once
}
}

// has account name already voted?
for(auto& vote : _votes)
{
if (vote.pollName == pollName && vote.account == accountName)
{
print(accountName, " has already voted in poll ", pollName);
//eosio_assert(true, "Already Voted");
return;
}
}

uint64_t pollId =99999; // get the pollId for the _votes table

// find the poll and the option and increment the count
for(auto& item : _polls)
{
if (item.pollName == pollName && item.option == option)
{
pollId = item.pollId; // for recording vote in this poll

_polls.modify(item, get_self(), [&](auto& p)
{
p.count = p.count + 1;
});
}
}

// record that accountName has voted
_votes.emplace(get_self(), [&](auto& pv)
{
pv.key = _votes.available_primary_key();
pv.pollId = pollId;
pv.pollName = pollName;
pv.account = accountName;
});
};

private:

// create the multi index tables to store the data

/// @abi table
struct poll
{
uint64_t key; // primary key
uint64_t pollId; // second key, non-unique, this table will have dup rows for each poll because of option
std::string pollName; // name of poll
uint8_t pollStatus =0; // staus where 0 = closed, 1 = open, 2 = finished
std::string option; // the item you can vote for
uint32_t count =0; // the number of votes for each itme -- this to be pulled out to separte table.

uint64_t primary_key() const { return key; }
uint64_t by_pollId() const {return pollId; }
};
typedef eosio::multi_index<N(poll), poll, indexed_by<N(pollId), const_mem_fun<poll, uint64_t, &poll::by_pollId>>> pollstable;


/// @abi table
struct pollvotes
{
uint64_t key;
uint64_t pollId;
std::string pollName; // name of poll
std::string account; //this account has voted, use this to make sure noone votes > 1

uint64_t primary_key() const { return key; }
uint64_t by_pollId() const {return pollId; }
};
typedef eosio::multi_index<N(pollvotes), pollvotes, indexed_by<N(pollId), const_mem_fun<pollvotes, uint64_t, &pollvotes::by_pollId>>> votes;

// local instances of the multi indexes
pollstable _polls;
votes _votes;
};

EOSIO_ABI( youvote, (version)(addpoll)(rmpoll)(status)(statusreset)(addpollopt)(rmpollopt)(vote))

注意EOSIO_ABI调用,它通过ABI公开函数,重要的是函数名ABI函数名规则一定要匹配。

======================================================================

分享一个交互式的在线编程实战,EOS智能合约与DApp开发入门

EOS教程

本课程帮助你快速入门EOS区块链去中心化应用的开发,内容涵盖EOS工具链、账户与钱包、发行代币、智能合约开发与部署、使用代码与智能合约交互等核心知识点,最后综合运用各知识点完成一个便签DApp的开发。

  • web3j教程,主要是针对java和android程序员进行区块链以太坊开发的web3j详解。
  • 以太坊教程,主要介绍智能合约与dapp应用开发,适合入门。
  • 以太坊开发,主要是介绍使用node.js、mongodb、区块链、ipfs实现去中心化电商DApp实战,适合进阶。
  • python以太坊,主要是针对python工程师使用web3.py进行区块链以太坊开发的详解。
  • php以太坊,主要是介绍使用php进行智能合约开发交互,进行账号创建、交易、转账、代币开发以及过滤器和事件等内容。
  • C#以太坊,主要讲解如何使用C#开发基于.Net的以太坊应用,包括账户管理、状态与交易、智能合约开发与交互、过滤器和事件等。

汇智网原创翻译,转载请标明出处。这里是原文