04-web3j交易(Transactions)


文章是本人学习过程翻译,原文来自官方文档: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)

当你创建了一个拥有以太币的账户后,你可以通过以下两种交易机制,和以太坊网络(私网/公网)交易:

  1. 通过以太坊客户端签名交易 - Transaction signing via an Ethereum client
  2. 线下签名交易 - 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 对象来完成,一共有如下几步:

  1. 确定交易账户的下一个 nonce
  2. 创建 RawTransaction 对象
  3. 使用 RLP 编码 RawTransaction 对象
  4. 签名 RawTransaction 对象
  5. 发送 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(
             , , , , 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
|", BigDecimal.valueOf(1.0), Convert.Unit.ETHER).send();

与智能合约交互

在于智能合约交互时,你必须执行所有的手动转换从solidity类型到本地Java类型。庆幸的时,使用web3j的Solidity smart contract wrappers ,它可以帮你完成这些转换,你可以很方便地使用。

  1. 创建智能合约,有两个属性:
  • value - 你希望存到智能合约的以太币
  • data - 十六进制格式,编译后的智能合同创建代码
// using a raw transaction
RawTransaction rawTransaction = RawTransaction.createContractTransaction(
        ,
        ,
        ,
        ,
        "0x ");
// 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(
        ,
        ,
        ,
        ,
        ,
        "0x " + encodedConstructor);

// send...

  1. 发起交易到智能合约

与存在的智能合约交互,需要提供以下属性:

  • 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() {}, ...));
String encodedFunction = FunctionEncoder.encode(function)
Transaction transaction = Transaction.createFunctionCallTransaction(
             , , , contractAddress, , 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

  1. 查询智能合约的状态

可以通过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() {}, ...));

String encodedFunction = FunctionEncoder.encode(function)
org.web3j.protocol.core.methods.response.EthCall response = web3j.ethCall(
             Transaction.createEthCallTransaction(, contractAddress, encodedFunction),
             DefaultBlockParameterName.LATEST)
             .sendAsync().get();

List someTypes = FunctionReturnDecoder.decode(
             response.getValue(), function.getOutputParameters());

注意:如果一个无效的函数调用,或一个空的结果,返回值将Collections.emptyList的实例()

你可能感兴趣的:(04-web3j交易(Transactions))