EOS技术研究:合约与数据库交互
智能合约操作链数据库是很常见的应用场景。EOS提供了专门的工具来做这件事(相当于Ethereum的leveldb),专业术语叫做持久化API,本文将完整严密地介绍这个工具以及对它的使用测试。
关键字:EOS,智能合约,链数据库,eosio::multi_index,constructor,emplace,erase,find。
需求
首先来看EOS中智能合约涉及到持久化的场景需求。一个action在执行时会有上下文变量出现,包括事务机制的处理,这些内容会应用链上分配的内存资源,而如果没有持久化技术,执行超过作用域时就会丢失掉这些上下文数据。因此要使用持久化技术将关键内容记录在链数据库中,任何时候使用都不受影响。持久化技术应该包括:
- 记录一些状态持久化到数据库中
- 具备查询的能力从数据库中获取内容
- 提供C++ 的API来调用这些服务,也服务于合约开发者
eosio::multi_index
这是模仿boost::multi_index开发的一套库。它使用C++编写,提供了合约与数据库的交互持久化接口。
Multi-Index Iterators:不同于其他key-value数据库,multi_index提供了不同类型的key对应的值也是可迭代的复杂集合类型。
创建表
- class/struct 定义一个持久化对象。
- 该对象需要有一个const的成员作为主键,类型为uint64_t
- 二级主键可选,提供不同的键类型
- 为每个二级索引定义一个键导出器,键导出器是一个函数,可以用来从Multi_index表中获取键
使用Multi-Index表
一般来讲,对数据库的操作无外乎增删改查,
- 增加对应的方法是emplace
- 修改就是modify
- 删除是erase
- 查找包括find和get,以及迭代器操作
实战
下面我们通过一个智能合约操作底层数据库的实例,来演示对持久化api,multi_index的使用。过程中,也会展示相应api的定义。
车辆维修跟踪
首先,创建一个service表,用来创建服务记录报告,它包含的字段有:
- 主键,客户ID不可当做主键,因为一个客户可以有多条记录。实际上,我们并不直接需要主键,所以我们可以让系统为我们生成一个。
- 客户ID,与账户名字对应
- 服务日期
- 里程表,汽车里程表
#include <eosiolib/eosio.hpp>
using namespace eosio;
class vehicle : public eosio::contract {
public:
/// @abi table
struct service_rec {
uint64_t pkey;
account_name customer;
uint32_t service_date;
uint32_t odometer;
auto primary_key() const { return pkey; }
account_name get_customer() const { return customer; }
EOSLIB_SERIALIZE(service_rec, (pkey)(customer)(service_date)(odometer))
};
typedef multi_index<N(service), service_rec> service_table_type;
using contract::contract;
/// @abi action
void exec(account_name owner, account_name customer) {
print("Hello, ", name{customer});
service_table_type service_table(current_receiver(), owner);// 构造器,第一个参数是code代表表的拥有者,目前是current_receiver(),也可以使用contract基类的构造器初始化时的账户名_self,第二个参数是scope,在代码层次范围的标识符,这里就使用传入的owner账户。
uint64_t pkeyf;// 主键
service_table.emplace(owner, [&](auto &s_rec) {
s_rec.pkey = service_table.available_primary_key();// 主键自增
pkeyf = s_rec.pkey;
print(pkeyf);// 打印主键内容
s_rec.customer = customer;
s_rec.service_date = 2000;
s_rec.odometer = 1000;
});
service_rec result = service_table.get(pkeyf);
print("_", result.pkey);
print("_", result.customer);
print("_", result.service_date);
print("_", result.odometer);
}
};
EOSIO_ABI(vehicle, (exec))
使用eosiocpp工具生成abi文件和wast文件。
eosiocpp -g vehicle.abi vehicle.cpp | eosiocpp -o vehicle.wasm vehicle.cpp
然后在终端中部署该合约,
cleos set contract one work/CLionProjects/github.com/eos/contracts/vehicle
调用合约的exec方法,并输出结果:
liuwenbin@liuwenbin-H81M-DS2:~$ cleos push action one exec '["one","two"]' -p one
executed transaction: 3a45eaeb06732ad0c53ba7b157003e1c503f74ed447029d82cecbe12926cc9a9 112 bytes 365 us
# one <= one::exec {"owner":"one","customer":"two"}
>> Hello, two13_13_14927180964919508992_2000_1000
warning: transaction executed locally, but may not be confirmed by the network yet
通过输出结果,可以知道主键为13,customer账户名被转为无符号32位整型数据14927180964919508992,服务时间为2000,里程表为1000。我们已经成功将数据存入了multi_index并取了出来。
删除的话可以通过service_table.erase(result);来删除掉对应记录。
find涉及二级索引,迭代器等操作,end判断等multi_index的api操作没有给出具体实例,未来在其他合约使用时会直接说明。
再演练一个例子
为了更好的熟悉multi_index的机制,我们再演练一个简单的例子:维护一个todolist的数据库表。直接上代码如下:
#include <eosiolib/eosio.hpp>
#include <eosiolib/print.hpp>
class todolist : public eosio::contract
{
public:
using eosio::contract::contract;
// @abi table todos i64
struct todo
{
uint64_t id; // 待办事项主键id
std::string description; // 待办事项的描述参数
uint64_t completed; // 标记一个待办事项是否已完成
uint64_t primary_key() const { return id; }
EOSLIB_SERIALIZE(todo, (id)(description)(completed))
};
typedef eosio::multi_index<N(todos), todo> todo_table;
// @abi action
void create(account_name author, const uint32_t id, const std::string &description)
{
todo_table todos(_self, author);
todos.emplace(author, [&](auto &new_todo) {
new_todo.id = id;
new_todo.description = description;
new_todo.completed = 0;
});
eosio::print("todo#", id, " created");
}
// @abi action
void complete(account_name author, const uint32_t id)
{
todo_table todos(_self, author);
auto todo_lookup = todos.find(id);
eosio_assert(todo_lookup != todos.end(), "Todo does not exist");
todos.modify(todo_lookup, author, [&](auto &modifiable_todo) {
modifiable_todo.completed = 1;
});
eosio::print("todo#", id, " marked as complete");
}
// @abi action
void destroy(account_name author, const uint32_t id)
{
todo_table todos(_self, author);
auto todo_lookup = todos.find(id);
todos.erase(todo_lookup);
eosio::print("todo#", id, " destroyed");
}
};
EOSIO_ABI(todolist, (create)(complete)(destroy))
这里加入了对数据的增删改查功能。然后我们使用
eosiocpp -o todolist.wast todolist.cpp && eosiocpp -g todolist.abi todolist.cpp
创建对应的abi和wast文件。接下来创建用户todo.user,
liuwenbin@liuwenbin-H81M-DS2:~$ cleos create account eosio todo.user EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV
executed transaction: 7d7191e2935c6fd0024f571e228ee11d51f4f44a64ed2ce977326fb27679f0c6 200 bytes 174 us
# eosio <= eosio::newaccount {"creator":"eosio","name":"todo.user","owner":{"threshold":1,"keys":[{"key":"EOS6MRyAjQq8ud7hVNYcfnV...
warning: transaction executed locally, but may not be confirmed by the network yet
部署合约,
liuwenbin@liuwenbin-H81M-DS2:~$ cleos set contract todo.user work/VSCode-Projects/eos/contracts/todolist -p todo.user
Reading WAST/WASM from work/VSCode-Projects/eos/contracts/todolist/todolist.wasm...
Using already assembled WASM...
Publishing contract...
executed transaction: 03befa58d6a54970db708beaa0520179277b01addf1ec647a76a9b3f6459ff57 5128 bytes 2203 us
# eosio <= eosio::setcode {"account":"todo.user","vmtype":0,"vmversion":0,"code":"0061736d01000000016e1260047f7e7f7f0060037f7e...
# eosio <= eosio::setabi {"account":"todo.user","abi":{"types":[],"structs":[{"name":"todo","base":"","fields":[{"name":"id",...
warning: transaction executed locally, but may not be confirmed by the network yet
创建新条目,
liuwenbin@liuwenbin-H81M-DS2:~$ cleos push action todo.user create '["todo.user",1,"hello, world"]' -p todo.user
executed transaction: 54d9825971370a242fa51fa7cc587a6478dd7852039c263d91058c6b1163a4bc 120 bytes 336 us
# todo.user <= todo.user::create {"author":"todo.user","id":1,"description":"hello, world"}
>> todo#1 created
查询,
liuwenbin@liuwenbin-H81M-DS2:~$ cleos get table todo.user todo.user todos
{
"rows": [{
"id": 1,
"description": "hello, world",
"completed": 0
}
],
"more": false
}
完成事项,
liuwenbin@liuwenbin-H81M-DS2:~$ cleos push action todo.user complete '["todo.user",1]' -p todo.user
executed transaction: 11423637cb321969961b3ce0305c93ba7f95a83b6d82c1a4e31a08c569f2dcaa 104 bytes 312 us
# todo.user <= todo.user::complete {"author":"todo.user","id":1}
>> todo#1 marked as complete
warning: transaction executed locally, but may not be confirmed by the network yet
再次查询,completed字段已置为1,
liuwenbin@liuwenbin-H81M-DS2:~$ cleos get table todo.user todo.user todos
{
"rows": [{
"id": 1,
"description": "hello, world",
"completed": 1
}
],
"more": false
}
创建多条记录,
liuwenbin@liuwenbin-H81M-DS2:~$ cleos push action todo.user create '["todo.user",2,"eos funk"]' -p todo.user
executed transaction: 35f417a4d7438e6ea9ffd837e3c261fca0cb3926b534dc6603fdb38f94d5cd77 120 bytes 329 us
# todo.user <= todo.user::create {"author":"todo.user","id":2,"description":"eos funk"}
>> todo#2 created
warning: transaction executed locally, but may not be confirmed by the network yet
liuwenbin@liuwenbin-H81M-DS2:~$ cleos push action todo.user create '["todo.user",10,"go to bank"]' -p todo.user
executed transaction: cd4cd2c85500e93a79eb5c3b3852a0a96a9fb220696c63f2683b473d58a6ca34 120 bytes 311 us
# todo.user <= todo.user::create {"author":"todo.user","id":10,"description":"go to bank"}
>> todo#10 created
warning: transaction executed locally, but may not be confirmed by the network yet
查询,
liuwenbin@liuwenbin-H81M-DS2:~$ cleos get table todo.user todo.user todos
{
"rows": [{
"id": 1,
"description": "hello, world",
"completed": 1
},{
"id": 2,
"description": "eos funk",
"completed": 0
},{
"id": 10,
"description": "go to bank",
"completed": 0
}
],
"more": false
}
删除一条数据,
liuwenbin@liuwenbin-H81M-DS2:~$ cleos push action todo.user destroy '["todo.user",2]' -p todo.user
executed transaction: c87e37f4521e0ce2a865acbeb63c0f3a0681b9b5a0a6c5db8cd53584d6d13dca 104 bytes 2198 us
# todo.user <= todo.user::destroy {"author":"todo.user","id":2}
>> todo#2 destroyed
warning: transaction executed locally, but may not be confirmed by the network yet
再次查询,
liuwenbin@liuwenbin-H81M-DS2:~$ cleos get table todo.user todo.user todos
{
"rows": [{
"id": 1,
"description": "hello, world",
"completed": 1
},{
"id": 10,
"description": "go to bank",
"completed": 0
}
],
"more": false
}
这是一个完整的,通过multi_index进行curd的一个例子(第一个例子是官方文档给出的,其中内容有bug)。
总结
通过本篇文章的学习,我们掌握了如何在EOS中使用智能合约调用multi_index实现数据的持久化。
参考文档
- EOS官方文档
- EOS asia
更多文章请转到醒者呆的博客园
EOS技术研究:合约与数据库交互的更多相关文章
- slivelight5和数据库交互
最近开始研究sliverlight和数据库交互了,无奈网上资料较少,查阅了大量资料终于成功了,但是我记得还有别的方法,希望大家讨论一下 数据访问层我的用的是ado.net实体数据模型 然后新建了一个w ...
- 【转】手把手教你读取Android版微信和手Q的聊天记录(仅作技术研究学习)
1.引言 特别说明:本文内容仅用于即时通讯技术研究和学习之用,请勿用于非法用途.如本文内容有不妥之处,请联系JackJiang进行处理! 我司有关部门为了获取黑产群的动态,有同事潜伏在大量的黑产群 ...
- 手把手教你读取Android版微信和手Q的聊天记录(仅作技术研究学习)
1.引言 特别说明:本文内容仅用于即时通讯技术研究和学习之用,请勿用于非法用途.如本文内容有不妥之处,请联系JackJiang进行处理! 我司有关部门为了获取黑产群的动态,有同事潜伏在大量的黑产群 ...
- <脱机手写汉字识别若干关键技术研究>
脱机手写汉字识别若干关键技术研究 对于大字符集识别问题,一般采用模板匹配的算法,主要是因为该算法比较简单,识别速度快.但直接的模板匹配算法往往无法满足实际应用中对识别精度的需求.为此任俊玲编著的< ...
- 重复数据删除(De-duplication)技术研究(SourceForge上发布dedup util)
dedup util是一款开源的轻量级文件打包工具,它基于块级的重复数据删除技术,可以有效缩减数据容量,节省用户存储空间.目前已经在Sourceforge上创建项目,并且源码正在不断更新中.该工具生成 ...
- 伪AP检测技术研究
转载自:http://www.whitecell-club.org/?p=310 随着城市无线局域网热点在公共场所大规模的部署,无线局域网安全变得尤为突出和重要,其中伪AP钓鱼攻击是无线网络中严重的安 ...
- 转:Android推送技术研究
Android推送技术研究 字数5208 阅读4026 评论5 喜欢35 前言 最近研究Android推送的实现, 研究了两天一夜, 有了一点收获, 写下来既为了分享, 也为了吐槽. 需要说明的是有些 ...
- jqGrid添加删除功能(不和数据库交互)
jqGrid添加删除功能(不和数据库交互) 一.背景需求 项目中需要在前端页面动态的添加行,删除行,上下移动行等,同时还不和数据库交互.一直在用jqGrid展示表格的我们,从没有深入的研究过它,当然看 ...
- MongoDB进阶之路:不仅仅是技术研究,还有优化和最佳实践--转载
摘要:MongoDB是一个基于分布式文件存储的数据库.由C++语言编写.旨在为WEB应用提供可扩展的高性能数据存储解决方案. 本文将从操作手册.技术研究.会议分享.场景应用等几个方面给大家推荐干货好文 ...
随机推荐
- 目前调试移动设备程序只能通过USB线缆
就像iOS,转移(到设备上)并调试App不可能通过WiFi或蓝牙连接. 一个有线的USB线缆连接现今主要用来调试. 确保你直接将Android设备插入Mac的USB接口,避免使用USB hubs和扩展 ...
- 漫谈程序员(十)大白菜装机版安装win7系统使用教程
大白菜装机版安装win7系统使用教程 安装win7系统准备工作: ①使用大白菜装机版,制作一个大白菜u盘启动盘 ②将下载好的ghost win7系统镜像包放入制作好的大白菜u盘启动盘中. ③更改电脑硬 ...
- STL - 容器共性机制研究
C++模板是容器的概念. 理论提高:所有容器提供的都是值(value)语意,而非引用(reference)语意.容器执行插入元素的操作时,内部实施拷贝动作.所以STL容器内存储的元素必须能够被拷贝(必 ...
- 《java入门第一季》之参数引用
Java中的参数传递问题: 基本类型:形式参数的改变对实际参数没有影响. 引用类型:形式参数的改变直接影响实际参数. */ class ArgsDemo { public static void ma ...
- OSB开发常用资料
成功搭建OSB环境并运行HelloWorld项目 http://www.beansoft.biz/?p=2066 Oracle Service Bus 11gR1开发环境安装文档 http://www ...
- JavaScript检测提交表单text合法
近日,一朋友开设了地方性质的论坛,让我帮他处理下Login.php(所谓的用户的登陆页面),但是登陆的时候,出现空字符或敏感字符,需要提交到服务端的Script处理,大大降低了效率,于是乎,就有了此代 ...
- Java的依赖注入(控制反转)
两个主角"依赖注入"和"控制反转": 1.二都说的都是同一件事,只是叫法不同.是一个重要的面向对象编程的法则,也是一种设计模式: 2.英文原称:依赖注入,Dep ...
- Java-HttpServletResponse-HttpServletResponseWrapper
//继承ServletResponse,发送回复信息,servlet容器创建一个HttpServletResponse对象,将它作为service函数的参数 public interface Http ...
- Java-ServletConfig
/** * * A servlet configuration object used by a servlet container * to pass information to a servle ...
- Linux - 动态(Dynamic)与静态(Static)函数库
首先我们要知道的是,函式库的类型有哪些?依据函式库被使用的类型而分为两大类,分别是静态 (Static) 与动态 (Dynamic) 函式库两类. 静态函式库的特色: 扩展名:(扩展名为 .a) ...