3
0
mirror of https://github.com/Qortal/altcoinj.git synced 2025-02-07 14:54:15 +00:00

Add basic fee solver to Wallet.completeTx and extensive testing.

This also adds support for SendRequests which have some predefined
inputs (already signed), which will be tested in a later commit.
This commit is contained in:
Matt Corallo 2013-05-21 17:20:34 +02:00 committed by Mike Hearn
parent 86046b7122
commit 375e553bdc
4 changed files with 662 additions and 50 deletions

View File

@ -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
* <p>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.</p>
*
* <p>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.</p>
*
* <p>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).</p>
*/
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
* <p>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.</p>
*
* <p>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.</p>
*
* <p>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.</p>
*/
public BigInteger fee = BigInteger.ZERO;
/**
* <p>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.</p>
*
* <p>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.</p>
*
* <p>You might also consider using a {@link SendRequest#fee} to set the fee added for the first kb of size.</p>
*/
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.</p>
*
* <p>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.</p>
* 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.</p>
*
* <p>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 {
/**
* <p>Sends coins to the given address, via the given {@link PeerGroup}. Change is returned to
* {@link Wallet#getChangeAddress()}. No fee is attached <b>even if one would be required</b>.</p>
* {@link Wallet#getChangeAddress()}. Note that a fee may be automatically added if one may be required for the
* transaction to be confirmed.</p>
*
* <p>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 <b>by you</b> at some other time.
* pending transaction must be broadcast <b>by you</b> 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<TransactionInput> 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<TransactionOutput> 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();
}

View File

@ -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);

View File

@ -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

View File

@ -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;
}