因为本人在大学生软件创新大赛分配的任务为在CP-ABE加密中需要将密文保存在智能合约中,完成这项工作花费了我2个月的时间,完成后发现如果现在的我穿越回去就一天时间就能完成这个任务,因此编写这篇文章一方面为了回忆,另一方面给大家参考。

        背景为:前端使用vue,后端使用java,springboot,MySQL等另外使用的环境还有remix,Ganache,IPFS。

首先说一下这俩月在干什么。

一个星期用来跑学长给的初始代码。搭配环境。

一个星期用来编写最原先的智能合约,因为当时不明白要做什么工作,思考的比较复杂。

又一个星期将智能合约代码转为java代码,对智能合约进行了输入调用。

接下期末考试+课程设计,因此基本没有进展时长20天

接下差不多一个星期进行复盘,对项目进行熟悉,因为正式进入寒假,就新开了一个单独测试文件。尝试了geth搭建私链,也搭建成功了,因为需要运行程序需要一直进行挖矿,因此过几天后进行淘汰。正式使用Ganache。

在这一个星期使用Ganache时发现了之前编写的智能合约不能在Ganache发布,具体报错为:Gas estimation errored with the following message (see below). The transaction execution will likely fail. Do you want to force sending?
Returned error: processing response error (body="{\"id\":89,\"jsonrpc\":\"2.0\",\"error\":{\"message\":\"VM Exception while processing transaction: invalid opcode\",\"stack\":\"RuntimeError: VM Exception while processing transaction: invalid opcode\\n at exactimate (C:\\\\Program Files\\\\WindowsApps\\\\GanacheUI_2.7.1.0_x64__rb4352f0jd4m2\\\\app\\\\resources\\\\static\\\\node\\\\node_modules\\\\ganache\\\\dist\\\\node\\\\1.js:2:182136)\",\"code\":-32000,\"name\":\"RuntimeError\",\"data\":{\"hash\":null,\"programCounter\":24,\"result\":\"0x\",\"reason\":null,\"message\":\"invalid opcode\"}}}", error={"code":-32000,"data":{"hash":null,"programCounter":24,"result":"0x","reason":null,"message":"invalid opcode"}}, requestBody="{\"method\":\"eth_estimateGas\",\"params\":等等。

发现这是由于智能合约版本的问题,下面是报错的智能合约代码:

// SPDX-License-Identifier: GPL-3.0
//pragma abicoder v2; // 启用 ABI 编码器 v2 特性
pragma solidity ^0.8.0;
contract message{
    
    mapping (string=> string[]) public I;
    function inputaddr(string memory TK, string memory addr) public{
        I[TK].push(addr);
    }
    event LogEvent(string[] l);
    function getsize(string memory TK) public view returns(uint256){
        return I[TK].length;
    }
    function outputaddr(string memory TK) public returns(string[] memory){
            emit LogEvent(I[TK]);
        return I[TK];
    }

}

发现是可以编译的,而且在测试链上也能运行,但是到自己的私链Ganache上就是不行,这个报错将Ganache断开就行了。

写到这里需要给出之前在测试链上发布的智能合约在调用读取函数时,一直出现pending问题,想来这里也是由于这个原因,这里大致的原因貌似是说调用这个合约无法预估出来gas花费为多少。让我放弃使用metamask测试链的主要原因就是这个pending,一直pending,然后过了几天才好,我一直以为是区块链忙,我使用的花销小,区块链没有理会我,当时还想是不是跟外国时间冲突了跟他们挤资源了,现在想来不是这个问题,此为用这个测试链在java中调用智能合约也不合适,因为会有一定的延迟,大概是过了3秒才有反应吧。

下面主要用图片讲解,文字应该没多少人看。

下面放的是正确的智能合约代码,实现的只是一个读取的功能。

// SPDX-License-Identifier: GPL-3.0
pragma abicoder v2; // 启用 ABI 编码器 v2 特性
pragma solidity ^0.7.0;
contract message{
    
    mapping (string=> string[]) public I;
    function inputaddr(string memory TK, string memory addr) public{
        I[TK].push(addr);
    }
    event LogEvent(string[] l);
    function getsize(string memory TK) public view returns(uint256){
        return I[TK].length;
    }
    function outputaddr(string memory TK) public returns(string[] memory){
            emit LogEvent(I[TK]);
        return I[TK];
    }

}

可以看到我是切换成了0.7的版本了,原本我参考的是一个读取CET4,6级成绩的区块链,那里的版本是0.4,在这里我得到了启发就进行慢慢升版本,最终升到0.7。接下来就水到渠成了。

下面是我进行的java读取测试代码:

package org.example;
import org.web3j.crypto.Credentials;
import org.web3j.protocol.Web3j;
import org.web3j.protocol.core.RemoteFunctionCall;
import org.web3j.protocol.core.methods.response.TransactionReceipt;
import org.web3j.protocol.http.HttpService;
import java.math.BigInteger;

public class Main {
    public static void main(String[] args) {
            Web3j web3j = Web3j.build(new HttpService("HTTP://127.0.0.1:8545"));
            BigInteger gasPrice = BigInteger.valueOf(20_000_000_000L);
            BigInteger gasLimit = BigInteger.valueOf(4_300_000);
            String privateKey = "0xe288a491f25708288b10a5a5d2b574e99a346eed8eda0e9109f0d8883e78768b";
            Credentials credentials = null;
            credentials = Credentials.create(privateKey);
            AddContract contract = AddContract.load(
                    "0x46291dBb726999012906A1d1A9B52f5EB972F640", // 合约地址
                    web3j, // Web3j 实例
                    credentials, // 包含你的私钥的 Credentials 实例
                    gasPrice, // Gas Price
                    gasLimit);
            TransactionReceipt receipt = null;
            try {
                receipt = contract.inputaddr("henan", "0x06Cb9c467DAA628De501e56510F44baA79241495").send();
                System.out.println("测试输入成功");
                System.out.println("调用合约前");
                RemoteFunctionCall<BigInteger> getnum=contract.getsize("henan");
                BigInteger numb=getnum.send();
                System.out.println(numb);
                for(BigInteger i = BigInteger.ZERO; i.compareTo(numb) < 0; i = i.add(BigInteger.ONE)) {
                    RemoteFunctionCall<String> setword = contract.I("henan", i);
                    String send = setword.send();
                    System.out.println(send);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
    }
}

详细代码解释可以扔给gpt。

如果需要进行测试,需要改变下面的端口;

Web3j web3j = Web3j.build(new HttpService("HTTP://127.0.0.1:8545"));//双引号中的url

String privateKey = "0xe288a491f25708288b10a5a5d2b574e99a346eed8eda0e9109f0d8883e78768b";//自己账号的私钥

AddContract contract = AddContract.load(
"0x46291dBb726999012906A1d1A9B52f5EB972F640", // 发布的合约地址

...........);

这是最基本的能够让你连接到智能合约。

下面是重中之重,智能合约的读取,智能合约通过web3j转为java代码,在java中调用智能合约。

首先是读取智能合约,

receipt = contract.inputaddr("henan", "0x06Cb9c467DAA628De501e56510F44baA79241495").send();

就是这么简单,详细可以看别的资料,我写过好几种读取的方法,最后还是这种好用。

接着是智能合约读取的调用:

由于我已经调用过4次读入合约且读入合约的输入一样,因此输出4次一模一样的string地址。

取出智能合约中的数据真的是到处都找不到啊,b站,csdn上都找不到适合我的,全是读取logs的数据,就这一个问题困扰了我有一个月,后来还是到b站找视频,找着找着突然发现人家在调用一个不是自己写的读出函数,才发现是智能合约自带的读取函数,比如下面的getsize和I函数。

web3j将智能合约转为java代码时也将这代码转换了,当时我看不懂转换后的代码,没有往这方面深究。这里要写一个当时我进入的一个误区。总所周知csdn上的东西比较老,因此我就下了一个比较老的web3j转换代码,为下面的版本

大家尽量不要用这个版本这个版本有各种问题,转换的代码也不行,在idea中都各种报错。

最终我下的是这个版本的web3j

下面是我的转换过程,这个可以网上自己查,先创建abi和bin文件,

C:\Users\23688\Desktop\web3j-4.5.16 (1)\web3j-4.5.16\bin>web3j solidity generate -b C:/Users/23688/Desktop/test/api/addContract.bin -a C:/Users/23688/Desktop/test/api/addContract.abi -o C:/Users/23688/Desktop/test/api/src/main/java -p com.example.api.service
QmTuFqGuDvNGkoTrL6uByGojyqnD9dopsbx7f4yTB2Aa9Z

首先win+r启动cmd然后输入cd C:\Users\23688\Desktop\web3j-4.5.16 (1)\web3j-4.5.16\bin 进入这个bin路径,不能在bin那个目录右键在终端中打开,否则会出现下面的错误:

下面是正确流程:

接着输入转换路径:

这代表转换成功了。具体的路径相信大家会自己改变,如果不懂可以问gpt,都是很好理解的,改成自己对应的路径。

接下来就去找到转换后的java代码,添加到自己想放的地址就行了。回到正题,

由于I函数每次调用需要输入地址和下标,因为我创建的I是一个string数组,它并不能一下返回全部的地址,但是我在测试链里测试的时候可以一下全部返回,但我这里就不去找了,我觉得我这个方法更好一点,获取数组的长度在获取值,缺点是调用次数多吧花费大。

下面写一点我在这2个月中遇到的其他问题:

第一个Maven包的更新,比如下面的okhttp依赖,这个依赖的问题是出现在前端调用后端智能合约时出现的错误,如下,这是包冲突的问题,在maven中添加一个4.3.1的okhttp依赖即可。

Exception: java.lang.NoSuchMethodError: No static method create(Ljava/lang/String;Lokhttp3/MediaType;)Lokhttp3/RequestBody; in class Lokhttp3/RequestBody; or its super classes (declaration of 'okhttp3.RequestBody' appears in /data/app/~~RN8nor63ml8VEBzzFTlgBw==/com.app.debug-i_6b_0V66n1AVMCDcJQLWg==/base.apk!classes5.dex)

        <dependency>
            <groupId>com.squareup.okhttp3</groupId>
            <artifactId>okhttp</artifactId>
            <version>4.3.1</version>
        </dependency>

添加完成后点击箭头那个东西进行更新,过一会就更新完成了。

Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐