前置条件已经安装Geth并启动。
现在我们讲一下Spring Boot项目中集成Geth,然后怎么以太坊区块链进行交互操作。
1、添加依赖到工程pom.xml
<dependency>
<groupId>org.web3j</groupId>
<artifactId>core</artifactId>
<version>4.8.7</version>
</dependency>
<dependency>
<groupId>org.web3j</groupId>
<artifactId>geth</artifactId>
<version>4.8.7</version>
</dependency>
2、添加配置到yml文件
web3j:
# client-address: http://192.168.99.100:8545
client-address: http://127.0.0.1:8545
admin-client: true
httpTimeoutSeconds: 60000
3、ETH配置类EthConfig.java
/**
* @author deray.wang
* @date 2024/04/20 17:18
*/
@Configuration
public class EthConfig {
@Value("${web3j.client-address}")
private String rpc;
@Bean
public Web3j web3j() {
OkHttpClient.Builder builder = new OkHttpClient.Builder();
builder.connectTimeout(30*1000, TimeUnit.MILLISECONDS);
builder.writeTimeout(30*1000, TimeUnit.MILLISECONDS);
builder.readTimeout(30*1000, TimeUnit.MILLISECONDS);
OkHttpClient httpClient = builder.build();
Web3j web3j = Web3j.build(new HttpService(rpc,httpClient,false));
return web3j;
}
/**
* 初始化admin级别操作的对象
* @return Admin
*/
@Bean
public Admin admin() {
OkHttpClient.Builder builder = new OkHttpClient.Builder();
builder.connectTimeout(30*1000, TimeUnit.MILLISECONDS);
builder.writeTimeout(30*1000, TimeUnit.MILLISECONDS);
builder.readTimeout(30*1000, TimeUnit.MILLISECONDS);
OkHttpClient httpClient = builder.build();
Admin admin = Admin.build(new HttpService(rpc,httpClient,false));
return admin;
}
/**
* 初始化personal级别操作的对象
* @return Geth
*/
@Bean
public Geth geth() {
OkHttpClient.Builder builder = new OkHttpClient.Builder();
builder.connectTimeout(30*1000, TimeUnit.MILLISECONDS);
builder.writeTimeout(30*1000, TimeUnit.MILLISECONDS);
builder.readTimeout(30*1000, TimeUnit.MILLISECONDS);
OkHttpClient httpClient = builder.build();
Geth geth = Geth.build(new HttpService(rpc,httpClient,false));
return geth;
}
}
4、封装两个bean:转账ETH参数类 TransferEchBean.java 和BlockchainTransaction.java
/**
* 转账ETH参数类
* @author
*/
@Data
@ApiModel
@ToString
public class TransferEchBean {
@ApiModelProperty("fromAddr")
private String fromAddr;
@ApiModelProperty("密码")
private String privateKey;
@ApiModelProperty("toAddr")
private String toAddr;
@ApiModelProperty("amount")
private BigDecimal amount;
@ApiModelProperty("data")
private String data;
}
/**
* @author deray.wang
* @date 2024/04/20 13:44
*/
@Data
public class BlockchainTransaction {
private String id;
//发送发件人ID
private Integer fromId;
//交易金额
private long value;
//收件人ID
private Integer toId;
private Boolean accepted;
}
5、封装操作区块链的方法 BlockchainService.java
BlockchainService.java类
/**
* @author deray.wang
* @date 2024/04/20 13:36
*/
public interface BlockchainService {
/**
* 获取账户的Nonce
* @param web3j
* @param addr
* @return
*/
BigInteger getAcountNonce(Web3j web3j, String addr);
/**
* 获取账户余额
* @param web3j
* @param addr
* @return
*/
BigDecimal getAccountBalance(Web3j web3j, String addr);
/**
* 查询区块内容
* @param web3j
* @param blockNumber
* @return
*/
EthBlock getBlockEthBlock(Web3j web3j, BigInteger blockNumber);
/**
* 创建钱包
* @param password
* @return
*/
ServiceResponse newAccount(String password);
/**
* 地址列表
* @return
*/
List<String> getAllAccounts();
/**
* 转账ETH
* @param web3j
* @param fromAddr
* @param privateKey
* @param toAddr
* @param amount
* @param data
* @return
*/
ServiceResponse transferETHD(Web3j web3j, TransferEchBean filterBean);
/**
* 普通转账ETH
* @param web3j
* @param filterBean
* @return
*/
ServiceResponse tranETH(Web3j web3j, TransferEchBean filterBean);
实现类:BlockchainServiceImpl.java
/**
* @author deray.wang
* @date 2024/11/20 13:52
*/
@Service
public class BlockchainServiceImpl implements BlockchainService {
private static final Logger LOGGER = LoggerFactory.getLogger(BlockchainService.class);
private static BigInteger gNoce = null;
@Value("${wallet.file}")
private String FILE;
@Autowired
private Admin admin;
@Autowired
private static Web3j web3j;
@Autowired
private Geth geth;
/**
* 获取账户的Nonce
* @param web3j
* @param addr
* @return
*/
@Override
public BigInteger getAcountNonce(Web3j web3j, String addr) {
return getNonce(web3j,addr);
}
@Override
public BigDecimal getAccountBalance(Web3j web3j, String addr) {
return getBalance(web3j,addr);
}
/**
*
*/
@Override
public ServiceResponse transferETHD(Web3j web3j, TransferEchBean filterBean){
//封装业务参数
Map<String,String> map = new HashMap<String,String>();
map.put("time", String.valueOf(new Date()));
map.put("type","info");
map.put("msg","Web3 Test!!!000000000000000000000000000");
JSONObject jsonObj=new JSONObject(map);
//将data转化为hex
String datahex = null;
try {
datahex = HexUtils.toHexString(jsonObj.toString().getBytes("UTF-8"));
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
return transferETH(web3j,filterBean.getFromAddr(),filterBean.getPrivateKey(),filterBean.getToAddr(),filterBean.getAmount(),datahex);
}
/**
*
*/
@Override
public ServiceResponse tranETH(Web3j web3j, TransferEchBean filterBean){
。。
return ServiceResponse.createFailResponse("",0,"");
}
/**
* 指定地址发送交易所需nonce获取
* @param web3j
* @param addr
* @return
*/
public static BigInteger getNonce(Web3j web3j, String addr){
EthGetTransactionCount transactionCount = null;
try {
transactionCount = web3j.ethGetTransactionCount(addr, DefaultBlockParameterName.LATEST).sendAsync().get();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
BigInteger nonce = transactionCount.getTransactionCount();
// LOGGER.info("Tx hash: {}", "transfer nonce : " + nonce);
return nonce;
}
/**
* 获取代币余额
* @param web3j
* @param fromAddress
* @param contractAddress
* @return
*/
public static BigInteger getTokenBalance(Web3j web3j, String fromAddress, String contractAddress) {
String methodName = "balanceOf";
List<Type> inputParameters = new ArrayList<>();
List<TypeReference<?>> outputParameters = new ArrayList<>();
Address address = new Address(fromAddress);
inputParameters.add(address);
TypeReference<Uint256> typeReference = new TypeReference<Uint256>() {
};
outputParameters.add(typeReference);
Function function = new Function(methodName, inputParameters, outputParameters);
String data = FunctionEncoder.encode(function);
Transaction transaction = Transaction.createEthCallTransaction(fromAddress, contractAddress, data);
EthCall ethCall;
BigInteger balanceValue = BigInteger.ZERO;
try {
ethCall = web3j.ethCall(transaction, DefaultBlockParameterName.LATEST).send();
List<Type> results = FunctionReturnDecoder.decode(ethCall.getValue(), function.getOutputParameters());
balanceValue = (BigInteger) results.get(0).getValue();
} catch (IOException e) {
e.printStackTrace();
}
return balanceValue;
}
/**
* 转账ETH
* @param web3j
* @param fromAddr 发起人钱包地址
* @param privateKey 钱包私钥
* @param toAddr 转入的钱包地址
* @param amount 转账金额,单位是wei
* @param data 备注的信息
* @param
* @return
*/
ServiceResponse transferETH(Web3j web3j, String fromAddr, String privateKey, String toAddr, BigDecimal amount, String data){
// 获得nonce
BigInteger nonce = getNonce(web3j, fromAddr);
// value转换
BigInteger value = Convert.toWei(amount, Convert.Unit.ETHER).toBigInteger();
// gasPrice转账费用
BigInteger gasPrice;
gasPrice = Convert.toWei("0", Convert.Unit.GWEI).toBigInteger();
//注意手续费的设置,这块很容易遇到问题
BigInteger gasLimit = Convert.toWei("45000", Convert.Unit.WEI).toBigInteger();
// 查询调用者余额,检测余额是否充足
BigDecimal ethBalance = getBalance(web3j, fromAddr);
BigDecimal balance = Convert.toWei(ethBalance, Convert.Unit.ETHER);
BigDecimal tt = Convert.toWei(String.valueOf(1), Convert.Unit.ETHER);
checkMoney(String.valueOf(amount),String.valueOf(balance));
BigInteger val = gasPrice.multiply(gasLimit);
if (balance.compareTo(tt.add(new BigDecimal(val))) < 0) {
//throw new RuntimeException("余额不足,请核实");
return ServiceResponse.createFailResponse("",0,"交易失败:余额不足,请核实");
}
//对交易签名,并发送交易
if(gNoce == null){
gNoce = nonce;
}
LOGGER.info("Tx hash: {}", "transfer nonce : " + gNoce+" gasPrice:"+gasPrice+" gasLimit:"+gasLimit+" toAddr:"+toAddr+" value:"+value+" data:"+data);
RawTransaction rawTransaction = RawTransaction.createTransaction(gNoce, gasPrice, gasLimit, toAddr, value, data);
gNoce = gNoce.add(new BigInteger("1"));
//RawTransaction.createEtherTransaction(nonce,gasPrice,gasLimit,to,value);
if (privateKey.startsWith("0x")){
privateKey = privateKey.substring(2);
}
ECKeyPair ecKeyPair = ECKeyPair.create(new BigInteger(privateKey, 16));
Credentials credentials = Credentials.create(ecKeyPair);
System.out.println(credentials.getAddress());
System.out.println("PrivateKey:" + credentials.getEcKeyPair().getPrivateKey());
//进行签名操作 签名Transaction,这里要对交易做签名
byte[] signedMessage = TransactionEncoder.signMessage(rawTransaction, credentials);
String hexValue = Numeric.toHexString(signedMessage);
//发送交易
EthSendTransaction ethSendTransaction = null;
if (!"".equals(hexValue)) {
try {
ethSendTransaction = web3j.ethSendRawTransaction(hexValue).sendAsync().get();
if(ethSendTransaction.hasError()) {
String message = ethSendTransaction.getError().getMessage();
System.out.println("transaction failed,info:" + message);
//Utils.writeFile("F:/testErr.txt", "transaction failed,info:" + message);
return ServiceResponse.createFailResponse("",0,"交易失败:"+message);
}
} catch (InterruptedException e) {
System.out.println("transaction failed,info:" + ethSendTransaction.getError().getMessage());
e.printStackTrace();
return ServiceResponse.createFailResponse("",0,"交易失败:"+ethSendTransaction.getError().getMessage());
} catch (ExecutionException e) {
System.out.println("transaction failed,info:" + ethSendTransaction.getError().getMessage());
e.printStackTrace();
return ServiceResponse.createFailResponse("",0,"交易失败:"+ethSendTransaction.getError().getMessage());
}
}
String transactionHash = ethSendTransaction.getTransactionHash();
if(ethSendTransaction.hasError()){
String message=ethSendTransaction.getError().getMessage();
return ServiceResponse.createFailResponse("",0,"交易失败:"+message);
}else {
EthGetTransactionReceipt send = null;
try {
send = web3j.ethGetTransactionReceipt(transactionHash).send();
} catch (IOException e) {
e.printStackTrace();
}
if (send != null) {
System.out.println("交易成功");
//System.out.println(send.getTransactionReceipt());
}
Map<String,String> mapRes = new HashMap<String,String>();
mapRes.put("txHash", transactionHash);
return ServiceResponse.createSuccessResponse("",mapRes,"交易成功,等待记账!");
}
}
/**
* 获取ETH余额
* @param web3j
* @param address
* @return
*/
public static BigDecimal getBalance(Web3j web3j, String address) {
try {
EthGetBalance ethGetBalance = web3j.ethGetBalance(address, DefaultBlockParameterName.LATEST).send();
//单位转换
BigDecimal banlance = Convert.fromWei(new BigDecimal(ethGetBalance.getBalance()),Convert.Unit.ETHER);
return banlance;
} catch (IOException e) {
e.printStackTrace();
//throw new Exception("查询钱包余额失败");
return null;
}
}
public static BigInteger getTransactionGasLimit(Web3j web3j, Transaction transaction) {
try {
EthEstimateGas ethEstimateGas = web3j.ethEstimateGas(transaction).send();
if (ethEstimateGas.hasError()){
throw new RuntimeException(ethEstimateGas.getError().getMessage());
}
return ethEstimateGas.getAmountUsed();
} catch (IOException e) {
throw new RuntimeException("net error");
}
}
/**
* generate a random group of mnemonics
* 生成一组随机的助记词
*/
private String generateMnemonics() {
byte[] initialEntropy = new byte[16];
new SecureRandom().nextBytes(initialEntropy);
String mnemonic = MnemonicUtils.generateMnemonic(initialEntropy);
return mnemonic;
}
private Map<String,String> createAccount() {
//创建Map对象
Map<String, String> map = new HashMap<String,String>(); //数据采用的哈希表结构
Bip39Wallet wallet = null;
// 创建一个存放keystore的文件夹
String path = FILE;
try {
// 创建钱包
wallet = WalletUtils.generateBip39Wallet("", new File(path));
} catch (Exception e) {
LOGGER.info("创建钱包失败");
}
// 获取keystore的名字
String keyStoreKey = wallet.getFilename();
LOGGER.info("keyStoreKey ================ " + keyStoreKey);
// 获取助记词
String mnemonic = wallet.getMnemonic();
LOGGER.info("mnemonic ======================== " + mnemonic);
// 使用密码和助记词让账户解锁
Credentials credentials = WalletUtils.loadBip39Credentials("", wallet.getMnemonic());
// 获取账户地址
String address = credentials.getAddress();
LOGGER.info("address ================= " + address);
// 获取公钥
String publicKey = credentials.getEcKeyPair().getPublicKey().toString(16);
LOGGER.info("publicKey ==================== " + publicKey);
// 获取私钥
String privateKey = credentials.getEcKeyPair().getPrivateKey().toString(16);
LOGGER.info("privateKey ================== " + privateKey);
map.put("address",address);
map.put("privateKey",privateKey);
map.put("publicKey",publicKey);
map.put("createTime", TimeUtil.getNowString());
return map;
}
/**
* 查询区块内容
* @param web3j
* @param blockNumber
* @return
*/
@Override
public EthBlock getBlockEthBlock(Web3j web3j,BigInteger blockNumber){
DefaultBlockParameter defaultBlockParameter = new DefaultBlockParameterNumber(blockNumber);
Request<?, EthBlock> request = web3j.ethGetBlockByNumber(defaultBlockParameter, true);
EthBlock ethBlock = null;
try {
ethBlock = request.send();
//返回值 - 区块对象
System.out.println(ethBlock.getBlock());
} catch (IOException e) {
e.printStackTrace();
}
return ethBlock;
}
/**
* 输入密码创建地址
* @param password 密码(建议同一个平台的地址使用一个相同的,且复杂度较高的密码)
* @return 地址hash
*/
@Override
public ServiceResponse newAccount(String password) {
return ServiceResponse.createSuccessResponse("",createAccount());
}
/**
* 根据hash值获取交易
* @param hash
* @return
* @throws IOException
*/
public static EthTransaction getTransactionByHash(String hash) throws IOException {
Request<?, EthTransaction> request = web3j.ethGetTransactionByHash(hash);
return request.send();
}
/**
* 账户解锁,使用完成之后需要锁定
* @param address
* @return
* @throws IOException
*/
public Boolean lockAccount(String address) throws IOException {
Request<?, BooleanResponse> request = geth.personalLockAccount(address);
BooleanResponse response = request.send();
return response.success();
}
/**
* 解锁账户,发送交易前需要对账户进行解锁
* @param address 地址
* @param password 密码
* @param duration 解锁有效时间,单位秒
* @return
* @throws IOException
*/
public Boolean unlockAccount(String address, String password, BigInteger duration) throws IOException{
Request<?, PersonalUnlockAccount> request = admin.personalUnlockAccount(address, password, duration);
PersonalUnlockAccount account = request.send();
return account.accountUnlocked();
}
/**
* 发送交易并获得交易hash值
* @param transaction
* @param password
* @return
* @throws IOException
*/
public String sendTransaction(Transaction transaction, String password) throws IOException {
Request<?, EthSendTransaction> request = admin.personalSendTransaction(transaction, password);
EthSendTransaction ethSendTransaction = request.send();
return ethSendTransaction.getTransactionHash();
}
/**
* 获取钱包里的所有用户
* @return
*/
@Autowired
public List<String> getAllAccounts() {
List<String> list = new ArrayList<String>();
try {
Request<?, EthAccounts> request = geth.ethAccounts();
list = request.send().getAccounts();
System.out.println(list.toString());
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return list;
}
/**
* 钱包地址余额是否足够转账校验
* @param bigDecimalValue
* @param addressBalance
* @return
*/
public static String checkMoney(String bigDecimalValue,String addressBalance){
if(new BigDecimal(addressBalance).subtract(new BigDecimal(bigDecimalValue)).compareTo(new BigDecimal("0")) <= 0){
System.out.println("转账金额大于钱包地址余额");
return "转账金额大于钱包地址余额";
}else{
System.out.println("=======================");
return "";
}
}
}
6、Controller类AccountController
/**
* @author deray.wang
* @date 2019/11/27 17:16
*/
@Slf4j
@Api(value = "用户账号接口", tags = "用户账号接口")
@RestController
@RequestMapping(CommonConst.API_PATH_VERSION_1 + "/account")
public class AccountController {
@Autowired
private BlockchainService blockchainService;
@RequestMapping(value = "/newAccount", method = RequestMethod.POST)
@ApiOperation(httpMethod = "POST", value = "创建地址", produces = MediaType.APPLICATION_JSON_VALUE)
public ServiceResponse newAccount(@ApiParam(name = "password") @RequestParam(name = "password") String password) {
return blockchainService.newAccount(password);
}
@RequestMapping(value = "/getAccount", method = RequestMethod.GET)
@ApiOperation(httpMethod = "GET", value = "获取钱包里的所有用户", produces = MediaType.APPLICATION_JSON_VALUE)
public ServiceResponse getAllAccounts() {
List<String> accounts = blockchainService.getAllAccounts();
return ServiceResponse.createSuccessResponse("",accounts);
}
}
大部分操作已经实现。有需要的可以联系我沟通。剩下的操作,给区块链处理,比如转账确认。