Appearance
开放数据联盟链智能合约
TIP
ODC 智能合约引擎基于 JVM 构建,提供 SpringBoot Starter 方便快速创建合约与应用集成,ODC 合约支持多协议接入如 TCP,UDP(QUIC),MQTT,WebSocket,RSocket,HTTP/HTTPS 等,默认提供 HTTP/HTTPS 服务支持。
创建一个 ODC 智能合约
TIP
以下示例基于 JAVA 语言及 springboot starter 构建的合约引擎,IDE 使用 IntelliJ IDEA 2022.1.4 版本 推荐使用 JDK 17+(LTS)版本,下载地址:https://www.oracle.com/java/technologies/downloads/#java17
# 查看已安装 JDK 版本
$ java -version
# 安装成功,输入命令显示
java version "17.0.4.1" 2022-08-18 LTS
Java(TM) SE Runtime Environment (build 17.0.4.1+1-LTS-2)
Java HotSpot(TM) 64-Bit Server VM (build 17.0.4.1+1-LTS-2, mixed mode, sharing)
创建一个项目 使用 IDEA 创建一个项目,使用 spring initializr 方式
注意
软件包名称必须使用以 cn.opendatachain 开始,其它名称会造成合约无法启动。 推荐使用 cn.opendatachain.子链名称 命名方式,包的层级数量无限制
导入需要的软件包
注意
推荐使用 springboot 版本为 2.6.x 推荐合约提供 HTTP/HTTPS 方式访问依赖类库为 sprng reactive web (ODC 采用基于异步非阻塞响应式框架 Reactor 进行研发,官方网站:https://projectreactor.io/) 其中 spring boot actuator 必选,提供合约运行状态监控,统计分析等功能
项目结构
项目创建成功后会生成以下目录结构并自动生成启动类
nmdc-contract
├── src
│ ├── main
│ │ ├── java
│ │ │ └── cn
│ │ │ └── opendatachain #软件包下包含智能合约源码
│ │ └── resources
│ │ └── application.yml #配置文件
│ └── test #测试智能合约代码
│ └── java
│ └── cn
├── target #项目编译后生成目录
│ ├── classes
│ │ ├── cn
│ │ │ └── opendatachain
│ │ └── application.yml
│ └── generated-sources
│ └── annotations
├── README.md #智能合约帮助文件
└── pom.xml #Maven 项目管理文件,包括依赖类库等
创建配置文件
新项目创建完成后一般会在 resources 目录下生成名为 application.properties 的配置文件,推荐修改后缀为 application.yml 使用 YMAL 语言,配置文件示例如下
注意:以下配置为普通合约配置属性,通用合约配置详见 智能合约进阶 章节
TIP
注意:bind-ip,bind-path 属性已从合约引擎中移除,详见 扩展及插件-WAF 章节 影响版本:ODC 合约引擎 odc-contract-spring-boot-starter > 1.2.0
# 合约开放端口
server:
port: 8000
# ODC 智能合约配置,以下所有信息可通过 https://console.opendatachain.cn 登录后获取
odc:
contract:
chain-name: 子链名称,必填
contract-name: 合约名称,必填
contract-public-key: 合约公钥,必填
contract-private-key: 合约私钥,使用合约进行签名时必须填写
nodeName: 在 ODC 控制台创建的节点名称,必填
node-public-key: 节点公钥,使用加密传输时必须填写
node-private-key: 节点私钥,必填
# 上链数据是否加密,使用 SM2 算法实现
is-encrypt: n
# 智能合约防火墙设置,IP 白名单,多个 IP 使用英文逗号进行分隔,其中 0.0.0.0 为对所有IP开放访问,注意使用白名单后需设置访问的请求头信息,详见智能合约进阶章节
#bind-ip: 0.0.0.0
# URL 白名单,多个 URL 使用英文逗号进行分隔,其中 all 为所有合约开放接口地址都可以访问,包括 swagger 接口文档
#bind-path: all
# 设置日志输出级别
logging:
level:
cn.opendatachain: debug
org.reflections: error
添加依赖类库
项目使用 maven 进行管理,pom.xml 示例如下
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.6.14</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>cn.opendatachain</groupId>
<artifactId>nmdc-contract</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>nmdc-contract</name>
<description>nmdc-contract</description>
<!-- 推荐使用 jdk 17 -->
<properties>
<java.version>11</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!-- ODC 合约引擎 springboot starter -->
<dependency>
<groupId>cn.opendatachain</groupId>
<artifactId>odc-contract-spring-boot-starter</artifactId>
<version>1.2.0</version>
</dependency>
<!-- ODC 辅助工具类 -->
<dependency>
<groupId>cn.opendatachain</groupId>
<artifactId>odc-common</artifactId>
<version>1.1.0</version>
</dependency>
<!-- springdoc 文档 -->
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-webflux-core</artifactId>
<version>1.6.9</version>
</dependency>
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-webflux-ui</artifactId>
<version>1.6.9</version>
</dependency>
<!-- lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- 测试 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.6.10</version>
</plugin>
</plugins>
</build>
</project>
创建上链数据对象
数据上链对象可根据实际业务需求进行定制开发,如需了解更多请参考 智能合约进阶 章节
package cn.opendatachain.nmdccontract;
import lombok.Data;
/**
* Dataset 上链数据集示例
* @author 郭志斌
* @version nmdc-contract 1.0.0.RELEASE
* <b>Creation Time:</b> 2022/7/22 14:06
*/
@Data
public class Dataset {
/**
* 数据集标题
*/
private String title;
/**
* 数据集标识 CSTR
*/
private String cstr;
/**
* 数据集关键字
*/
private String keyword;
// 除基础数据类型外,合约支持存储复杂 json 数据格式,属性可包括 List<Object>,Map<String.Object> 等数据结构,以实际上链需求为准
}
创建合约
合约引擎会提供数据上链,查询历史状态及获取最新状态数据的接口,代码示例如下
TIP
数据上链新增及修改使用相同接口,通过调用 transaction.upsert(payload) 方法实现,确定执行的操作是通过上链数据的 UID 唯一标识来进行验证,如标识唯一且不重复为新增,反之则为修改。
package cn.opendatachain.nmdccontract;
import cn.opendatachain.common.core.domain.ContractPayload;
import cn.opendatachain.common.rsocket.ODCResponse;
import cn.opendatachain.contract.annotation.ODCContract;
import cn.opendatachain.contract.context.Context;
import cn.opendatachain.contract.context.Transaction;
import cn.opendatachain.contract.util.JsonUtils;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Mono;
/**
* TestContract ODC 智能合约示例
*
* @author 郭志斌
* @version nmdc-contract 1.0.0.RELEASE
* <b>Creation Time:</b> 2022/9/13 15:44
*/
@Slf4j
@RestController
@Tag(name = "测试智能合约")
@ODCContract(name = "test-contract")
public class TestContract {
/**
* 智能合约上下文
*/
private final Context context;
/**
* JSON 工具类
*/
private final JsonUtils jsonUtils;
/**
* 构造方法 注入合约引擎上下文及其他对象,也可以使用 @Autowired 进行对象注入
*
* @param context 上下文
* @param jsonUtils 工具类
*/
public TestContract(Context context, JsonUtils jsonUtils) {
this.context = context;
this.jsonUtils = jsonUtils;
}
/**
* 数据上链
*
* @param dataset 数据上链对象
* @return Mono<ODCResponse>
*/
@Operation(summary = "数据上链")
@PostMapping("/test.upsert")
public Mono<ODCResponse> upsert(@RequestBody Dataset dataset) {
//获取交易对象
Transaction transaction = context.getTransaction();
//转换为 json 数据格式
String json = jsonUtils.toJson(dataset);
log.debug("获取原始上链数据 json :{}", json);
// 省略获取 Dataset 后数据的校验
//构建数据上链相关参数及签名
ContractPayload payload = transaction.payloadBuilder(dataset.getCstr(), json);
log.debug("ContractPayload : {}", jsonUtils.toJson(payload));
// 数据上链
return transaction.upsert(payload);
}
/**
* 批量数据上链 - 如何调用智能合约接口详见智能合约进阶章节
*
* @param dataset 数据上链对象
* @return Mono<ODCResponse>
*/
@Operation(summary = "批量数据上链")
@PostMapping("/test.upsert.batch")
public Publisher<ODCResponse> upsertBatch(@RequestBody List<Dataset> datasets) throws IllegalAccessException {
//获取交易对象
Transaction transaction = context.getTransaction();
//获取批量数据并转换为可上链对象 ContractPayload 列表
List<ContractPayload> payloads = datasets.stream().map(ds ->
transaction.payloadBuilder(ds.getCstr(), jsonUtils.toJson(ds))
).collect(Collectors.toList());
log.debug("ContractPayloads size is : {}", payloads.size());
// 批量数据上链
return transaction.upsertBatch(payloads);
}
/**
* 数据上链 - 不存储状态数据
*
* @param dataset 数据上链对象
* @return Mono<ODCResponse>
*/
@Operation(summary = "数据上链-不存储状态数据")
@PostMapping("/dataset.upsert.notSaveStatus")
public Mono<ODCResponse> notSaveStatus(@RequestBody Dataset dataset) {
//获取交易对象
Transaction transaction = context.getTransaction();
//转换为 json 数据格式
String json = jsonUtils.toJson(dataset);
log.debug("接受数据 json :{}", json);
//构建数据上链对象及签名信息
ContractPayload payload = transaction.payloadBuilder(dataset.getCstr(), json);
log.debug("ContractPayload : {}", jsonUtils.toJson(payload));
// 数据上链
return transaction.notSaveStatus(payload);
}
/**
* 历史数据查询
*
* @param uid 数据标识,推荐使用 cstr
* @return Mono<ODCResponse>
*/
@Operation(summary = "历史数据查询")
@GetMapping("/test.history")
public Mono<ODCResponse> history(@RequestParam(name = "uid") String uid) {
//获取交易对象
Transaction transaction = context.getTransaction();
//构建数据查询相关参数及签名
ContractPayload payload = transaction.payloadQueryBuilder(uid);
log.debug("ContractPayload : {}", jsonUtils.toJson(payload));
// 查询历史
return transaction.history(payload);
}
/**
* 获取状态数据查询
*
* @param uid 数据标识,推荐使用 cstr
* @return Mono<ODCResponse>
*/
@Operation(summary = "获取最新状态数据")
@GetMapping("/test.latestStatus")
public Mono<ODCResponse> latestStatus(@RequestParam(name = "uid") String uid) {
//获取交易对象
Transaction transaction = context.getTransaction();
//构建数据查询相关参数及签名
ContractPayload payload = transaction.payloadQueryBuilder(uid);
log.debug("ContractPayload : {}", jsonUtils.toJson(payload));
return transaction.latestStatus(payload);
}
}
智能合约启动类
智能合约启动类为合约启动入口,配置完成后可运行及调试,代码示例如下
package cn.opendatachain.nmdccontract;
import cn.opendatachain.contract.OpenDataChainContract;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* TestContractApp 智能合约启动类
*
* @author 郭志斌
* @version nmdc-contract 1.0.0.RELEASE
* <b>Creation Time:</b> 2022/7/23 22:29
*/
@SpringBootApplication
public class TestContractApp extends OpenDataChainContract{
//程序启动入口
public static void main(String[] args) {
OpenDataChainContract.run(NmdcContractApp.class,args);
}
}
通过上述步骤完成 ODC 智能合约的编写,更多用法请参考进阶指南智能合约部分内容
代码示例
TIP
智能合约代码示例请访问 https://gitee.com/scichain/odc-contract-sample