利用趣链作为底链,通过java智能合约进行数据上链

介绍

本java项目举例说明如何将person数据上链及获取链上数据。完整代码:https://github.com/forReak/hyperTest

用趣链官网得Hyperchain,开源版本,作为底链,搭建一个节点。

底链地址:https://github.com/hyperchain/hyperchain

底链相关文档:https://docs.hyperchain.cn/docs/flato-solo/5.1-flato-sdk-litesdk

用本地虚拟机搭建一个节点,然后将数据存储到节点得合约中。以及通过合约获取合约中得数据

 

一、搭建节点

去官网下载 https://github.com/hyperchain/hyperchain/releases

用docker得话直接创建镜像

 

本地虚拟机安装后

 

注意端口是8081,节点部署完成

 

二、创建java项目--pom.xml

java项目即是智能合约,将项目打成jar包部署到链上,之后通过api调用链上得智能合约进行保存读取数据。注意,数据是持久化到合约中得。

新建maven项目,pom文件如下,注意打包方式为jar,构建方式是普通jar包构建,mainclass需要配置成自己得继承 BaseContract类 得类。后面会讲到。

<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.furao</groupId>
    <artifactId>hyperTest</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>jar</packaging>
    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>
        <dependency>
            <groupId>cn.hyperchain</groupId>
            <artifactId>litesdk</artifactId>
            <version>1.0.0</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.13.1</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-jar-plugin</artifactId>
                <version>3.2.0</version>
                <configuration>
                    <archive>
                        <manifest>
                            <mainClass>
                                org.legalperson.MyContractImpl
                            </mainClass>
                        </manifest>
                    </archive>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

 

三、创建person类

定义一个人类,后面需要将个人信息传入到链上

package org.legalperson;

import java.util.Date;

public class Person {
    String id ;
    String name;

    Integer age;

    String copAddr;

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }


    public String getCopAddr() {
        return copAddr;
    }

    public void setCopAddr(String copAddr) {
        this.copAddr = copAddr;
    }

    @Override
    public String toString() {
        return "Person{" +
                "id='" + id + '\'' +
                ", name='" + name + '\'' +
                ", age=" + age +
                ", copAddr='" + copAddr + '\'' +
                '}';
    }
}

四、创建 MyContract.java 接口类

合约接口类描述这个合约能干什么。比如当前代码中,有俩方法,一个是保存数据,一个是读取数据。注意合约接口类需要继承 BaseContractInterface

package org.legalperson;

import cn.hyperchain.contract.BaseContractInterface;

public interface MyContract extends BaseContractInterface {

    String saveData(Person person);

    String getData(String id);
}

 

五、创建合约 InvokeMyContract.java 合约调用类

这个类为调用合约得入口。当把合约部署到链上得时候,就通过这个类进行数据传输,把数据发给链上得合约。

注意:

1、这个类需要 实现 BaseInvoke 。并且 实现一个invoke方法。invoke方法的入参和返回值就是 BaseInvoke 的泛型,第一个是合约得返回值,第二个是合约得入参。

也就是说这个合约不管怎么样,只能通过invoke方法进行 入参和返回参数。相当于main方法。

2、所以合约的入参和返回值需要考虑通用。比如在本代码中,invoke方法里的调用的两个方法都返回了string类型。一个是保存数据,一个是读取数据。

3、需要有一个空构造函数

4、需要有一个所有入参的构造函数,用于接收入参。因为数据是通过构造函数来传输的。

 

package org.legalperson;

import cn.hyperchain.contract.BaseInvoke;

public class InvokeMyContract implements BaseInvoke<String, MyContract> {

    public int function;

    public Person person;

    public String id;

    public InvokeMyContract() {
    }

    public InvokeMyContract(int function, Person person, String id) {
        this.function = function;
        this.person = person;
        this.id = id;
    }

    @Override
    public String invoke(MyContract myContract) {
        if(function == 1){
            return myContract.saveData(person);
        }
        else{
            return myContract.getData(id);
        }
    }
}

六、合约实现类 MyContractImpl.java

有了定义,有了入口,则需要创建合约实现类,来实现合约的功能。

需要注意:

1、实现类需要继承 BaseContract 实现 第四步的接口实现合约接口中的功能。

2、链上数据是保存在合约中的持久化变量中的,因此需要在合约中定义一个变量,用注解 @StoreField 进行标识持久化字段 

3、实现接口中的方法。本代码中就是将数据保存到变量里。以及读取变量中的数据。

package org.legalperson;

import cn.hyperchain.annotations.StoreField;
import cn.hyperchain.core.HyperMap;
import cn.hyperchain.contract.BaseContract;

public class MyContractImpl extends BaseContract implements MyContract{

    @StoreField
    public HyperMap<String, Person> personMap = new HyperMap<String, Person>();

    public MyContractImpl() {
    }

    @Override
    public String saveData(Person person) {
        personMap.put(person.getId(),person);
        return "true";
    }

    @Override
    public String getData(String id) {
        return personMap.get(id).toString();
    }
}

 

七、打包

打成普通jar包,需要一个mainclass,指向你的合约实现类

maven的配置如下:

<build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-jar-plugin</artifactId>
                <version>3.2.0</version>
                <configuration>
                    <archive>
                        <manifest>
                            <mainClass>
                                org.legalperson.MyContractImpl
                            </mainClass>
                        </manifest>
                    </archive>
                </configuration>
            </plugin>
        </plugins>
    </build>

 

打包完成后,在target 文件夹中出现jar,jar包结构如下

 

 

八、部署合约,让合约上链

(1)创建测试类 TestPerson

注意里面的变量,一个jar包路径,一会儿需要上链的,一个虚拟机ip,装链节点的,一个合约链上地址。先为空。一会儿合约上链后,需要手动更改这个地址。

public class TestPerson {
    //合约jar包路径
    String jarPath = "C:\\Users\\furao\\IdeaProjects\\hyperTest\\hyperTest\\target\\hyperTest-1.0-SNAPSHOT.jar";
    String defaultURL = "192.168.204.130:8081";

    String addr = "";
}

 (2)添加获取节点管理器

根据官网文档描述,需要创建一个方法,用于链接节点。在TestPerson类中添加此方法

/**
 * 获取节点管理器
 * @return
 */
public ProviderManager getProviderManager(){
    //负责管理与节点的连接
    DefaultHttpProvider defaultHttpProvider = new DefaultHttpProvider.Builder().setUrl(defaultURL).build();
    //rovideManager负责集成、管理这些HttpProvider
    return ProviderManager.createManager(defaultHttpProvider);
}

 (3)添加部署合约方法

原理是把合约jar包通过文件流的形式传到链上。按官网文档来写。如果你的合约写的有问题,部署合约时这个方法会报错。在TestPerson类中添加此方法

public String deploy() throws IOException, RequestException {
    InputStream is = FileUtil.readFileAsStream(jarPath);
    ProviderManager providerManager = getProviderManager();
    ContractService contractService = ServiceManager.getContractService(providerManager);
    AccountService accountService = ServiceManager.getAccountService(providerManager);
    Account account = accountService.genAccount(Algo.ECRAW);
    //部署合约
    Transaction transaction = new Transaction.HVMBuilder(account.getAddress()).deploy(is).build();
    transaction.sign(account);
    TxHashResponse send = contractService.deploy(transaction).send();
    ReceiptResponse receiptResponse = send.polling();
    //获取合约地址
    String contractAddress = receiptResponse.getContractAddress();
    System.out.println("contract address: " + contractAddress);
    return contractAddress;
}

(3)添加 main 方法

TestPerson类中添加此方法

public static void main(String[] args) throws IOException, RequestException {
    TestPerson testPerson = new TestPerson();
    String addr = testPerson.deploy();

}

 

(4)执行main方法,部署合约

手动将打印的合约地址保存到addr变量里。这里其实合约已经部署完成了。这个地址就是合约在链上的地址。永远不会没有,改变。

 

(5)调用合约 -- 存储数据

添加调用合约 setPerson 方法。注意调用合约的时候,invoke里传入合约地址,以及你的合约入口类 InvokeMyContract ,并把数据通过构造函数的形式传递过去。这里通过构造把person 张三传过去了。然后构造的第一个参数,当是1时执行保存,当是其他,执行读取。参见 InvokeMyContract invoke 方法

public void setPerson() throws RequestException {
    ProviderManager providerManager = getProviderManager();
    ContractService contractService = ServiceManager.getContractService(providerManager);
    AccountService accountService = ServiceManager.getAccountService(providerManager);
    Account account = accountService.genAccount(Algo.ECRAW);
    Person zhangsan = new Person();
    zhangsan.setId("001");
    zhangsan.setAge(18);
    zhangsan.setName("张三");
    zhangsan.setCopAddr("地址");

    //创建指定invoke bean的交易
    Transaction transaction = new Transaction.HVMBuilder(account.getAddress()).invoke(addr, new InvokeMyContract(1,zhangsan,"")).build();
    transaction.sign(account);
    ReceiptResponse receiptResponse1 = contractService.invoke(transaction).send().polling();
    //对交易执行结果进行解码
    String decodeHVM = Decoder.decodeHVM(receiptResponse1.getRet(), String.class);
    System.out.println("decode: " + decodeHVM);
}

 main方法稍微修改,

public static void main(String[] args) throws IOException, RequestException {
    TestPerson testPerson = new TestPerson();
    //String addr = testPerson.deploy();
    testPerson.setPerson();
    //testPerson.getPErson();

}

 执行main方法,调用合约,将数据上链,看到已经返回 true

 

(6)调用合约 -- 获取合约数据

其实同(5)中的setPerson方法。唯一区别是

new InvokeMyContract(2,p1,"001")

中传了2,获取数据

public void getPErson() throws RequestException {
    ProviderManager providerManager = getProviderManager();
    ContractService contractService = ServiceManager.getContractService(providerManager);
    AccountService accountService = ServiceManager.getAccountService(providerManager);
    Account account = accountService.genAccount(Algo.ECRAW);
    Person p1 = new Person();
    //创建指定invoke bean的交易
    Transaction transaction = new Transaction.HVMBuilder(account.getAddress()).invoke(addr, new InvokeMyContract(2,p1,"003")).build();
    transaction.sign(account);
    ReceiptResponse receiptResponse1 = contractService.invoke(transaction).send().polling();
    //对交易执行结果进行解码
    String decodeHVM = Decoder.decodeHVM(receiptResponse1.getRet(), String.class);
    System.out.println("decode: " + decodeHVM);
}

执行main方法如下

可见已经从map中获取了存储的数据

posted @ 2023-03-09 16:40  Furaooooo  阅读(162)  评论(0)    收藏  举报