package org.matthiaszimmermann.web3j.demo; import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import java.math.BigDecimal; import java.math.BigInteger; import org.junit.Test; import org.matthiaszimmermann.web3j.util.Alice; import org.matthiaszimmermann.web3j.util.Bob; import org.matthiaszimmermann.web3j.util.Web3jConstants; import org.web3j.crypto.Credentials; import org.web3j.crypto.TransactionEncoder; import org.web3j.protocol.core.DefaultBlockParameter; import org.web3j.protocol.core.Response.Error; import org.web3j.protocol.core.methods.request.RawTransaction; import org.web3j.protocol.core.methods.request.Transaction; import org.web3j.protocol.core.methods.response.EthBlock; import org.web3j.protocol.core.methods.response.EthBlock.Block; import org.web3j.protocol.core.methods.response.EthBlock.TransactionObject; import org.web3j.protocol.core.methods.response.EthBlock.TransactionResult; import org.web3j.protocol.core.methods.response.EthSendTransaction; import org.web3j.protocol.core.methods.response.EthTransaction; import org.web3j.protocol.core.methods.response.TransactionReceipt; import org.web3j.tx.Transfer; import org.web3j.utils.Convert; import org.web3j.utils.Numeric; public class TransferEtherTest extends AbstractEthereumTest { /** * Ether transfer tests using methods {@link Transfer#sendFunds()}. * Sending account needs to be unlocked for this to work. */ @Test public void testSendFunds() throws Exception { BigDecimal amountEther = BigDecimal.valueOf(0.123); BigInteger amountWei = Convert.toWei(amountEther, Convert.Unit.ETHER).toBigInteger(); ensureFunds(Alice.ADDRESS, amountWei); BigInteger fromBalanceBefore = getBalanceWei(Alice.ADDRESS); BigInteger toBalanceBefore = getBalanceWei(Bob.ADDRESS); // this is the method to test here TransactionReceipt txReceipt = Transfer.sendFunds( web3j, Alice.CREDENTIALS, Bob.ADDRESS, amountEther, Convert.Unit.ETHER); BigInteger txFees = txReceipt.getGasUsed().multiply(Web3jConstants.GAS_PRICE); assertFalse(txReceipt.getBlockHash().isEmpty()); assertEquals("Unexected balance for 'from' address", fromBalanceBefore.subtract(amountWei.add(txFees)), getBalanceWei(Alice.ADDRESS)); assertEquals("Unexected balance for 'to' address", toBalanceBefore.add(amountWei), getBalanceWei(Bob.ADDRESS)); } /** * Ether transfer tests using methods {@link Transaction#createEtherTransaction()}, and {@link Web3j#ethSendTransaction()}. * Sending account needs to be unlocked for this to work. */ @Test public void testCreateAndSendTransaction() throws Exception { String from = getCoinbase(); BigInteger nonce = getNonce(from); String to = Alice.ADDRESS; BigInteger amountWei = Convert.toWei("0.456", Convert.Unit.ETHER).toBigInteger(); // this is the method to test here Transaction transaction = Transaction .createEtherTransaction( from, nonce, Web3jConstants.GAS_PRICE, Web3jConstants.GAS_LIMIT_ETHER_TX, to, amountWei); // record account balances before the transfer BigInteger fromBalanceBefore = getBalanceWei(from); BigInteger toBalanceBefore = getBalanceWei(to); // send the transaction to the ethereum client EthSendTransaction ethSendTx = web3j .ethSendTransaction(transaction) .sendAsync() .get(); String txHash = ethSendTx.getTransactionHash(); assertFalse(txHash.isEmpty()); TransactionReceipt txReceipt = waitForReceipt(txHash); BigInteger txFee = txReceipt.getCumulativeGasUsed().multiply(Web3jConstants.GAS_PRICE); // coinbase might have gotten additional funds from mining BigInteger fromMinimumBalanceExpected = fromBalanceBefore.subtract(amountWei.add(txFee)); BigInteger fromBalanceActual = getBalanceWei(from); BigInteger fromBalanceDelta = fromBalanceActual.subtract(fromMinimumBalanceExpected); System.out.println("testCreateAndSendTransaction balance difference=" + fromBalanceDelta + " likely cause block reward (5 Ethers) and tx fees (" + txFee + " Weis)"); assertTrue("Unexected balance for 'from' address. difference=" + fromBalanceDelta, fromBalanceDelta.signum() >= 0); assertEquals("Unexected balance for 'to' address", toBalanceBefore.add(amountWei), getBalanceWei(to)); } /** * Ether transfer tests using methods {@link RawTransaction#createEtherTransaction()}, {@link TransactionEncoder#signMessage()} and {@link Web3j#ethSendRawTransaction()}. * Most complex transfer mechanism, but offers the highest flexibility. */ @Test public void testCreateSignAndSendTransaction() throws Exception { String from = Alice.ADDRESS; Credentials credentials = Alice.CREDENTIALS; BigInteger nonce = getNonce(from); String to = Bob.ADDRESS; BigInteger amountWei = Convert.toWei("0.789", Convert.Unit.ETHER).toBigInteger(); // create raw transaction RawTransaction txRaw = RawTransaction .createEtherTransaction( nonce, Web3jConstants.GAS_PRICE, Web3jConstants.GAS_LIMIT_ETHER_TX, to, amountWei); // sign raw transaction using the sender's credentials byte[] txSignedBytes = TransactionEncoder.signMessage(txRaw, credentials); String txSigned = Numeric.toHexString(txSignedBytes); BigInteger txFeeEstimate = Web3jConstants.GAS_LIMIT_ETHER_TX.multiply(Web3jConstants.GAS_PRICE); // make sure sender has sufficient funds ensureFunds(Alice.ADDRESS, amountWei.add(txFeeEstimate)); // record balanances before the ether transfer BigInteger fromBalanceBefore = getBalanceWei(Alice.ADDRESS); BigInteger toBalanceBefore = getBalanceWei(Bob.ADDRESS); // send the signed transaction to the ethereum client EthSendTransaction ethSendTx = web3j .ethSendRawTransaction(txSigned) .sendAsync() .get(); Error error = ethSendTx.getError(); assertTrue(error == null); String txHash = ethSendTx.getTransactionHash(); assertFalse(txHash.isEmpty()); TransactionReceipt txReceipt = waitForReceipt(txHash); BigInteger txFee = txReceipt.getCumulativeGasUsed().multiply(Web3jConstants.GAS_PRICE); assertEquals("Unexected balance for 'from' address", fromBalanceBefore.subtract(amountWei.add(txFee)), getBalanceWei(from)); assertEquals("Unexected balance for 'to' address", toBalanceBefore.add(amountWei), getBalanceWei(to)); } /** * Test accessing transactions, blocks and their attributes using methods {@link Web3j#ethGetTransactionByHash()}, {@link Web3j#ethGetBlockByHash()} {@link Web3j#ethGetBlockByNumber()}. */ @Test public void testTransactionAndBlockAttributes() throws Exception { String account0 = getCoinbase(); String account1 = getAccount(1); BigInteger transferAmount = new BigInteger("31415926"); String txHash = transferWei(account0, account1, transferAmount); waitForReceipt(txHash); // query for tx via tx hash value EthTransaction ethTx = web3j .ethGetTransactionByHash(txHash) .sendAsync() .get(); org.web3j.protocol.core.methods.response.Transaction tx = ethTx .getTransaction() .get(); String blockHash = tx.getBlockHash(); BigInteger blockNumber = tx.getBlockNumber(); String from = tx.getFrom(); String to = tx.getTo(); BigInteger amount = tx.getValue(); // check tx attributes assertTrue("Tx hash does not match input hash", txHash.equals(tx.getHash())); assertTrue("Tx block index invalid", blockNumber == null || blockNumber.compareTo(new BigInteger("0")) >= 0); assertTrue("Tx from account does not match input account", account0.equals(from)); assertTrue("Tx to account does not match input account", account1.equals(to)); assertTrue("Tx transfer amount does not match input amount", transferAmount.equals(amount)); // query for block by hash EthBlock ethBlock = web3j .ethGetBlockByHash(blockHash, true) .sendAsync() .get(); Block blockByHash = ethBlock.getBlock(); assertNotNull(String.format("Failed to get block for hash %s", blockHash), blockByHash); System.out.println("Got block for hash " + blockHash); // query for block by number DefaultBlockParameter blockParameter = DefaultBlockParameter .valueOf(blockNumber); ethBlock = web3j .ethGetBlockByNumber(blockParameter, true) .sendAsync() .get(); Block blockByNumber = ethBlock.getBlock(); assertNotNull(String.format("Failed to get block for number %d", blockNumber), blockByNumber); System.out.println("Got block for number " + blockNumber); assertTrue("Bad tx hash for block by number", blockByNumber.getHash().equals(blockHash)); assertTrue("Bad tx number for block by hash", blockByHash.getNumber().equals(blockNumber)); assertTrue("Query block by hash and number have different parent hashes", blockByHash.getParentHash().equals(blockByNumber.getParentHash())); assertTrue("Query block by hash and number results in different blocks", blockByHash.equals(blockByNumber)); // find original tx in block boolean found = false; for(TransactionResult<?> txResult: blockByHash.getTransactions()) { TransactionObject txObject = (TransactionObject) txResult; // verify tx attributes returned by block query if(txObject.getHash().equals(txHash)) { assertTrue("Tx from block has bad from", txObject.getFrom().equals(account0)); assertTrue("Tx from block has bad to", txObject.getTo().equals(account1)); assertTrue("Tx from block has bad amount", txObject.getValue().equals(transferAmount)); found = true; break; } } assertTrue("Tx not found in blocks transaction list", found); } }