diff --git a/core/src/main/java/com/google/bitcoin/core/Wallet.java b/core/src/main/java/com/google/bitcoin/core/Wallet.java
index 4f141fe9..248c011a 100644
--- a/core/src/main/java/com/google/bitcoin/core/Wallet.java
+++ b/core/src/main/java/com/google/bitcoin/core/Wallet.java
@@ -1648,13 +1648,18 @@ public class Wallet implements Serializable, BlockChainListener {
*/
public static class SendRequest {
/**
- * A transaction, probably incomplete, that describes the outline of what you want to do. This typically will
+ *
A transaction, probably incomplete, that describes the outline of what you want to do. This typically will
* mean it has some outputs to the intended destinations, but no inputs or change address (and therefore no
* fees) - the wallet will calculate all that for you and update tx later.
*
* Be careful when adding outputs that you check the min output value
* ({@link TransactionOutput#getMinNonDustValue(BigInteger)}) to avoid the whole transaction being rejected
* because one output is dust.
+ *
+ * If there are already inputs to the transaction, make sure their out point has a connected output,
+ * otherwise their value will be added to fee. Also ensure they are either signed or are spendable by a wallet
+ * key, otherwise the behavior of {@link Wallet#completeTx(Wallet.SendRequest, boolean)} is undefined (likely
+ * RuntimeException).
*/
public Transaction tx;
@@ -1666,15 +1671,37 @@ public class Wallet implements Serializable, BlockChainListener {
public Address changeAddress = null;
/**
- * A transaction can have a fee attached, which is defined as the difference between the input values
+ * A transaction can have a fee attached, which is defined as the difference between the input values
* and output values. Any value taken in that is not provided to an output can be claimed by a miner. This
* is how mining is incentivized in later years of the Bitcoin system when inflation drops. It also provides
* a way for people to prioritize their transactions over others and is used as a way to make denial of service
- * attacks expensive. Some transactions require a fee due to their structure - currently bitcoinj does not
- * correctly calculate this! As of late 2012 most transactions require no fee.
+ * attacks expensive.
+ *
+ * This is a constant fee (in satoshis) which will be added to the transaction. It is recommended that it be
+ * at least {@link Transaction#REFERENCE_DEFAULT_MIN_TX_FEE} if it is set, as default reference clients will
+ * otherwise simply treat the transaction as if there were no fee at all.
+ *
+ * You might also consider adding a {@link SendRequest#feePerKb} to set the fee per kb of transaction size
+ * (rounded down to the nearest kb) as that is how transactions are sorted when added to a block by miners.
*/
public BigInteger fee = BigInteger.ZERO;
+ /**
+ * A transaction can have a fee attached, which is defined as the difference between the input values
+ * and output values. Any value taken in that is not provided to an output can be claimed by a miner. This
+ * is how mining is incentivized in later years of the Bitcoin system when inflation drops. It also provides
+ * a way for people to prioritize their transactions over others and is used as a way to make denial of service
+ * attacks expensive.
+ *
+ * This is a dynamic fee (in satoshis) which will be added to the transaction for each kilobyte in size after
+ * the first. This is useful as as miners usually sort pending transactions by their fee per unit size when
+ * choosing which transactions to add to a block. Note that, to keep this equivalent to the reference client
+ * definition, a kilobyte is defined as 1000 bytes, not 1024.
+ *
+ * You might also consider using a {@link SendRequest#fee} to set the fee added for the first kb of size.
+ */
+ public BigInteger feePerKb = BigInteger.ZERO;
+
/**
* The AES key to use to decrypt the private keys before signing.
* If null then no decryption will be performed and if decryption is required an exception will be thrown.
@@ -1683,7 +1710,8 @@ public class Wallet implements Serializable, BlockChainListener {
public KeyParameter aesKey = null;
// Tracks if this has been passed to wallet.completeTx already: just a safety check.
- private boolean completed;
+ // default for testing
+ boolean completed;
private SendRequest() {}
@@ -1733,8 +1761,9 @@ public class Wallet implements Serializable, BlockChainListener {
* and lets you see the proposed transaction before anything is done with it.
*
* This is a helper method that is equivalent to using {@link Wallet.SendRequest#to(Address, java.math.BigInteger)}
- * followed by {@link Wallet#completeTx(com.google.bitcoin.core.Wallet.SendRequest)} and returning the requests
- * transaction object. If you want more control over the process, just do those two steps yourself.
+ * followed by {@link Wallet#completeTx(Wallet.SendRequest, true)} and returning the requests transaction object.
+ * Note that this means a fee may be automatically added if required, if you want more control over the process,
+ * just do those two steps yourself.
*
* IMPORTANT: This method does NOT update the wallet. If you call createSend again you may get two transactions
* that spend the same coins. You have to call {@link Wallet#commitTx(Transaction)} on the created transaction to
@@ -1751,7 +1780,7 @@ public class Wallet implements Serializable, BlockChainListener {
*/
public Transaction createSend(Address address, BigInteger nanocoins) {
SendRequest req = SendRequest.to(address, nanocoins);
- if (completeTx(req)) {
+ if (completeTx(req, true) != null) {
return req.tx;
} else {
return null; // No money.
@@ -1762,14 +1791,19 @@ public class Wallet implements Serializable, BlockChainListener {
* Sends coins to the given address but does not broadcast the resulting pending transaction. It is still stored
* in the wallet, so when the wallet is added to a {@link PeerGroup} or {@link Peer} the transaction will be
* announced to the network. The given {@link SendRequest} is completed first using
- * {@link Wallet#completeTx(com.google.bitcoin.core.Wallet.SendRequest)} to make it valid.
+ * {@link Wallet#completeTx(Wallet.SendRequest, boolean)} to make it valid.
*
+ * @param enforceDefaultReferenceClientFeeRelayRules Requires that there be enough fee for a default reference client to at least relay the transaction.
+ * (ie ensure the transaction will not be outright rejected by the network).
+ * Note that this does not enforce certain fee rules that only apply to transactions which are larger than
+ * 26,000 bytes. If you get a transaction which is that large, you should set a fee and feePerKb of at least
+ * {@link Transaction#REFERENCE_DEFAULT_MIN_TX_FEE}
* @return the Transaction that was created, or null if there are insufficient coins in the wallet.
*/
- public Transaction sendCoinsOffline(SendRequest request) {
+ public Transaction sendCoinsOffline(SendRequest request, boolean enforceDefaultReferenceClientFeeRelayRules) {
lock.lock();
try {
- if (!completeTx(request))
+ if (completeTx(request, enforceDefaultReferenceClientFeeRelayRules) == null)
return null; // Not enough money! :-(
commitTx(request.tx);
return request.tx;
@@ -1782,7 +1816,8 @@ public class Wallet implements Serializable, BlockChainListener {
/**
*
Sends coins to the given address, via the given {@link PeerGroup}. Change is returned to
- * {@link Wallet#getChangeAddress()}. No fee is attached even if one would be required.
+ * {@link Wallet#getChangeAddress()}. Note that a fee may be automatically added if one may be required for the
+ * transaction to be confirmed.
*
* The returned object provides both the transaction, and a future that can be used to learn when the broadcast
* is complete. Complete means, if the PeerGroup is limited to only one connection, when it was written out to
@@ -1802,7 +1837,7 @@ public class Wallet implements Serializable, BlockChainListener {
*/
public SendResult sendCoins(PeerGroup peerGroup, Address to, BigInteger value) {
SendRequest request = SendRequest.to(to, value);
- return sendCoins(peerGroup, request);
+ return sendCoins(peerGroup, request, true);
}
/**
@@ -1818,14 +1853,19 @@ public class Wallet implements Serializable, BlockChainListener {
*
* @param peerGroup a PeerGroup to use for broadcast or null.
* @param request the SendRequest that describes what to do, get one using static methods on SendRequest itself.
+ * @param enforceDefaultReferenceClientFeeRelayRules Requires that there be enough fee for a default reference client to at least relay the transaction
+ * (ie ensure the transaction will not be outright rejected by the network).
+ * Note that this does not enforce certain fee rules that only apply to transactions which are larger than
+ * 26,000 bytes. If you get a transaction which is that large, you should set a fee and feePerKb of at least
+ * {@link Transaction#REFERENCE_DEFAULT_MIN_TX_FEE}
* @return An object containing the transaction that was created, and a future for the broadcast of it.
*/
- public SendResult sendCoins(PeerGroup peerGroup, SendRequest request) {
+ public SendResult sendCoins(PeerGroup peerGroup, SendRequest request, boolean enforceDefaultReferenceClientFeeRelayRules) {
// Does not need to be synchronized as sendCoinsOffline is and the rest is all thread-local.
// Commit the TX to the wallet immediately so the spent coins won't be reused.
// TODO: We should probably allow the request to specify tx commit only after the network has accepted it.
- Transaction tx = sendCoinsOffline(request);
+ Transaction tx = sendCoinsOffline(request, enforceDefaultReferenceClientFeeRelayRules);
if (tx == null)
return null; // Not enough money.
SendResult result = new SendResult();
@@ -1842,13 +1882,14 @@ public class Wallet implements Serializable, BlockChainListener {
/**
* Sends coins to the given address, via the given {@link Peer}. Change is returned to {@link Wallet#getChangeAddress()}.
* If an exception is thrown by {@link Peer#sendMessage(Message)} the transaction is still committed, so the
- * pending transaction must be broadcast by you at some other time.
+ * pending transaction must be broadcast by you at some other time. Note that a fee may be automatically added
+ * if one may be required for the transaction to be confirmed.
*
* @return The {@link Transaction} that was created or null if there was insufficient balance to send the coins.
* @throws IOException if there was a problem broadcasting the transaction
*/
public Transaction sendCoins(Peer peer, SendRequest request) throws IOException {
- Transaction tx = sendCoinsOffline(request);
+ Transaction tx = sendCoinsOffline(request, true);
if (tx == null)
return null; // Not enough money.
peer.sendMessage(tx);
@@ -1860,10 +1901,15 @@ public class Wallet implements Serializable, BlockChainListener {
* to the instructions in the request. The transaction in the request is modified by this method.
*
* @param req a SendRequest that contains the incomplete transaction and details for how to make it valid.
+ * @param enforceDefaultReferenceClientFeeRelayRules Requires that there be enough fee for a default reference client to at least relay the transaction
+ * (ie ensure the transaction will not be outright rejected by the network).
+ * Note that this does not enforce certain fee rules that only apply to transactions which are larger than
+ * 26,000 bytes. If you get a transaction which is that large, you should set a fee and feePerKb of at least
+ * {@link Transaction#REFERENCE_DEFAULT_MIN_TX_FEE}
* @throws IllegalArgumentException if you try and complete the same SendRequest twice.
- * @return False if we cannot afford this send, true otherwise.
+ * @return Either the total fee paid (assuming all existing inputs had a connected output) or null if we cannot afford the transaction.
*/
- public boolean completeTx(SendRequest req) {
+ public BigInteger completeTx(SendRequest req, boolean enforceDefaultReferenceClientFeeRelayRules) {
lock.lock();
try {
Preconditions.checkArgument(!req.completed, "Given SendRequest has already been completed.");
@@ -1872,11 +1918,32 @@ public class Wallet implements Serializable, BlockChainListener {
for (TransactionOutput output : req.tx.getOutputs()) {
value = value.add(output.getValue());
}
- value = value.add(req.fee);
+ BigInteger totalOutput = value;
- log.info("Completing send tx with {} outputs totalling {}",
+ log.info("Completing send tx with {} outputs totalling {} (not including fees)",
req.tx.getOutputs().size(), bitcoinValueToFriendlyString(value));
+ // If any inputs have already been added, we don't need to get their value from wallet
+ BigInteger totalInput = BigInteger.ZERO;
+ for (TransactionInput input : req.tx.getInputs())
+ if (input.getConnectedOutput() != null)
+ totalInput = totalInput.add(input.getConnectedOutput().getValue());
+ else
+ log.warn("SendRequest transaction already has inputs but we don't know how much they are worth - they will be added to fee.");
+ value = value.subtract(totalInput);
+
+ List originalInputs = new ArrayList(req.tx.getInputs());
+
+ // We need to know if we need to add an additional fee because one of our values are smaller than 0.01 BTC
+ boolean needAtLeastReferenceFee = false;
+ if (enforceDefaultReferenceClientFeeRelayRules) {
+ for (TransactionOutput output : req.tx.getOutputs())
+ if (output.getValue().compareTo(Utils.CENT) < 0) {
+ needAtLeastReferenceFee = true;
+ break;
+ }
+ }
+
// Calculate a list of ALL potential candidates for spending and then ask a coin selector to provide us
// with the actual outputs that'll be used to gather the required amount of value. In this way, users
// can customize coin selection policies.
@@ -1886,30 +1953,198 @@ public class Wallet implements Serializable, BlockChainListener {
// Note that output.isMine(this) needs to test the keychain which is currently an array, so it's
// O(candidate outputs ^ keychain.size())! There's lots of low hanging fruit here.
LinkedList candidates = calculateSpendCandidates(true);
- // Of the coins we could spend, pick some that we actually will spend.
- CoinSelection selection = coinSelector.select(value, candidates);
- // Can we afford this?
- if (selection.valueGathered.compareTo(value) < 0) {
- log.warn("Insufficient value in wallet for send, missing " +
- bitcoinValueToFriendlyString(value.subtract(selection.valueGathered)));
+ Address changeAddress = req.changeAddress;
+ int minSize = 0;
+ // There are 3 possibilities for what adding change might do:
+ // 1) No effect
+ // 2) Causes increase in fee (change < 0.01 COINS)
+ // 3) Causes the transaction to have a dust output or change < fee increase (ie change will be thrown away)
+ // If we get either of the last 2, we keep note of what the inputs looked like at the time and move try to
+ // add inputs as we go up the list (keeping track of minimum inputs for each category). At the end, we pick
+ // the best input set as the one which generates the lowest total fee.
+ BigInteger additionalValueForNextCategory = null;
+ CoinSelection selection3 = null;
+ CoinSelection selection2 = null; TransactionOutput selection2Change = null;
+ CoinSelection selection1 = null; TransactionOutput selection1Change = null;
+ while (true) {
+ req.tx.clearInputs();
+ for (TransactionInput input : originalInputs)
+ req.tx.addInput(input);
+
+ BigInteger fees = req.fee.add(BigInteger.valueOf(minSize/1000).multiply(req.feePerKb));
+ if (needAtLeastReferenceFee && fees.compareTo(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE) < 0)
+ fees = Transaction.REFERENCE_DEFAULT_MIN_TX_FEE;
+
+ BigInteger valueNeeded = value.add(fees);
+ if (additionalValueForNextCategory != null)
+ valueNeeded = valueNeeded.add(additionalValueForNextCategory);
+ BigInteger additionalValueSelected = additionalValueForNextCategory;
+
+ // Of the coins we could spend, pick some that we actually will spend.
+ CoinSelection selection = coinSelector.select(valueNeeded, candidates);
+ // Can we afford this?
+ if (selection.valueGathered.compareTo(valueNeeded) < 0)
+ break;
+ checkState(selection.gathered.size() > 0 || originalInputs.size() > 0);
+
+ // We keep track of an upper bound on transaction size to calculate fees that need added
+ // Note that the difference between the upper bound and lower bound is usually small enough that it
+ // will be very rare that we pay a fee we do not need to
+ int size = 0;
+
+ // We can't be sure a selection is valid until we check fee per kb at the end, so we just store them here temporarily
+ boolean eitherCategory2Or3 = false;
+ boolean isCategory3 = false;
+
+ BigInteger change = selection.valueGathered.subtract(valueNeeded);
+ if (additionalValueSelected != null)
+ change = change.add(additionalValueSelected);
+
+ TransactionOutput changeOutput = null;
+ // If change is < 0.01 BTC, we will need to have at least minfee to be accepted by the network
+ if (enforceDefaultReferenceClientFeeRelayRules && !change.equals(BigInteger.ZERO) &&
+ change.compareTo(Utils.CENT) < 0 && fees.compareTo(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE) < 0) {
+ // This solution may fit into category 2, but it may also be category 3, we'll check that later
+ eitherCategory2Or3 = true;
+ additionalValueForNextCategory = Utils.CENT;
+ // If the change is smaller than the fee we want to add, this will be negative
+ change = change.subtract(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE.subtract(fees));
+ }
+
+ if (change.compareTo(BigInteger.ZERO) > 0) {
+ // The value of the inputs is greater than what we want to send. Just like in real life then,
+ // we need to take back some coins ... this is called "change". Add another output that sends the change
+ // back to us. The address comes either from the request or getChangeAddress() as a default..
+ if (changeAddress == null)
+ changeAddress = getChangeAddress();
+ changeOutput = new TransactionOutput(params, req.tx, change, changeAddress);
+ // If the change output would result in this transaction being rejected as dust, just drop the change and make it a fee
+ if (enforceDefaultReferenceClientFeeRelayRules && Transaction.MIN_NONDUST_OUTPUT.compareTo(change) >= 0) {
+ // This solution definitely fits in category 3
+ isCategory3 = true;
+ additionalValueForNextCategory = Transaction.REFERENCE_DEFAULT_MIN_TX_FEE.add(
+ Transaction.MIN_NONDUST_OUTPUT.add(BigInteger.ONE));
+ } else {
+ size += changeOutput.bitcoinSerialize().length + VarInt.sizeOf(req.tx.getOutputs().size()) - VarInt.sizeOf(req.tx.getOutputs().size() - 1);
+ // This solution is either category 1 or 2
+ if (!eitherCategory2Or3) // must be category 1
+ additionalValueForNextCategory = null;
+ }
+ } else {
+ if (eitherCategory2Or3) {
+ // This solution definitely fits in category 3 (we threw away change because it was smaller than MIN_TX_FEE)
+ isCategory3 = true;
+ additionalValueForNextCategory = Transaction.REFERENCE_DEFAULT_MIN_TX_FEE.add(BigInteger.ONE);
+ }
+ }
+
+ for (TransactionOutput output : selection.gathered) {
+ req.tx.addInput(output);
+ // If the scriptBytes don't default to none, our size calculations will be thrown off
+ checkState(req.tx.getInput(req.tx.getInputs().size()-1).getScriptBytes().length == 0);
+ try {
+ if (output.getScriptPubKey().isSentToAddress()) {
+ // Send-to-address spends usually take maximum pubkey.length (as it may be compressed or not) + 75 bytes
+ size += this.findKeyFromPubHash(output.getScriptPubKey().getPubKeyHash()).getPubKey().length + 75;
+ } else if (output.getScriptPubKey().isSentToRawPubKey())
+ size += 74; // Send-to-pubkey spends usually take maximum 74 bytes to spend
+ else
+ throw new RuntimeException("Unknown output type returned in coin selection");
+ } catch (ScriptException e) {
+ // If this happens it means an output script in a wallet tx could not be understood. That should never
+ // happen, if it does it means the wallet has got into an inconsistent state.
+ throw new RuntimeException(e);
+ }
+ }
+
+ // Estimate transaction size and loop again if we need more fee per kb
+ size += req.tx.bitcoinSerialize().length;
+ if (size/1000 > minSize/1000 && req.feePerKb.compareTo(BigInteger.ZERO) > 0) {
+ minSize = size;
+ // We need more fees anyway, just try again with the same additional value
+ additionalValueForNextCategory = additionalValueSelected;
+ continue;
+ }
+
+ if (isCategory3) {
+ if (selection3 == null)
+ selection3 = selection;
+ } else if (eitherCategory2Or3) {
+ // If we are in selection2, we will require at least CENT additional. If we do that, there is no way
+ // we can end up back here because CENT additional will always get us to 1
+ checkState(selection2 == null);
+ checkState(additionalValueForNextCategory.equals(Utils.CENT));
+ selection2 = selection;
+ selection2Change = checkNotNull(changeOutput); // If we get no change in category 2, we are actually in category 3
+ } else {
+ // Once we get a category 1 (change kept), we should break out of the loop because we can't do better
+ checkState(selection1 == null);
+ checkState(additionalValueForNextCategory == null);
+ selection1 = selection;
+ selection1Change = changeOutput;
+ }
+
+ if (additionalValueForNextCategory != null) {
+ if (additionalValueSelected != null)
+ checkState(additionalValueForNextCategory.compareTo(additionalValueSelected) > 0);
+ continue;
+ }
+ break;
+ }
+
+ req.tx.clearInputs();
+ for (TransactionInput input : originalInputs)
+ req.tx.addInput(input);
+
+ if (selection3 == null && selection2 == null && selection1 == null) {
+ log.warn("Insufficient value in wallet for send");
// TODO: Should throw an exception here.
- return false;
+ return null;
}
- checkState(selection.gathered.size() > 0);
- req.tx.getConfidence().setConfidenceType(ConfidenceType.PENDING);
- BigInteger change = selection.valueGathered.subtract(value);
- if (change.compareTo(BigInteger.ZERO) > 0) {
- // The value of the inputs is greater than what we want to send. Just like in real life then,
- // we need to take back some coins ... this is called "change". Add another output that sends the change
- // back to us. The address comes either from the request or getChangeAddress() as a default.
- Address changeAddress = req.changeAddress != null ? req.changeAddress : getChangeAddress();
- log.info(" with {} coins change", bitcoinValueToFriendlyString(change));
- req.tx.addOutput(new TransactionOutput(params, req.tx, change, changeAddress));
+
+ BigInteger lowestFee = null;
+ CoinSelection bestCoinSelection = null;
+ TransactionOutput bestChangeOutput = null;
+ if (selection1 != null) {
+ if (selection1Change != null)
+ lowestFee = selection1.valueGathered.subtract(selection1Change.getValue());
+ else
+ lowestFee = selection1.valueGathered;
+ bestCoinSelection = selection1;
+ bestChangeOutput = selection1Change;
}
- for (TransactionOutput output : selection.gathered) {
+
+ if (selection2 != null) {
+ BigInteger fee = selection2.valueGathered.subtract(checkNotNull(selection2Change).getValue());
+ if (lowestFee == null || fee.compareTo(lowestFee) < 0) {
+ lowestFee = fee;
+ bestCoinSelection = selection2;
+ bestChangeOutput = selection2Change;
+ }
+ }
+
+ if (selection3 != null) {
+ if (lowestFee == null || selection3.valueGathered.compareTo(lowestFee) < 0) {
+ bestCoinSelection = selection3;
+ bestChangeOutput = null;
+ }
+ }
+
+ for (TransactionOutput output : bestCoinSelection.gathered)
req.tx.addInput(output);
+
+ totalInput = totalInput.add(bestCoinSelection.valueGathered);
+
+ req.tx.getConfidence().setConfidenceType(ConfidenceType.PENDING);
+
+ if (bestChangeOutput != null) {
+ req.tx.addOutput(bestChangeOutput);
+ totalOutput = totalOutput.add(bestChangeOutput.getValue());
+ log.info(" with {} coins change", bitcoinValueToFriendlyString(bestChangeOutput.getValue()));
}
+ //TODO: Shuffle inputs for some anonymity
+
// Now sign the inputs, thus proving that we are entitled to redeem the connected outputs.
try {
req.tx.signInputs(Transaction.SigHash.ALL, this, req.aesKey);
@@ -1925,7 +2160,7 @@ public class Wallet implements Serializable, BlockChainListener {
// TODO: Throw an exception here.
log.error("Transaction could not be created without exceeding max size: {} vs {}", size,
Transaction.MAX_STANDARD_TX_SIZE);
- return false;
+ return null;
}
// Label the transaction as being self created. We can use this later to spend its change output even before
@@ -1934,7 +2169,7 @@ public class Wallet implements Serializable, BlockChainListener {
req.completed = true;
log.info(" completed {} with {} inputs", req.tx.getHashAsString(), req.tx.getInputs().size());
- return true;
+ return totalInput.subtract(totalOutput);
} finally {
lock.unlock();
}
diff --git a/core/src/test/java/com/google/bitcoin/core/PeerGroupTest.java b/core/src/test/java/com/google/bitcoin/core/PeerGroupTest.java
index 0efa8aa1..df202590 100644
--- a/core/src/test/java/com/google/bitcoin/core/PeerGroupTest.java
+++ b/core/src/test/java/com/google/bitcoin/core/PeerGroupTest.java
@@ -306,7 +306,7 @@ public class PeerGroupTest extends TestWithPeerGroup {
// Do the same thing with an offline transaction.
peerGroup.removeWallet(wallet);
- Transaction t3 = wallet.sendCoinsOffline(Wallet.SendRequest.to(dest, Utils.toNanoCoins(2, 0)));
+ Transaction t3 = wallet.sendCoinsOffline(Wallet.SendRequest.to(dest, Utils.toNanoCoins(2, 0)), false);
assertNull(outbound(p1)); // Nothing sent.
// Add the wallet to the peer group (simulate initialization). Transactions should be announced.
peerGroup.addWallet(wallet);
diff --git a/core/src/test/java/com/google/bitcoin/core/WalletTest.java b/core/src/test/java/com/google/bitcoin/core/WalletTest.java
index 89cd708e..a55fe20d 100644
--- a/core/src/test/java/com/google/bitcoin/core/WalletTest.java
+++ b/core/src/test/java/com/google/bitcoin/core/WalletTest.java
@@ -17,6 +17,7 @@
package com.google.bitcoin.core;
import com.google.bitcoin.core.Transaction.SigHash;
+import com.google.bitcoin.core.Wallet.SendRequest;
import com.google.bitcoin.core.WalletTransaction.Pool;
import com.google.bitcoin.crypto.KeyCrypter;
import com.google.bitcoin.crypto.KeyCrypterException;
@@ -51,6 +52,7 @@ import static com.google.bitcoin.core.TestUtils.createFakeTx;
import static com.google.bitcoin.core.Utils.bitcoinValueToFriendlyString;
import static com.google.bitcoin.core.Utils.toNanoCoins;
import static org.junit.Assert.*;
+import static org.junit.Assert.assertEquals;
public class WalletTest extends TestWithWallet {
public Logger log = LoggerFactory.getLogger(WalletTest.class.getName());
@@ -125,7 +127,7 @@ public class WalletTest extends TestWithWallet {
if (testEncryption) {
// Try to create a send with a fee but no password (this should fail).
try {
- wallet.completeTx(req);
+ wallet.completeTx(req, false);
fail("No exception was thrown trying to sign an encrypted key with no password supplied.");
} catch (KeyCrypterException kce) {
assertEquals("This ECKey is encrypted but no decryption key has been supplied.", kce.getMessage());
@@ -139,7 +141,7 @@ public class WalletTest extends TestWithWallet {
req.fee = toNanoCoins(0, 1);
try {
- wallet.completeTx(req);
+ wallet.completeTx(req, false);
fail("No exception was thrown trying to sign an encrypted key with the wrong password supplied.");
} catch (KeyCrypterException kce) {
assertEquals("Could not decrypt bytes", kce.getMessage());
@@ -155,7 +157,7 @@ public class WalletTest extends TestWithWallet {
}
// Complete the transaction successfully.
- wallet.completeTx(req);
+ wallet.completeTx(req, false);
Transaction t2 = req.tx;
assertEquals("Wrong number of UNSPENT.3", 1, wallet.getPoolSize(WalletTransaction.Pool.UNSPENT));
@@ -236,7 +238,7 @@ public class WalletTest extends TestWithWallet {
Wallet.SendRequest req = Wallet.SendRequest.to(new ECKey().toAddress(params), toNanoCoins(0, 48));
req.aesKey = aesKey;
Address a = req.changeAddress = new ECKey().toAddress(params);
- wallet.completeTx(req);
+ wallet.completeTx(req, false);
Transaction t3 = req.tx;
assertEquals(a, t3.getOutput(1).getScriptPubKey().getToAddress(params));
assertNotNull(t3);
@@ -269,7 +271,7 @@ public class WalletTest extends TestWithWallet {
t2.addOutput(v2, a2);
t2.addOutput(v3, a2);
t2.addOutput(v4, a2);
- boolean complete = wallet.completeTx(Wallet.SendRequest.forTx(t2));
+ boolean complete = wallet.completeTx(Wallet.SendRequest.forTx(t2), false) != null;
// Do some basic sanity checks.
assertTrue(complete);
@@ -972,7 +974,7 @@ public class WalletTest extends TestWithWallet {
Transaction t2 = new Transaction(params);
TransactionOutput o2 = new TransactionOutput(params, t2, v2, k2.toAddress(params));
t2.addOutput(o2);
- boolean complete = wallet.completeTx(Wallet.SendRequest.forTx(t2));
+ boolean complete = wallet.completeTx(Wallet.SendRequest.forTx(t2), false) != null;
assertTrue(complete);
// Commit t2, so it is placed in the pending pool
@@ -1173,7 +1175,382 @@ public class WalletTest extends TestWithWallet {
tx.addOutput(v, new Address(params, bits));
}
Wallet.SendRequest req = Wallet.SendRequest.forTx(tx);
- assertFalse(wallet.completeTx(req));
+ assertNull(wallet.completeTx(req, true));
+ }
+
+ @Test
+ public void feeSolverAndCoinSelectionTest() throws Exception {
+ // Make sure TestWithWallet isnt doing anything crazy.
+ assertTrue(wallet.getTransactions(true).size() == 0);
+
+ Address notMyAddr = new ECKey().toAddress(params);
+
+ // Generate a few outputs to us that are far too small to spend reasonably
+ StoredBlock block = new StoredBlock(makeSolvedTestBlock(blockStore, notMyAddr), BigInteger.ONE, 1);
+ Transaction tx1 = createFakeTx(params, BigInteger.ONE, myAddress);
+ wallet.receiveFromBlock(tx1, block, AbstractBlockChain.NewBlockType.BEST_CHAIN);
+ Transaction tx2 = createFakeTx(params, BigInteger.ONE, myAddress); assertTrue(!tx1.getHash().equals(tx2.getHash()));
+ wallet.receiveFromBlock(tx2, block, AbstractBlockChain.NewBlockType.BEST_CHAIN);
+ Transaction tx3 = createFakeTx(params, BigInteger.TEN, myAddress);
+ wallet.receiveFromBlock(tx3, block, AbstractBlockChain.NewBlockType.BEST_CHAIN);
+
+ // No way we can add nearly enough fee
+ assertTrue(wallet.createSend(notMyAddr, BigInteger.ONE) == null);
+ // Spend it all without fee enforcement
+ assertTrue(wallet.sendCoinsOffline(Wallet.SendRequest.to(notMyAddr, BigInteger.TEN.add(BigInteger.ONE.add(BigInteger.ONE))), false) != null);
+ assertTrue(wallet.getBalance().equals(BigInteger.ZERO));
+
+ // Add some reasonable-sized outputs
+ block = new StoredBlock(makeSolvedTestBlock(blockStore, notMyAddr), BigInteger.ONE, 1);
+ Transaction tx4 = createFakeTx(params, Utils.COIN, myAddress);
+ wallet.receiveFromBlock(tx4, block, AbstractBlockChain.NewBlockType.BEST_CHAIN);
+
+ // Simple test to make sure if we have an ouput < 0.01 we get a fee
+ Transaction spend1 = wallet.createSend(notMyAddr, Utils.CENT.subtract(BigInteger.ONE));
+ assertTrue(spend1.getOutputs().size() == 2);
+ // We optimize for priority, so the output selected should be the largest one
+ assertTrue(spend1.getOutput(0).getValue().add(spend1.getOutput(1).getValue())
+ .equals(Utils.COIN.subtract(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE))); // We should have paid the default minfee
+
+ // But not at exactly 0.01
+ Transaction spend2 = wallet.createSend(notMyAddr, Utils.CENT);
+ assertTrue(spend2.getOutputs().size() == 2);
+ // We optimize for priority, so the output selected should be the largest one
+ assertTrue(spend2.getOutput(0).getValue().add(spend2.getOutput(1).getValue()).equals(Utils.COIN));
+
+ // ...but not more fee than what we request
+ SendRequest request3 = SendRequest.to(notMyAddr, Utils.CENT.subtract(BigInteger.ONE));
+ request3.fee = Transaction.REFERENCE_DEFAULT_MIN_TX_FEE.add(BigInteger.ONE);
+ assertEquals(wallet.completeTx(request3, true), request3.fee);
+ Transaction spend3 = request3.tx;
+ assertTrue(spend3.getOutputs().size() == 2);
+ // We optimize for priority, so the output selected should be the largest one
+ assertTrue(spend3.getOutput(0).getValue().add(spend3.getOutput(1).getValue())
+ .equals(Utils.COIN.subtract(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE.add(BigInteger.ONE))));
+
+ // ...unless we need it
+ SendRequest request4 = SendRequest.to(notMyAddr, Utils.CENT.subtract(BigInteger.ONE));
+ request4.fee = Transaction.REFERENCE_DEFAULT_MIN_TX_FEE.subtract(BigInteger.ONE);
+ assertEquals(wallet.completeTx(request4, true), Transaction.REFERENCE_DEFAULT_MIN_TX_FEE);
+ Transaction spend4 = request4.tx;
+ assertTrue(spend4.getOutputs().size() == 2);
+ // We optimize for priority, so the output selected should be the largest one
+ assertTrue(spend4.getOutput(0).getValue().add(spend4.getOutput(1).getValue())
+ .equals(Utils.COIN.subtract(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE)));
+
+ SendRequest request5 = SendRequest.to(notMyAddr, Utils.COIN.subtract(Utils.CENT.subtract(BigInteger.ONE)));
+ assertEquals(wallet.completeTx(request5, true), Transaction.REFERENCE_DEFAULT_MIN_TX_FEE);
+ Transaction spend5 = request5.tx;
+ // If we would have a change output < 0.01, it should add the fee
+ assertTrue(spend5.getOutputs().size() == 2);
+ // We optimize for priority, so the output selected should be the largest one
+ assertTrue(spend5.getOutput(0).getValue().add(spend5.getOutput(1).getValue())
+ .equals(Utils.COIN.subtract(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE)));
+
+ SendRequest request6 = SendRequest.to(notMyAddr, Utils.COIN.subtract(Utils.CENT));
+ assertEquals(wallet.completeTx(request6, true), BigInteger.ZERO);
+ Transaction spend6 = request6.tx;
+ // ...but not if change output == 0.01
+ assertTrue(spend6.getOutputs().size() == 2);
+ // We optimize for priority, so the output selected should be the largest one
+ assertTrue(spend6.getOutput(0).getValue().add(spend6.getOutput(1).getValue()).equals(Utils.COIN));
+
+ SendRequest request7 = SendRequest.to(notMyAddr, Utils.COIN.subtract(Utils.CENT.subtract(BigInteger.valueOf(2)).multiply(BigInteger.valueOf(2))));
+ request7.tx.addOutput(Utils.CENT.subtract(BigInteger.ONE), notMyAddr);
+ assertEquals(wallet.completeTx(request7, true), Transaction.REFERENCE_DEFAULT_MIN_TX_FEE);
+ Transaction spend7 = request7.tx;
+ // If change is 0.1-nanocoin and we already have a 0.1-nanocoin output, fee should be reference fee
+ assertTrue(spend7.getOutputs().size() == 3);
+ // We optimize for priority, so the output selected should be the largest one
+ assertTrue(spend7.getOutput(0).getValue().add(spend7.getOutput(1).getValue()).add(spend7.getOutput(2).getValue())
+ .equals(Utils.COIN.subtract(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE)));
+
+ SendRequest request8 = SendRequest.to(notMyAddr, Utils.COIN.subtract(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE));
+ assertEquals(wallet.completeTx(request8, true), Transaction.REFERENCE_DEFAULT_MIN_TX_FEE);
+ Transaction spend8 = request8.tx;
+ // If we would have a change output == REFERENCE_DEFAULT_MIN_TX_FEE that would cause a fee, throw it away and make it fee
+ assertTrue(spend8.getOutputs().size() == 1);
+ // We optimize for priority, so the output selected should be the largest one
+ assertTrue(spend8.getOutput(0).getValue().equals(Utils.COIN.subtract(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE)));
+
+ SendRequest request9 = SendRequest.to(notMyAddr, Utils.COIN.subtract(
+ Transaction.REFERENCE_DEFAULT_MIN_TX_FEE.add(Transaction.MIN_NONDUST_OUTPUT)));
+ assertEquals(wallet.completeTx(request9, true), Transaction.REFERENCE_DEFAULT_MIN_TX_FEE.add(Transaction.MIN_NONDUST_OUTPUT));
+ Transaction spend9 = request9.tx;
+ // ...in fact, also add fee if we would get back less than MIN_NONDUST_OUTPUT
+ assertTrue(spend9.getOutputs().size() == 1);
+ // We optimize for priority, so the output selected should be the largest one
+ assertTrue(spend9.getOutput(0).getValue().equals(
+ Utils.COIN.subtract(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE.add(Transaction.MIN_NONDUST_OUTPUT))));
+
+ SendRequest request10 = SendRequest.to(notMyAddr, Utils.COIN.subtract(
+ Transaction.REFERENCE_DEFAULT_MIN_TX_FEE.add(Transaction.MIN_NONDUST_OUTPUT).add(BigInteger.ONE)));
+ assertEquals(wallet.completeTx(request10, true), Transaction.REFERENCE_DEFAULT_MIN_TX_FEE);
+ Transaction spend10 = request10.tx;
+ // ...but if we get back any more than that, we should get a refund (but still pay fee)
+ assertTrue(spend10.getOutputs().size() == 2);
+ // We optimize for priority, so the output selected should be the largest one
+ assertTrue(spend10.getOutput(0).getValue().add(spend10.getOutput(1).getValue()).equals(
+ Utils.COIN.subtract(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE)));
+
+ SendRequest request11 = SendRequest.to(notMyAddr, Utils.COIN.subtract(
+ Transaction.REFERENCE_DEFAULT_MIN_TX_FEE.add(Transaction.MIN_NONDUST_OUTPUT).add(BigInteger.valueOf(2))));
+ request11.fee = Transaction.REFERENCE_DEFAULT_MIN_TX_FEE.add(BigInteger.ONE);
+ assertEquals(wallet.completeTx(request11, true), request11.fee);
+ Transaction spend11 = request11.tx;
+ // ...of course fee should be min(request.fee, MIN_TX_FEE) so we should get MIN_TX_FEE.add(ONE) here
+ assertTrue(spend11.getOutputs().size() == 2);
+ // We optimize for priority, so the output selected should be the largest one
+ assertTrue(spend11.getOutput(0).getValue().add(spend11.getOutput(1).getValue()).equals(
+ Utils.COIN.subtract(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE.add(BigInteger.ONE))));
+
+ // Remove the coin from our wallet
+ wallet.commitTx(spend11);
+ Transaction tx5 = createFakeTx(params, Utils.CENT, myAddress);
+ wallet.receiveFromBlock(tx5, block, AbstractBlockChain.NewBlockType.BEST_CHAIN);
+ assertTrue(wallet.getBalance().equals(Utils.CENT));
+
+ // Now test coin selection properly selects coin*depth
+ for (int i = 0; i < 100; i++) {
+ block = new StoredBlock(makeSolvedTestBlock(blockStore, notMyAddr), BigInteger.ONE, 1);
+ wallet.notifyNewBestBlock(block);
+ }
+
+ block = new StoredBlock(makeSolvedTestBlock(blockStore, notMyAddr), BigInteger.ONE, 1);
+ Transaction tx6 = createFakeTx(params, Utils.COIN, myAddress);
+ wallet.receiveFromBlock(tx6, block, AbstractBlockChain.NewBlockType.BEST_CHAIN);
+ assertTrue(tx5.getOutput(0).isMine(wallet) && tx5.getOutput(0).isAvailableForSpending() && tx5.getConfidence().getDepthInBlocks() == 100);
+ assertTrue(tx6.getOutput(0).isMine(wallet) && tx6.getOutput(0).isAvailableForSpending() && tx6.getConfidence().getDepthInBlocks() == 1);
+
+ // tx5 and tx6 have exactly the same coin*depth, so the larger should be selected...
+ Transaction spend12 = wallet.createSend(notMyAddr, Utils.CENT);
+ assertTrue(spend12.getOutputs().size() == 2 && spend12.getOutput(0).getValue().add(spend12.getOutput(1).getValue()).equals(Utils.COIN));
+
+ wallet.notifyNewBestBlock(block);
+ assertTrue(tx5.getOutput(0).isMine(wallet) && tx5.getOutput(0).isAvailableForSpending() && tx5.getConfidence().getDepthInBlocks() == 101);
+ assertTrue(tx6.getOutput(0).isMine(wallet) && tx6.getOutput(0).isAvailableForSpending() && tx6.getConfidence().getDepthInBlocks() == 1);
+ // Now tx5 has slightly higher coin*depth than tx6...
+ Transaction spend13 = wallet.createSend(notMyAddr, Utils.CENT);
+ assertTrue(spend13.getOutputs().size() == 1 && spend13.getOutput(0).getValue().equals(Utils.CENT));
+
+ block = new StoredBlock(makeSolvedTestBlock(blockStore, notMyAddr), BigInteger.ONE, 1);
+ wallet.notifyNewBestBlock(block);
+ assertTrue(tx5.getOutput(0).isMine(wallet) && tx5.getOutput(0).isAvailableForSpending() && tx5.getConfidence().getDepthInBlocks() == 102);
+ assertTrue(tx6.getOutput(0).isMine(wallet) && tx6.getOutput(0).isAvailableForSpending() && tx6.getConfidence().getDepthInBlocks() == 2);
+ // Now tx6 has higher coin*depth than tx5...
+ Transaction spend14 = wallet.createSend(notMyAddr, Utils.CENT);
+ assertTrue(spend14.getOutputs().size() == 2 && spend14.getOutput(0).getValue().add(spend14.getOutput(1).getValue()).equals(Utils.COIN));
+
+ // Now test feePerKb
+ SendRequest request15 = SendRequest.to(notMyAddr, Utils.CENT);
+ for (int i = 0; i < 29; i++)
+ request15.tx.addOutput(Utils.CENT, notMyAddr);
+ assertTrue(request15.tx.bitcoinSerialize().length > 1000);
+ request15.feePerKb = BigInteger.ONE;
+ assertEquals(wallet.completeTx(request15, true), BigInteger.ONE);
+ Transaction spend15 = request15.tx;
+ // If a transaction is over 1kb, the set fee should be added
+ assertTrue(spend15.getOutputs().size() == 31);
+ // We optimize for priority, so the output selected should be the largest one
+ BigInteger outValue15 = BigInteger.ZERO;
+ for (TransactionOutput out : spend15.getOutputs())
+ outValue15 = outValue15.add(out.getValue());
+ assertTrue(outValue15.equals(Utils.COIN.subtract(BigInteger.ONE)));
+
+ SendRequest request16 = SendRequest.to(notMyAddr, Utils.CENT);
+ for (int i = 0; i < 29; i++)
+ request16.tx.addOutput(Utils.CENT, notMyAddr);
+ assertTrue(request16.tx.bitcoinSerialize().length > 1000);
+ assertEquals(wallet.completeTx(request16, true), BigInteger.ZERO);
+ Transaction spend16 = request16.tx;
+ // Of course the fee shouldn't be added if feePerKb == 0
+ assertTrue(spend16.getOutputs().size() == 31);
+ // We optimize for priority, so the output selected should be the largest one
+ BigInteger outValue16 = BigInteger.ZERO;
+ for (TransactionOutput out : spend16.getOutputs())
+ outValue16 = outValue16.add(out.getValue());
+ assertTrue(outValue16.equals(Utils.COIN));
+
+ // Create a transaction who's max size could be up to 999 (if signatures were maximum size)
+ SendRequest request17 = SendRequest.to(notMyAddr, Utils.CENT);
+ for (int i = 0; i < 22; i++)
+ request17.tx.addOutput(Utils.CENT, notMyAddr);
+ request17.tx.addOutput(new TransactionOutput(params, request17.tx, Utils.CENT, new byte[15]));
+ request17.feePerKb = BigInteger.ONE;
+ assertTrue(wallet.completeTx(request17, true).equals(BigInteger.ZERO) && request17.tx.getInputs().size() == 1);
+ // Calculate its max length to make sure it is indeed 999
+ int theoreticalMaxLength17 = request17.tx.bitcoinSerialize().length + myKey.getPubKey().length + 75;
+ for (TransactionInput in : request17.tx.getInputs())
+ theoreticalMaxLength17 -= in.getScriptBytes().length;
+ assertTrue(theoreticalMaxLength17 == 999);
+ Transaction spend17 = request17.tx;
+ // Its actual size must be between 997 and 999 (inclusive) as signatures have a 3-byte size range (almost always)
+ assertTrue(spend17.bitcoinSerialize().length >= 997 && spend17.bitcoinSerialize().length <= 999);
+ // Now check that it didn't get a fee since its max size is 999
+ assertTrue(spend17.getOutputs().size() == 25);
+ // We optimize for priority, so the output selected should be the largest one
+ BigInteger outValue17 = BigInteger.ZERO;
+ for (TransactionOutput out : spend17.getOutputs())
+ outValue17 = outValue17.add(out.getValue());
+ assertTrue(outValue17.equals(Utils.COIN));
+
+ // Create a transaction who's max size could be up to 1000 (if signatures were maximum size)
+ SendRequest request18 = SendRequest.to(notMyAddr, Utils.CENT);
+ for (int i = 0; i < 22; i++)
+ request18.tx.addOutput(Utils.CENT, notMyAddr);
+ request18.tx.addOutput(new TransactionOutput(params, request18.tx, Utils.CENT, new byte[16]));
+ request18.feePerKb = BigInteger.ONE;
+ assertTrue(wallet.completeTx(request18, true).equals(BigInteger.ONE) && request18.tx.getInputs().size() == 1);
+ // Calculate its max length to make sure it is indeed 1000
+ Transaction spend18 = request18.tx;
+ int theoreticalMaxLength18 = spend18.bitcoinSerialize().length + myKey.getPubKey().length + 75;
+ for (TransactionInput in : spend18.getInputs())
+ theoreticalMaxLength18 -= in.getScriptBytes().length;
+ assertTrue(theoreticalMaxLength18 == 1000);
+ // Its actual size must be between 998 and 1000 (inclusive) as signatures have a 3-byte size range (almost always)
+ assertTrue(spend18.bitcoinSerialize().length >= 998 && spend18.bitcoinSerialize().length <= 1000);
+ // Now check that it did get a fee since its max size is 1000
+ assertTrue(spend18.getOutputs().size() == 25);
+ // We optimize for priority, so the output selected should be the largest one
+ BigInteger outValue18 = BigInteger.ZERO;
+ for (TransactionOutput out : spend18.getOutputs())
+ outValue18 = outValue18.add(out.getValue());
+ assertTrue(outValue18.equals(Utils.COIN.subtract(BigInteger.ONE)));
+
+ // Now create a transaction that will spend COIN + fee, which makes it require both inputs
+ assertTrue(wallet.getBalance().equals(Utils.CENT.add(Utils.COIN)));
+ SendRequest request19 = SendRequest.to(notMyAddr, Utils.CENT);
+ for (int i = 0; i < 99; i++)
+ request19.tx.addOutput(Utils.CENT, notMyAddr);
+ // If we send now, we shouldnt need a fee and should only have to spend our COIN
+ assertTrue(wallet.completeTx(request19, true).equals(BigInteger.ZERO)
+ && request19.tx.getInputs().size() == 1 && request19.tx.getOutputs().size() == 100);
+ // Now reset request19 and give it a fee per kb
+ request19.completed = false; request19.tx.clearInputs();
+ request19.feePerKb = BigInteger.ONE;
+ assertTrue(wallet.completeTx(request19, true).equals(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE) && request19.tx.getInputs().size() == 2);
+ BigInteger outValue19 = BigInteger.ZERO;
+ for (TransactionOutput out : request19.tx.getOutputs())
+ outValue19 = outValue19.add(out.getValue());
+ // But now our change output is CENT-minfee, so we have to pay min fee
+ // Change this assert when we eventually randomize output order
+ assertTrue(request19.tx.getOutput(request19.tx.getOutputs().size()-1).getValue().equals(Utils.CENT.subtract(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE)));
+ assertTrue(outValue19.equals(Utils.COIN.add(Utils.CENT).subtract(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE)));
+
+ // Create another transaction that will spend COIN + fee, which makes it require both inputs
+ SendRequest request20 = SendRequest.to(notMyAddr, Utils.CENT);
+ for (int i = 0; i < 99; i++)
+ request20.tx.addOutput(Utils.CENT, notMyAddr);
+ // If we send now, we shouldnt need a fee and should only have to spend our COIN
+ assertTrue(wallet.completeTx(request20, true).equals(BigInteger.ZERO)
+ && request20.tx.getInputs().size() == 1 && request20.tx.getOutputs().size() == 100);
+ // Now reset request19 and give it a fee per kb
+ request20.completed = false; request20.tx.clearInputs();
+ request20.feePerKb = Transaction.REFERENCE_DEFAULT_MIN_TX_FEE;
+ assertTrue(wallet.completeTx(request20, true).equals(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE.multiply(BigInteger.valueOf(3)))
+ && request20.tx.getInputs().size() == 2);
+ BigInteger outValue20 = BigInteger.ZERO;
+ for (TransactionOutput out : request20.tx.getOutputs())
+ outValue20 = outValue20.add(out.getValue());
+ // This time the fee we wanted to pay was more, so that should be what we paid
+ assertTrue(outValue20.equals(Utils.COIN.add(Utils.CENT).subtract(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE.multiply(BigInteger.valueOf(3)))));
+
+ // Same as request 19, but make the change 0 (so it doesnt force fee) and make us require min fee as a result of an output < CENT
+ SendRequest request21 = SendRequest.to(notMyAddr, Utils.CENT);
+ for (int i = 0; i < 99; i++)
+ request21.tx.addOutput(Utils.CENT, notMyAddr);
+ request21.tx.addOutput(Utils.CENT.subtract(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE), notMyAddr);
+ // If we send without a feePerKb, we should still require REFERENCE_DEFAULT_MIN_TX_FEE because we have an output < 0.01
+ assertTrue(wallet.completeTx(request21, true).equals(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE) && request21.tx.getInputs().size() == 2);
+ BigInteger outValue21 = BigInteger.ZERO;
+ for (TransactionOutput out : request21.tx.getOutputs())
+ outValue21 = outValue21.add(out.getValue());
+ assertTrue(outValue21.equals(Utils.COIN.add(Utils.CENT).subtract(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE)));
+
+ // Now create an identical request22 and give it a fee per kb slightly less than what we will have to pay
+ SendRequest request22 = SendRequest.to(notMyAddr, Utils.CENT);
+ for (int i = 0; i < 99; i++)
+ request22.tx.addOutput(Utils.CENT, notMyAddr);
+ request22.tx.addOutput(Utils.CENT.subtract(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE), notMyAddr);
+ assertTrue(!Transaction.REFERENCE_DEFAULT_MIN_TX_FEE.mod(BigInteger.valueOf(3)).equals(BigInteger.ZERO)); // This test won't work if REFERENCE_DEFAULT_MIN_TX_FEE is divisiable by 3
+ request22.feePerKb = Transaction.REFERENCE_DEFAULT_MIN_TX_FEE.divide(BigInteger.valueOf(3));
+ // Now check that we get the same exact transaction back
+ assertTrue(wallet.completeTx(request22, true).equals(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE) && request22.tx.getInputs().size() == 2);
+ BigInteger outValue22 = BigInteger.ZERO;
+ for (TransactionOutput out : request22.tx.getOutputs())
+ outValue22 = outValue22.add(out.getValue());
+ assertTrue(outValue22.equals(Utils.COIN.add(Utils.CENT).subtract(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE)));
+
+ // Now create an identical request23 and give it a fee equal to what we will have to pay anyway
+ SendRequest request23 = SendRequest.to(notMyAddr, Utils.CENT);
+ for (int i = 0; i < 99; i++)
+ request23.tx.addOutput(Utils.CENT, notMyAddr);
+ request23.tx.addOutput(Utils.CENT.subtract(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE), notMyAddr);
+ request23.feePerKb = Transaction.REFERENCE_DEFAULT_MIN_TX_FEE.divide(BigInteger.valueOf(3));
+ request23.fee = Transaction.REFERENCE_DEFAULT_MIN_TX_FEE.mod(BigInteger.valueOf(3));
+ // Now check that we get the same exact transaction back
+ assertTrue(wallet.completeTx(request23, true).equals(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE) && request23.tx.getInputs().size() == 2);
+ BigInteger outValue23 = BigInteger.ZERO;
+ for (TransactionOutput out : request23.tx.getOutputs())
+ outValue23 = outValue23.add(out.getValue());
+ assertTrue(outValue23.equals(Utils.COIN.add(Utils.CENT).subtract(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE)));
+
+ // Now create an identical request24 and add one nanocoin of fee, putting us over our wallet balance
+ SendRequest request24 = SendRequest.to(notMyAddr, Utils.CENT);
+ for (int i = 0; i < 99; i++)
+ request24.tx.addOutput(Utils.CENT, notMyAddr);
+ request24.tx.addOutput(Utils.CENT.subtract(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE), notMyAddr);
+ request24.feePerKb = request23.feePerKb;
+ request24.fee = request23.fee.add(BigInteger.ONE);
+ // Now check that we dont complete
+ assertNull(wallet.completeTx(request24, true));
+
+ // Test feePerKb when we aren't using enforceDefaultReferenceClientFeeRelayRules
+ // Same as request 19
+ SendRequest request25 = SendRequest.to(notMyAddr, Utils.CENT);
+ for (int i = 0; i < 99; i++)
+ request25.tx.addOutput(Utils.CENT, notMyAddr);
+ // If we send now, we shouldnt need a fee and should only have to spend our COIN
+ assertTrue(wallet.completeTx(request25, true).equals(BigInteger.ZERO) && request25.tx.getInputs().size() == 1 && request25.tx.getOutputs().size() == 100);
+ // Now reset request19 and give it a fee per kb
+ request25.completed = false; request25.tx.clearInputs();
+ request25.feePerKb = Utils.CENT.divide(BigInteger.valueOf(3));
+ assertTrue(wallet.completeTx(request25, false).equals(Utils.CENT.subtract(BigInteger.ONE)) && request25.tx.getInputs().size() == 2);
+ BigInteger outValue25 = BigInteger.ZERO;
+ for (TransactionOutput out : request25.tx.getOutputs())
+ outValue25 = outValue25.add(out.getValue());
+ // Our change output should be one nanocoin
+ // Change this assert when we eventually randomize output order
+ assertTrue(request25.tx.getOutput(request25.tx.getOutputs().size()-1).getValue().equals(BigInteger.ONE));
+ // and our fee should be CENT-1 nanocoin
+ assertTrue(outValue25.equals(Utils.COIN.add(BigInteger.ONE)));
+
+ //Spend our CENT output
+ Transaction spendTx5 = new Transaction(params);
+ spendTx5.addOutput(Utils.CENT, notMyAddr);
+ spendTx5.addInput(tx5.getOutput(0));
+ spendTx5.signInputs(SigHash.ALL, wallet);
+ wallet.receiveFromBlock(spendTx5, block, AbstractBlockChain.NewBlockType.BEST_CHAIN);
+ assertEquals(wallet.getBalance(), Utils.COIN);
+
+ // Ensure change is discarded if it results in a fee larger than the chain (same as 8 and 9 but with feePerKb)
+ SendRequest request26 = SendRequest.to(notMyAddr, Utils.CENT);
+ for (int i = 0; i < 98; i++)
+ request26.tx.addOutput(Utils.CENT, notMyAddr);
+ request26.tx.addOutput(Utils.CENT.subtract(
+ Transaction.REFERENCE_DEFAULT_MIN_TX_FEE.add(Transaction.MIN_NONDUST_OUTPUT)), notMyAddr);
+ assertTrue(request26.tx.bitcoinSerialize().length > 1000);
+ request26.feePerKb = BigInteger.ONE;
+ assertEquals(wallet.completeTx(request26, true), Transaction.REFERENCE_DEFAULT_MIN_TX_FEE.add(Transaction.MIN_NONDUST_OUTPUT));
+ Transaction spend26 = request26.tx;
+ // If a transaction is over 1kb, the set fee should be added
+ assertTrue(spend26.getOutputs().size() == 100);
+ // We optimize for priority, so the output selected should be the largest one
+ BigInteger outValue26 = BigInteger.ZERO;
+ for (TransactionOutput out : spend26.getOutputs())
+ outValue26 = outValue26.add(out.getValue());
+ assertTrue(outValue26.equals(Utils.COIN.subtract(
+ Transaction.REFERENCE_DEFAULT_MIN_TX_FEE.add(Transaction.MIN_NONDUST_OUTPUT))));
}
// There is a test for spending a coinbase transaction as it matures in BlockChainTest#coinbaseTransactionAvailability
diff --git a/tools/src/main/java/com/google/bitcoin/tools/WalletTool.java b/tools/src/main/java/com/google/bitcoin/tools/WalletTool.java
index ceb92cc6..79d9bb82 100644
--- a/tools/src/main/java/com/google/bitcoin/tools/WalletTool.java
+++ b/tools/src/main/java/com/google/bitcoin/tools/WalletTool.java
@@ -437,7 +437,7 @@ public class WalletTool {
}
req.aesKey = wallet.getKeyCrypter().deriveKey(password);
}
- if (!wallet.completeTx(req)) {
+ if (wallet.completeTx(req, false) == null) {
System.err.println("Insufficient funds: have " + Utils.bitcoinValueToFriendlyString(wallet.getBalance()));
return;
}