中移链合约常用开发介绍 (二)多索引表的使用

news2025/1/11 4:14:40

一、目的

本文详细介绍了开发、部署和测试一个地址簿的智能合约的流程,适用于EOS的初学者了解如何使用智能合约实现本地区块链上数据的持久化和对持久化数据的增删改查。

二、智能合约介绍

区块链作为一种分布式可信计算平台,去中心化是其最本质的特征。每笔交易的记录不可篡改地存储在区块链上。智能合约中定义可以在区块链上执行的动作action和交易transaction的代码。可以在区块链上执行,并将合约执行状态作为该区块链实例不可变历史的一部分。

因此,开发人员可以依赖该区块链作为可信计算环境,其中智能合约的输入、执行和结果都是独立的,不受外部影响。

三、术语解释

EOS

EOS是Enterprise Operation System的缩写,是商用分布式应用设计的一款区块链操作系统。EOS引入了一种新的区块链架构EOSIO,用于实现分布式应用的性能扩展。与比特币、以太坊等货币不同,EOS是一种基于EOSIO软件项目发布的代币,也被称为区块链3.0。

索引

索引一般是指关系数据库中对某一列或多个列的值进行预排序的数据结构。在这里,索引是内存表的某一字段,我们可以根据该字段操作内存表的数据。

多索引multi_index

EOS仿造Boost库中的Multi-Index Containers,开发了C++类 eosio::multi_index(以下简称为multi_index),中文也可以叫作多索引表类。通过这个API,我们可以很简单地支持数据库表的多键排序、查找、使用上下限等功能。这个新的API使用迭代器接口,可显著提升扫表的性能。

四、编写智能合约

(一)定义程序基本结构

在链所在目录下新建一个addressbook文件夹,在addressbook文件夹中创建一个addressbook.cpp文件。

cd your_contract_path
mkdir addressbook
cd addressbook
touch addressbook.cpp

引入头文件、命名空间,

#include <eosio/eosio.hpp>

using namespace eosio;

定义合约类addressbook和其构造函数。合约类应当继承自eosio::contract。eosio::contract具有三个保护的成员,和众多公有成员函数。其中三个保护成员如下:

类型

名称

意义

eosio::name

_self

部署此账户的合约名称

eosio::name

_first_receiver

首次收到传入操作的账户

datastream< const char * >

_ds

合约的数据流

在声明派生类构造函数时,需要指明这三个成员。

class [[eosio::contract("addressbook")]] addressbook : public eosio::contract {
 public:
 addressbook(name receiver, name code, datastream<const char*> ds):
 contract(receiver, code, ds) {}
 
 private:
 
};

(二)定义数据表结构及索引

1、定义结构体

首先使用struct关键字创建一个结构体,然后用[[eosio::table]]标注这个结构体是一个合约表,这里声明了一个person结构体:

private:
struct [[eosio::table]] person {
 name key;
 std::string first_name;
 std::string last_name;
 uint64_t age;
 std::string street;
 std::string city;
 std::string state;
 };

类型说明:

name:名称类型,账号名、表名、动作名都是该类型,只能使用26个小写字母和1到5的数字,特殊符号可以使用小数点,必须以字母开头且总长不超过12。

uint64_t:无符号64位整数类型,表主键、name实质都是该类型。

这里需要注意,合约的表名与结构体的名称没有关系,因此结构体的名称不必遵循name类型的规则。

表的结构如下:

类型

名称

意义

eosio::name

key

主键 账户名

string

first_name

名字

string

last_name

姓氏

uint64_t

age

年龄

string

street

街道

string

city

城市

string

state

2、定义主键

传统数据库表通常有唯一的主键,它允许明确标识表中的特定行,并为表中的行提供标准排列顺序。

EOS合约数据库支持类似的语义,但是在multi_index容器中主键必须是唯一的无符号64位整数(即uint64_t类型)。multi_index中的对象按主键索引,以无符号64位整数主键的升序排列。

接下来我们定义一个主键函数,上文中已经说明name类型实质上是 uint64_t类型,该函数使用key.value返回一个uint64_t类型的值,并且由于key字段的含义是EOS中的账户名,因此可以保证唯一性:

uint64_t primary_key() const { return key.value;}

3、定义二级索引

multi_index容器中非主键索引可以是:

  • uint64_t

  • uint128_t

  • double

  • long double

  • eosio::checksum256

常用的是uint64_t和double类型。

使用age字段作为二级索引:

uint64_t primary_key() const { return key.value;}

4、定义多索引表

合约里的表都是通过multi_index容器来定义,我们将上面定义的person结构体传入multi_index容器并配置主键索引和二级索引:

using address_index = eosio::multi_index<"people"_n, person,
indexed_by<"byage"_n, const_mem_fun<person, uint64_t, &person::get_secondary>>;
>;

说明:

  • _n操作符用于定义一个name类型,上述代码将“people” 定义为name类型并作为表名;

  • person结构体被传入作为表的结构;

  • indexed_by结构用于实例化索引,第一个参数“byage”_n为索引名,第二个参数const_mem_fun为函数调用运算符,该运算符提取const值作为索引键。本例中,我们将其指向之前定义的getter函数get_secondary。

使用上述定义,现在我们有了一个名为people的表,目前addressbook.cpp的完整代码如下:

#include <eosio/eosio.hpp>

using namespace eosio;

class [[eosio::contract("addressbook")]] addressbook : public eosio::contract {
 public:
  
  // 构造函数,调用基类构造函数
 addressbook(name receiver, name code,  datastream<const char*> ds): contract(receiver, code, ds) {}
 
 private:
  // 表结构
 struct [[eosio::table]] person {
  name key;
  std::string first_name;
  std::string last_name;
  uint64_t age;
  std::string street;
  std::string city;
  std::string state;
  
  uint64_t primary_key() const { return key.value; }
  uint64_t get_secondary_1() const { return age; }
  
 };
 
 // 定义多索引表
 using address_index = eosio::multi_index<"people"_n, person, indexed_by<"byage"_n, const_mem_fun<person, uint64_t, &person::get_secondary_1>>>;
 
};

(三)定义数据操作方法

定义好表的结构后,我们通过[[eosio::action]]来定义对数据进行增删改的基本动作。

1、增添和修改动作

首先是提供了插入或修改数据的动作upsert,为了简化用户体验,使用单一方法负责行的插入和修改,并将其命名为 “upsert” ,即 “update” 和 “insert” 的组合。

该方法的参数应当包括所有需要存入people表的信息成员。

public:

[[eosio::action]]
void upsert(
 name user,
 std::string first_name,
 std::string last_name,
 uint64_t age,
 std::string street,
 std::string city,
 std::string state
) {}

一般来说,用户希望只有自己能对自己的记录进行更改,因此我们使用 require_auth() 来验证权限,此方法接收name类型参数,并断言执行该动作的账户等于接收的值,具有执行动作的权限:

[[eosio::action]]
void upsert(name user, std::string first_name, std::string last_name, uint64_t age, std::string street, std::string city, std::string state) {
  require_auth( user );
}

现在我们需要实例化已经定义配置好的表,上面我们已配置多索引表并将其声明为address_index,现在要实例化表,需要两个参数:

  • 第一个参数code,它指定此表的所有者,在这里,表的所有者为合约所部署的账户,我们使用get_first_receiver()函数传入,get_first_receiver() 函数返回该动作的第一个接受者的name类型名字。

  • 第二个参数scope,它确保表在此合约范围内的唯一性。在这里,我们使用get_first_receiver().value传入。

 address_index addresses(get_first_receiver(), get_first_receiver().value);

接下来,查询迭代器,并用变量iterator来接收:

auto iterator = addresses.find(user.value);

之后我们可以使用emplace() 函数和modify() 函数来插入或修改记录。当多索引表中未查询到该账户的记录时,使用emplace() 向表中添加;当多索引表中查询到过往记录时,使用modify() 修改原有记录。

 if( iterator == addresses.end() )
  {
    //增添
    addresses.emplace(user, [&]( auto& row ) {
      row.key = user;
      row.first_name = first_name;
      row.last_name = last_name;
      row.age = age;
      row.street = street;
      row.city = city;
      row.state = state;
    });
  }
  else {
    //修改
    addresses.modify(iterator, user, [&]( auto& row ) {
      row.key = user;
      row.first_name = first_name;
      row.last_name = last_name;
      row.age = age;
      row.street = street;
      row.city = city;
      row.state = state;
    });
  }

2、删除动作

与上文类似,定义erase动作提供删除数据的动作,查询迭代器。

[[eosio::action]]
  void erase(name user) {
    require_auth(user);

    address_index addresses(get_self(), get_first_receiver().value);

    auto iterator = addresses.find(user.value);
  
  }

这里使用check() 判断表中是否存在该记录,若不存在则给出报错,存在则使用erase删除该项。

 check(iterator != addresses.end(), "Record does not exist");
    addresses.erase(iterator);

3、保存文件

加入以上两个动作后,目前addressbook.cpp的完整代码如下:

#include <eosio/eosio.hpp>

using namespace eosio;

class [[eosio::contract("addressbook")]] addressbook : public eosio::contract {

public:

  addressbook(name receiver, name code,  datastream<const char*> ds): 
  contract(receiver, code, ds) {}

  [[eosio::action]]
  void upsert(name user, std::string first_name, std::string last_name, uint64_t age, std::string street, std::string city, std::string state) {
    require_auth( user );
    address_index addresses(get_first_receiver(),get_first_receiver().value);
    auto iterator = addresses.find(user.value);
    if( iterator == addresses.end() )
    {
      addresses.emplace(user, [&]( auto& row ) {
       row.key = user;
       row.first_name = first_name;
       row.last_name = last_name;
       row.age = age;
       row.street = street;
       row.city = city;
       row.state = state;
      });
    }
    else {
      addresses.modify(iterator, user, [&]( auto& row ) {
        row.key = user;
        row.first_name = first_name;
        row.last_name = last_name;
        row.age = age;
        row.street = street;
        row.city = city;
        row.state = state;
      });
    }
  }

  [[eosio::action]]
  void erase(name user) {
    require_auth(user);

    address_index addresses(get_self(), get_first_receiver().value);

    auto iterator = addresses.find(user.value);
    check(iterator != addresses.end(), "Record does not exist");
    addresses.erase(iterator);
  }

private:
  struct [[eosio::table]] person {
    name key;
    std::string first_name;
    std::string last_name;
    uint64_t age;
    std::string street;
    std::string city;
    std::string state;

    uint64_t primary_key() const { return key.value; }
    uint64_t get_secondary_1() const { return age; }

  };

  using address_index = eosio::multi_index<"people"_n, person, indexed_by<"byage"_n, const_mem_fun<person, uint64_t, &person::get_secondary_1>>>;

};

五、部署测试

(一)部署

1、创建addressbook账户

cleos create account eosio addressbook EOS7yK5K2mRCijPpRzStvucn53D4SybVge8uQ3hSnLaUvT4CPXzgo

注意其中:

EOS7yK5K2mRCijPpRzStvucn53D4SybVge8uQ3hSnLaUvT4CPXzgo 是存储于cleos钱包中的一个公钥,实际开发情况中需根据自己钱包中存储的公钥进行替换。

2、进入智能合约所在目录

cd your_contract_path/addressbook

3、编译

eosio-cpp-abigen-oaddressbook.wasmaddressbook.cpp

4、部署合约到addressbook账户上

cd ..
cleos set contract addressbook addressbook

(二)测试

1、创建两个测试账户alice和bob

cleos create account eosio alice 公钥
cleos create account eosio bob 公钥

2、插入数据

调用upsert动作插入数据

cleos push action addressbook upsert '["alice", "alice", "liddell", 9, "123 drink me way", "wonderland", "amsterdam"]' -p alice@active
cleos push action addressbook upsert '["bob", "bob", "is a guy", 49, "doesnt exist", "somewhere", "someplace"]' -p bob@active

插入成功,运行结果如下

3、查询数据

查询信息一般在命令行使用cleos get table进行:

cleos get table 拥有表的账户 表所在合约名 表名

此条命令可以查询出表内的所有信息,也可以通过添加后续的约束来查询指定信息:

  • --upper XX等于或在此之前

  • --lower XX等于或在此之后

  • --key-type XXX类型

  • --index X根据第几个索引

  • --limit XX显示前几个数据

我们可以通过主键key查询表的数据

cleos get table addressbook addressbook people --lower alice

--lower alice表示查询的下界,以“alice”作为下界可以查询到两条记录:

接下来通过二级索引age查询数据

cleos get table addressbook addressbook people --upper 10 \
--key-type i64 \
--index 2

--upper 10表示查询上界,即查询索引字段小于等于10的记录。--index 2表示使用二级索引查询。查询到一条记录:

4、修改数据

调用upsert动作修改数据:

cleos push action addressbook upsert '["alice", "mary", "brown", 9, "123 drink me way", "wonderland", "amsterdam"]' -p alice@active

查询后可以看到记录的first_name和last_name字段已被修改:

5、删除数据

调用erase动作删除数据:

cleos push action addressbook erase '["alice"]' -p alice@active

删除后查询不到alice,说明删除成功:

六、常见问题

更改数据表结构

需要注意的是,如果合约已经部署到合约账户上且表中已经存储了数据,那在更改表的结构如添加删除索引或字段时,则需要将表中的数据全部删除后,再将新的合约部署到合约账户上。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/160402.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

17.Isaac教程--机器学习流程

机器学习流程 ISAAC教程合集地址: https://blog.csdn.net/kunhe0512/category_12163211.html 文章目录机器学习流程模拟训练PC 和边缘设备上的推理使用 TensorRT 进行推理Torch推理使用 Tensorflow 进行推理示例PyCodelet配套代码Tensorflow 推理小码TensorRT 推理小码SampleAc…

23种设计模式(三)——模板方法模式【组件协作】

文章目录意图什么时候使用模板方法真实世界类比常说的钩子模板方法的实现模板方法模式的优缺点亦称&#xff1a; Template Method 意图 它的主要思想是&#xff0c;定义一个操作的一系列步骤&#xff0c;对于某些暂时确定不下来的步骤&#xff0c;就留给子类去实现好了&#x…

位图与矢量图的区别

相信大家在日常的工作过程中&#xff0c;经常会听到位图和矢量图&#xff0c;那么位图和矢量图的区别是什么呢&#xff1f; 1.定义 ①位图&#xff1a;称为点阵图像或栅格图像&#xff0c;是由称作像素&#xff08;图片元素&#xff09;的单个点组成的。这 些点可以进行不同的排…

测试用例设计方法有哪些?举例说明

众所周知&#xff0c;测试用例是编制的一组测试输入、执行条件及预期结果&#xff0c;专门为的是某个特殊目标&#xff0c;即测试某个程序路径&#xff0c;或是核实是否满足某个特定的需求。一般来讲&#xff0c;常用的测试用例设计方法有五种&#xff0c;分别是&#xff1a;正…

微信小程序中如何实现双向绑定

一、双向绑定 在微信小程序中如何实现双向绑定&#xff1f;在开始之前先介绍下什么是双向绑定&#xff0c;js中定义变量数据后&#xff0c;通过{{}}绑定到模板中&#xff0c;这个过程是单向绑定&#xff0c;即数据的更新只能是js中更新了数据&#xff0c;模板中跟着修改。但是如…

深度学习笔记:神经网络(3)

关于本章之前内容可以参考以下之前文章&#xff1a; 1 https://blog.csdn.net/Raine_Yang/article/details/128473486?spm1001.2014.3001.5501 2 https://blog.csdn.net/Raine_Yang/article/details/128584916?spm1001.2014.3001.5501 神经网络的输出层设计 机器学习问题一般…

2022年12月青少年软件编程(Python) 等级考试试卷(三级)

2022. 12 青少年软件编程&#xff08;Python&#xff09; 等级考试试卷&#xff08;三级&#xff09; 一、 单选题(共 25 题&#xff0c; 共 50 分) 1.列表 L1 中全是整数&#xff0c; 小明想将其中所有奇数都增加 1&#xff0c; 偶数不变&#xff0c; 于是编写了如下图 所示的…

高级树结构之平衡二叉树(ALV树)

文章目录平衡二叉树简介失衡类型&处理办法RR型失衡【左旋调整】调整演示代码实现LL型失衡【右旋调整】调整演示代码实现RL型失衡【先右旋后左旋调整】调整演示代码实现LR型失衡【先右旋后左旋调整】调整演示代码实现插入操作综合代码演示平衡二叉树简介 在数据有序的情况下…

Codeforces Round #842 (Div. 2)

Codeforces Round #843 (Div. 2) 传送门 不想搞的很累&#xff0c;对自己不做要求&#xff0c;有兴趣就做。 A. Greatest Convex #include <bits/stdc.h>using namespace std; const int maxn 1e6 10; vector<int> cnt[maxn]; map<int, int> mp;int mai…

nvm安装 疑难问题解决

nvm介绍 NVM是node.js的版本管理器&#xff0c;设计为每个用户安装&#xff0c;并在每个shell中调用。nvm可以在任何兼容posix的shell (sh、dash、ksh、zsh、bash)上运行&#xff0c;特别是在这些平台上:unix、macOS和windows WSL。 nvm安装 &#xff01;&#xff01;重要&a…

强化学习在智能补货场景的应用

本文作者&#xff1a;应如是&#xff0c;观远算法团队工程师&#xff0c;毕业于伦敦帝国理工学院计算机系&#xff0c;主要研究方向为强化学习、时间序列算法及其落地应用。深耕零售消费品场景&#xff0c;解决供应链运筹优化问题。为客户提供基于机器学习的AI解决方案。1. 背景…

2023.Q1 go语言记录

1. Go 语言数组声明和初始化var variable_name [SIZE] variable_type&#xff0c;eg&#xff1a;var balance [10] float32var balance [5]float32{1000.0, 2.0, 3.4, 7.0, 50.0}balance : [5]float32{1000.0, 2.0, 3.4, 7.0, 50.0}长度不确定的初始化var balance [...]float…

少儿Python每日一题(21):八皇后问题

原题解答 本次的题目如下所示: 会下国际象棋的人都很清楚:皇后可以在横、竖、斜线上不限步数地吃掉其他棋子。如何将8个皇后放在棋盘上(有8 8个方格),使它们谁也不能被吃掉!这就是著名的八皇后问题。 对于某个满足要求的8皇后的摆放方法,定义一个皇后串a与之对应,即,…

【Ansible】Ansible Playbook 的任务控制

Ansible Playbook 的任务控制 文章目录Ansible Playbook 的任务控制一、Ansible 任务控制基本介绍二、条件判断1.解决第一个问题2.nginx 语法校验三、循环控制四、Tags 属性五、Handlers 属性一、Ansible 任务控制基本介绍 任务控制类似于编程语言中的 if …、for …等逻辑控制…

MSF社会工程学

● Metasploit发现两个远程代码执行漏洞 ○ 问题都出在WEB组件方面 ○ MSF不受影响 ● 安全面前任何软件都是平等的 ○ 没有绝对安全的软件 为什么要说社会工程学 ● Metasploit可以很好的配合到社会工程学攻击的各个阶段 ● Setoolkit工具包大量依赖Metasploit ● 基于浏览器…

[SUCTF 2019]EasySQL

目录 预先知识 信息收集 思路 源码分析 非预期解 预期解 补充 预先知识 环境 use mysql; create table if not exists my_table( id int PRIMARY key auto_increment, name VARCHAR(20), age int); insert into my_table values(NULL,xiao,19); insert into my_table v…

BUFF80双模蓝牙5.2热插拔PCB

键盘使用说明索引&#xff08;均为出厂默认值&#xff09;软件支持一些常见问题解答&#xff08;FAQ&#xff09;首次使用测试步骤蓝牙配对规则&#xff08;重要&#xff09;蓝牙和USB切换键盘默认层默认触发层0的FN键配置的功能默认功能层1配置的功能默认的快捷键蓝牙参数蓝牙…

Jetpack Compose中的Canvas

Jetpack Compose中的Canvas API 使用起来感觉比传统View中的要简单一些&#xff0c;因为它不需要画笔Paint和画布分开来&#xff0c;大多数直接就是一个函数搞定&#xff0c;当然也有一些限制。 Compose 直接提供了一个叫 Canvas 的 Composable 组件&#xff0c;可以在任何 Co…

containerd环境下build镜像

containerd环境下build镜像安装nerdctl使用nerdctl打包docker镜像下载安装 buildkit编写systemd unit文件&#xff1a;启用buildkit.service并设置开机自动运行修改Dockerfile构建镜像containerd配置代理containerd配置代理ansible剧本安装nerdctl https://blog.csdn.net/omai…

Python采集最热影评 + 制作词云图

人生苦短&#xff0c;我用Python 电影评论&#xff0c;简称影评 是对一部电影的导演、演员、镜头、摄影、剧情、线索、环境、色彩、光线、视听语言、道具作用、转场、剪辑等进行分析和评论。 电影评论的目的在于分析、鉴定和评价蕴含在银幕中的审美价值、认识价值、社会意义、…