Skip to content
On this page

开放数据联盟链智能合约

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.子链名称 命名方式,包的层级数量无限制

299f300289a659a15f1251ed2879eb64.png

导入需要的软件包

注意

推荐使用 springboot 版本为 2.6.x 推荐合约提供 HTTP/HTTPS 方式访问依赖类库为 sprng reactive web (ODC 采用基于异步非阻塞响应式框架 Reactor 进行研发,官方网站:https://projectreactor.io/) 其中 spring boot actuator 必选,提供合约运行状态监控,统计分析等功能

3425a7a50f00b0d0889f34afa499072b.png

项目结构

项目创建成功后会生成以下目录结构并自动生成启动类

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

版权所有@2019-2022 中国科学院计算机网络信息中心 京ICP备 09112257号-115