前言:本文将从一个区块链入门小白的视角,来一步步的讲解如何实现区块链数据上链,链上数据查询,geth多节点同步。以及讲解在上链过程中,我踩过的坑及其解决方案。如果有不对的地方,还请大佬指教!🙇🙇🙇
声明:本文为作者Huathy原创文章,未经许可,禁止转载。否则依法追究责任!
文章目录
- 环境搭建
- GoLang环境安装
- Geth环境安装
- geth初始化
- 启动节点
- Geth节点同步
- 主节点控制台卡死、无法退出问题
- 部署智能合约
- 编写合约代码MyContract.sol
- 将智能合约转为Java代码
- 智能合约Java代理类使用
环境搭建
GoLang环境安装
- 版本安装 :https://studygolang.com/dl
基于go1.22.0.windows-amd64.msi (60MB)稳定版本 - gopath配置
Windows版本安装自动配置,或类似JavaHome配置
Geth环境安装
下载geth1.8.20版本:geth-windows-amd64-1.8.20-24d727b6.exe
配置环境变量:
geth初始化
- 编写创世区块配置文件genesis.json
{ "config": { "chainId": 1, "homesteadBlock": 0, "eip150Block": 0, "eip150Hash": "0x0000000000000000000000000000000000000000000000000000000000000000", "eip155Block": 0, "eip158Block": 0, "byzantiumBlock": 0, "constantinopleBlock": 0, "petersburgBlock": 0, "istanbulBlock": 0, "ethash": {} }, "nonce": "0x0", "timestamp": "0x5ddf8f3e", "extraData": "0x0000000000000000000000000000000000000000000000000000000000000000", "gasLimit": "0xffffffff", "difficulty": "0x00002", "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", "coinbase": "0x0000000000000000000000000000000000000000", "alloc": { }, "number": "0x0", "gasUsed": "0x0", "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000" }
- 执行初始化命令
geth init --datadir eth_node1 C:\env\Geth\genesis.json geth init --datadir eth_node2 C:\env\Geth\genesis.json
启动节点
geth --datadir "eth_node1" --port 30303 --ipcdisable --networkid 23 --rpc --rpcaddr "localhost" --rpcport "8546" --rpccorsdomain "*" --rpcapi "db,eth,net,web3" --cache 2048 --miner.threads 4 console 2
geth --datadir "eth_node2" --port 30304 --ipcdisable --networkid 23 --rpc --rpcaddr "localhost" --rpcport "8547" --rpccorsdomain "*" --rpcapi "db,eth,net,web3" console 2
# 日志重定向
geth --datadir "eth_node1" --port 30303 --ipcdisable --networkid 23 --rpc --rpcaddr "localhost" --rpcport "8546" --rpccorsdomain "*" --rpcapi "db,eth,net,web3" --cache 2048 --miner.threads 4 console 2>eth_node1.log
geth --datadir "eth_node2" --port 30304 --ipcdisable --networkid 23 --rpc --rpcaddr "localhost" --rpcport "8547" --rpccorsdomain "*" --rpcapi "db,eth,net,web3" console 2>eth_node2.log
Geth节点同步
- 在node1上获取enode地址
admin.nodeInfo.enode
- node2将node1地址添加为peer
# 将node1的enode地址添加到node2中 admin.addPeer("enode://46de00161f2bf8995b736dfa98a94187fe72c3b4324a96741348a0708ca90be35d728941aeeedcc6349365744bec2b64bc72ec2e2e0d2858d5cbae94ae227118@127.0.0.1:30303") # 使用admin.peers查看 admin.peers
- node1调用miner.start()开始掘金,node2开始同步
# 新建账户 personal.newAccount("huathy") # 查看账户 eth.accounts # 指定掘金账户 miner.setEtherbase("0xd8554b507a868d5d30ff3d2e14b92615c243831e") # 开始掘金 miner.start() # 停止掘金 miner.stop() # 查看同步状态 eth.syncing # 查看区块数量是否同步 eth.blockNumber
特别注意:同步区块时,不要关闭控制台!注意使用正常的exit
命令退出!否则下次启动同步,将导致程序退出,同步失败!
主节点控制台卡死、无法退出问题
先在节点2执行exit命令,正常退出节点2。然后直接关闭主节点控制台。但再次打开时,节点2添加peers会导致主节点异常退出。执行eth.blockNumber会发现主节点的区块数量为0。这时,我们找到主节点的文件夹,删除geth目录下的chaindata文件夹。同时将节点2的该文件夹拷贝到geth目录下。再次启动节点1、2开启同步,可见区块数量一致,同步正常。
部署智能合约
编写合约代码MyContract.sol
使用remix平台,编写智能合约代码https://remix.ethereum.org/
注意选择编译环境,以及复制生成的abi以及bytecode到文件MyContract.abi和MyContract.bin。
pragma solidity ^0.8.0;
contract MyContract {
mapping(string => string) public data;
function storeData(string memory id, string memory value) public {
data[id] = value;
}
function retrieveData(string memory id) public view returns (string memory) {
return data[id];
}
}
将智能合约转为Java代码
- 创建SpringBoot项目,并引入依赖
<dependency>
<groupId>org.web3j</groupId>
<artifactId>geth</artifactId>
<version>${geth.version}</version>
<exclusions>
<exclusion>
<artifactId>core</artifactId>
<groupId>org.web3j</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.web3j</groupId>
<artifactId>parity</artifactId>
<version>${geth.version}</version>
<exclusions>
<exclusion>
<artifactId>core</artifactId>
<groupId>org.web3j</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.web3j</groupId>
<artifactId>codegen</artifactId>
<version>5.0.0</version>
</dependency>
- 编写转换代码
import org.web3j.codegen.SolidityFunctionWrapperGenerator;
import java.util.Arrays;
import java.util.stream.Stream;
public class Sol2Java {
public static void main(String[] args) {
Sol2Java.generateClass(
"D:\\Huathy\\Desktop\\taobao\\warehouse\\src\\main\\resources\\sol\\MyContract.abi",
"D:\\Huathy\\Desktop\\taobao\\warehouse\\src\\main\\resources\\sol\\MyContract.bin",
"D:\\Huathy\\Desktop\\taobao\\warehouse\\src\\main\\resources\\sol");
}
/**
*
* 生成合约的java代码
* 其中 -p 为生成java代码的包路径此参数和 -o 参数配合使用,以便将java文件放入正确的路径当中
* @param abiFile abi的文件路径
* @param binFile bin的文件路径
* @param generateFile 生成的java文件路径
*/
public static void generateClass(String abiFile,String binFile,String generateFile){
String[] args = Arrays.asList(
"-a",abiFile,
"-b",binFile,
"-p","",
"-o",generateFile
).toArray(new String[0]);
Stream.of(args).forEach(System.out::println);
SolidityFunctionWrapperGenerator.main(args);
}
}
智能合约Java代理类使用
- 获取私钥。注意在获取的私钥前的0x
2. 部署合约
package com.yeqifu;
import com.yeqifu.bus.geth.MyContract;
import org.junit.Test;
import org.web3j.crypto.Credentials;
import org.web3j.protocol.Web3j;
import org.web3j.protocol.http.HttpService;
import org.web3j.tx.gas.DefaultGasProvider;
public class TestMyContract {
static String nodeUrl = "http://localhost:8546"; // 连接以太坊节点,替换为实际节点URL
static String contractAddress = "0xd8554b507a868d5d30ff3d2e14b92615c243831e";
static String privateKey = "0xff9b62e39ec890263e586eab6e5a216c637ba1360381c9eb870ef11884a0b0a6"; // 部署者的私钥
/**
* 部署合约
*
* @throws Exception
*/
@Test
public void deplyContract() throws Exception {
// RPC调用url(此处为ropsten)
Web3j web3j = Web3j.build(new HttpService(nodeUrl));
Credentials credentials = Credentials.create(privateKey);
MyContract contract = MyContract.deploy(web3j, credentials, new DefaultGasProvider()).sendAsync().get();
System.out.println("DataStore contract deployed at address: " + contract.getContractAddress());
}
}
如果在部署过程中出现gas不足或者gas超出限制等问题,可能是由于eth余额不足,可以先进行调用矿工进行掘金。如果排查余额充足,则可能是geth版本问题,Version: 1.8.20-stable版本亲测可用。另外新版的语法于旧版本有所不同。