文章是本人学习过程翻译,原文来自官方文档:https://web3j.readthedocs.io/en/latest/#
官网:https://web3j.io/
官方GitHub:https://github.com/web3j/web3j
官方demo:https://github.com/web3j/web3j/tree/master/integration-tests
文档版本v3.4.0。
一般来说,在以太坊支持三种类型的交易:
- 交易以太币
- 创建智能合约
- 发起交易到智能合约
要进行这些交易,需要消耗gas,如果你只是查询合约的状态则不需要gas.
获取以太币(Obtaining Ether)
有两种获取以太币的方法:
- 挖矿
- 跟别人购买以太币
以太坊测试网(Ethereum testnets)
下面列出的是Ethereum的各种测试网络,支持各自的客户端:
- Rinkeby (Geth only)
- Kovan (Parity only)
- Ropsten (Geth and Parity)
对于开发者,推荐使用Rinkeby/Kovan测试网络,因为它们使用POA共识机制,可以确保交易和区块可以及时被确认打包。
Ropsten测试网络很接近Mainet主网,同样使用POW共识机制,过去曾经被攻击过,使用它的开发者可能会遇到很多问题。
在Rinkeby网络获取以太币,请看here.
在Kovan网络获取以太币,请看here
在Ropsten网络获取以太币,你需要把你的钱包地址发送到web3j Gitter channel
在测试网/私有网挖矿(Mining on testnet/private blockchains)
在测试网/私有网挖矿没有主网那么高难度,只需要一台安装客户端节点的普通的电脑。
- Geth - https://github.com/ethereum/go-ethereum/wiki/Mining
- Parity - https://github.com/paritytech/parity/wiki/Mining
当你通过挖矿获得以太币后,你就可以发起交易了。
Gas
要进行交易,需要发起的账户花费一些gas,把交易的结果提交到以太坊的区块上。你需要指定两个参数,让客户端节点在处理交易时,知道你希望花费多少以太币来完成交易。
Gas price gas价格
web3j使用默认价格22,000,000,000 Wei (22 x 10-8 Ether).这个默认值定义在ManagedTransaction
Gas limit 最高gas
这个值一般要小于6,700,000, 可以在 https://ethstats.net/ 查看当前的gas limit.
这两个参数的设置会影响到交易被处理的速度,您可能需要调整这些参数,以确保及时交易发生。
发起交易查询当前gas price:
Transfer transfer = new Transfer(web3j, transactionManager);
BigInteger gasPrice = transfer.requestCurrentGasPrice();
交易机制(Transaction mechanisms)
当你创建了一个拥有以太币的账户后,你可以通过以下两种交易机制,和以太坊网络(私网/公网)交易:
- 通过以太坊客户端签名交易 - Transaction signing via an Ethereum client
- 线下签名交易 - Offline transaction signing
通过以太坊客户端签名交易
通过客户端交易,首先你要启动客户端(geth、parity),然后使用客户端来创建你的钱包账户:
- Geth的控制台支持导入一个已经存在的私钥文件,或者创建一个新的账户。
- Geth的控制台提供了json-rpc的命令来让你管理geth,比如 _personal_newAccount _ 来创建一个新的账户。
创建了账户后,你就可以使用web3j来连接客户端(ipc/http),这不需要提供秘钥,只需要保证客户端可以连接,连接后可以解锁账户、发起交易,代码如下:
Admin web3j = Admin.build(new WindowsIpcService("\\\\.\\pipe\\geth.ipc"));
PersonalUnlockAccount personalUnlockAccount = web3j
.personalUnlockAccount("0x053b2252a10356ec0ce1cfc587b909beee591409", "111").send();
System.out.println(personalUnlockAccount.accountUnlocked());
EthGetTransactionCount ethGetTransactionCount = web3j
.ethGetTransactionCount("0x053b2252a10356ec0ce1cfc587b909beee591409", DefaultBlockParameterName.LATEST)
.send();
BigInteger nonce = ethGetTransactionCount.getTransactionCount();
Transaction transaction = Transaction.createEtherTransaction("0x053b2252a10356ec0ce1cfc587b909beee591409",
nonce, GAS_PRICE, GAS_LIMIT, "0xc7d9fffaf663c5dfa31b096164cc843c01d0797a", BigInteger.valueOf(20L));
org.web3j.protocol.core.methods.response.EthSendTransaction transactionResponse = web3j
.ethSendTransaction(transaction).send();
String transactionHash = transactionResponse.getTransactionHash();
System.out.println(transactionHash);
更多实例请看 DeployContractIT和它的父类 Scenario。
线下交易签名(Offline transaction signing)
如果你不想管理你的客户端节点,或者不想把提供钱包密码给客户端节点,线下交易签名比较适合你。
线下交易签名允许你使用web3j提供的钱包账户发起交易,你完全控制自己的私钥,交易发送到网络上的其它节点并传播。
通过覆盖签名方法ECKeyPair,来发起交易签名。
ECKeyPair:椭圆曲线算法生成秘钥对(Elliptic Curve SECP-256k1 generated key pair)
为了实现线下交易,你需要使用web3j生成安全的钱包账户,并这个账户交易。
创建钱包文件:
String fileName = WalletUtils.generateNewWalletFile(
"your password",
new File("/path/to/destination"));
加载钱包文件:
Credentials credentials = WalletUtils.loadCredentials(
"your password",
"/path/to/walletfile");
credentials 用来签名交易。
查看完整的钱包文件规范:Web3 Secret Storage Definition
交易签名
线下交易使用 RawTransaction 对象来完成,一共有如下几步:
- 确定交易账户的下一个 nonce 值
- 创建 RawTransaction 对象
- 使用 RLP 编码 RawTransaction 对象
- 签名 RawTransaction 对象
- 发送 RawTransaction 对象给节点处理。
交易nonce
以太坊实战-再谈nonce使用陷阱:https://blog.csdn.net/wo541075754/article/details/79054937
- nonce 值用来唯一标识账户的交易,一个值只能使用一次
- nonce 值可以通过eth_getTransactionCount方法获取
- 如果一个账户使用相同的 nonce通过发起多个交易,只有一个交易会被接受,(gas price)手续费高的会覆盖手续费低的交易,如果交易费一样,后发起的交易会被拒绝。
- Transaction的 nonce 值可以为空,不指定 nonce 值由客户端自动排序赋值
- RawTransaction的 nonce 值不能为空
Admin web3j = Admin.build(new HttpService()); // defaults to http://localhost:8545/
Credentials credentials = WalletUtils.loadCredentials("111", "E:\\develop\\geth\\data_dev\\keystore\\UTC--2018-05-05T06-50-18.813015000Z--053b2252a10356ec0ce1cfc587b909beee591409");
BigInteger value = Convert.toWei("5", Convert.Unit.ETHER).toBigInteger();
// get the next available nonce
EthGetTransactionCount ethGetTransactionCount = web3j.ethGetTransactionCount(
"0x053b2252a10356ec0ce1cfc587b909beee591409", DefaultBlockParameterName.LATEST).send();
BigInteger nonce = ethGetTransactionCount.getTransactionCount();
// create our transaction
RawTransaction rawTransaction = RawTransaction.createEtherTransaction(
nonce, GAS_PRICE, GAS_LIMIT, "0xc7d9fffaf663c5dfa31b096164cc843c01d0797a", value);
// sign & send our transaction
byte[] signedMessage = TransactionEncoder.signMessage(rawTransaction, credentials);
String hexValue = Numeric.toHexString(signedMessage);
EthSendTransaction ethSendTransaction = web3j.ethSendRawTransaction(hexValue).send();
System.out.println(ethSendTransaction.getTransactionHash());
交易类型(Transaction types)
两种交易类型:
- Transaction
- RawTransaction
不管哪种交易类型,都需要下面的参数:
- Gas price
- Gas limit
- Nonce
- From
从一个账户发送以太币到另外一个账户
RawTransaction
BigInteger value = Convert.toWei("1.0", Convert.Unit.ETHER).toBigInteger();
RawTransaction rawTransaction = RawTransaction.createEtherTransaction(
<nonce>, <gas price>, <gas limit>, <toAddress>, value);
Transfer
Web3j web3 = Web3j.build(new HttpService()); // defaults to http://localhost:8545/
Credentials credentials = WalletUtils.loadCredentials("password", "/path/to/walletfile");
TransactionReceipt transactionReceipt = Transfer.sendFunds(
web3, credentials, "0x<address>|<ensName>",
BigDecimal.valueOf(1.0), Convert.Unit.ETHER).send();
与智能合约交互
在于智能合约交互时,你必须执行所有的手动转换从solidity类型到本地Java类型。庆幸的时,使用web3j的Solidity smart contract wrappers ,它可以帮你完成这些转换,你可以很方便地使用。
- 创建智能合约,有两个属性:
- value - 你希望存到智能合约的以太币
- data - 十六进制格式,编译后的智能合同创建代码
// using a raw transaction
RawTransaction rawTransaction = RawTransaction.createContractTransaction(
<nonce>,
<gasPrice>,
<gasLimit>,
<value>,
"0x <compiled smart contract code>");
// send...
// get contract address
EthGetTransactionReceipt transactionReceipt =
web3j.ethGetTransactionReceipt(transactionHash).send();
if (transactionReceipt.getTransactionReceipt.isPresent()) {
String contractAddress = transactionReceipt.get().getContractAddress();
} else {
// try again
}
如果智能合约有构造函数,需要提供构造参数,必须编码这些参数并拼到合约代码后面:
String encodedConstructor =
FunctionEncoder.encodeConstructor(Arrays.asList(new Type(value), ...));
// using a regular transaction
Transaction transaction = Transaction.createContractTransaction(
<fromAddress>,
<nonce>,
<gasPrice>,
<gasLimit>,
<value>,
"0x <compiled smart contract code>" + encodedConstructor);
// send...
- 发起交易到智能合约
与存在的智能合约交互,需要提供以下属性:
- to - 智能合约地址
- value - 你希望存到智能合约的以太币
- data - 编码后的调用的函数和参数
Function function = new Function<>(
"functionName", // function we're calling
Arrays.asList(new Type(value), ...), // Parameters to pass as Solidity Types
Arrays.asList(new TypeReference<Type>() {}, ...));
String encodedFunction = FunctionEncoder.encode(function)
Transaction transaction = Transaction.createFunctionCallTransaction(
<from>, <gasPrice>, <gasLimit>, contractAddress, <funds>, encodedFunction);
org.web3j.protocol.core.methods.response.EthSendTransaction transactionResponse =
web3j.ethSendTransaction(transaction).sendAsync().get();
String transactionHash = transactionResponse.getTransactionHash();
// wait for response using EthGetTransactionReceipt...
交易调用不可能直接得到返回值,要获取返回值,必须用Filters and Events 。
更多关于函数编码的介绍,请看Application Binary Interface
- 查询智能合约的状态
可以通过eth_call这个JSON-RPC call接口来查询合约状态,这个调用不用花费gas,因为它没有改变账户的状态。
Function function = new Function<>(
"functionName",
Arrays.asList(new Type(value)), // Solidity Types in smart contract functions
Arrays.asList(new TypeReference<Type>() {}, ...));
String encodedFunction = FunctionEncoder.encode(function)
org.web3j.protocol.core.methods.response.EthCall response = web3j.ethCall(
Transaction.createEthCallTransaction(<from>, contractAddress, encodedFunction),
DefaultBlockParameterName.LATEST)
.sendAsync().get();
List<Type> someTypes = FunctionReturnDecoder.decode(
response.getValue(), function.getOutputParameters());
注意:如果一个无效的函数调用,或一个空的结果,返回值将Collections.emptyList的实例()
网友评论