mirror of
https://github.com/Qortal/altcoinj.git
synced 2025-02-07 23:03:04 +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:
parent
86046b7122
commit
375e553bdc
@ -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,29 +1953,197 @@ 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);
|
||||
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(value, candidates);
|
||||
CoinSelection selection = coinSelector.select(valueNeeded, 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)));
|
||||
// TODO: Should throw an exception here.
|
||||
return false;
|
||||
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));
|
||||
}
|
||||
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));
|
||||
// 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 null;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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 {
|
||||
@ -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();
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user