前言:本文将从一个区块链入门小白的视角,来一步步的讲解如何实现区块链数据上链,链上数据查询,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"; // 连接以太坊节点,替换为实际节点URLstatic String contractAddress = "0xd8554b507a868d5d30ff3d2e14b92615c243831e";static String privateKey = "0xff9b62e39ec890263e586eab6e5a216c637ba1360381c9eb870ef11884a0b0a6"; // 部署者的私钥/*** 部署合约** @throws Exception*/@Testpublic 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版本亲测可用。另外新版的语法于旧版本有所不同。