3
0
mirror of https://github.com/Qortal/altcoinj.git synced 2025-02-13 10:45:51 +00:00

Wallet: Calculate fee based on feePerKb on byte precision.

Transaction.DEFAULT_TX_FEE is increased to make up for a drop in average fees because of the higher precision.
This commit is contained in:
Andreas Schildbach 2016-03-11 13:49:29 +01:00
parent b1bc34eb76
commit 9505227616
3 changed files with 50 additions and 152 deletions

View File

@ -101,7 +101,7 @@ public class Transaction extends ChildMessage {
* If using this feePerKb, transactions will get confirmed within the next couple of blocks. * If using this feePerKb, transactions will get confirmed within the next couple of blocks.
* This should be adjusted from time to time. Last adjustment: March 2016. * This should be adjusted from time to time. Last adjustment: March 2016.
*/ */
public static final Coin DEFAULT_TX_FEE = Coin.valueOf(10000); // 0.1 mBTC public static final Coin DEFAULT_TX_FEE = Coin.valueOf(50000); // 0.5 mBTC
/** /**
* Any standard (ie pay-to-address) output smaller than this value (in satoshis) will most likely be rejected by the network. * Any standard (ie pay-to-address) output smaller than this value (in satoshis) will most likely be rejected by the network.

View File

@ -5040,13 +5040,7 @@ public class Wallet extends BaseTaggableObject
while (true) { while (true) {
resetTxInputs(req, originalInputs); resetTxInputs(req, originalInputs);
Coin fees; Coin fees = req.feePerKb.multiply(lastCalculatedSize).divide(1000);
if (lastCalculatedSize > 0) {
// If the size is exactly 1000 bytes then we'll over-pay, but this should be rare.
fees = req.feePerKb.multiply((lastCalculatedSize / 1000) + 1);
} else {
fees = req.feePerKb; // First time around the loop.
}
if (needAtLeastReferenceFee && fees.compareTo(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE) < 0) if (needAtLeastReferenceFee && fees.compareTo(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE) < 0)
fees = Transaction.REFERENCE_DEFAULT_MIN_TX_FEE; fees = Transaction.REFERENCE_DEFAULT_MIN_TX_FEE;
@ -5130,7 +5124,7 @@ public class Wallet extends BaseTaggableObject
// include things we haven't added yet like input signatures/scripts or the change output. // include things we haven't added yet like input signatures/scripts or the change output.
size += req.tx.unsafeBitcoinSerialize().length; size += req.tx.unsafeBitcoinSerialize().length;
size += estimateBytesForSigning(selection); size += estimateBytesForSigning(selection);
if (size/1000 > lastCalculatedSize/1000 && req.feePerKb.signum() > 0) { if (size > lastCalculatedSize && req.feePerKb.signum() > 0) {
lastCalculatedSize = size; lastCalculatedSize = size;
// We need more fees anyway, just try again with the same additional value // We need more fees anyway, just try again with the same additional value
additionalValueForNextCategory = additionalValueSelected; additionalValueForNextCategory = additionalValueSelected;

View File

@ -2267,10 +2267,7 @@ public class WalletTest extends TestWithWallet {
} }
@Test @Test
public void feeSolverAndCoinSelectionTests1() throws Exception { public void basicFeeSolverTests() throws Exception {
// Tests basic fee solving works
// Add some reasonable-sized outputs
sendMoneyToWallet(AbstractBlockChain.NewBlockType.BEST_CHAIN, Coin.COIN); sendMoneyToWallet(AbstractBlockChain.NewBlockType.BEST_CHAIN, Coin.COIN);
// Simple test to make sure if we have an ouput < 0.01 we get a fee // Simple test to make sure if we have an ouput < 0.01 we get a fee
@ -2279,10 +2276,6 @@ public class WalletTest extends TestWithWallet {
wallet.completeTx(request1); wallet.completeTx(request1);
Transaction spend1 = request1.tx; Transaction spend1 = request1.tx;
assertEquals(2, spend1.getOutputs().size()); assertEquals(2, spend1.getOutputs().size());
// We optimize for priority, so the output selected should be the largest one.
// We should have paid the default minfee.
assertEquals(spend1.getOutput(0).getValue().add(spend1.getOutput(1).getValue()),
Coin.COIN.subtract(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE));
// But not at exactly 0.01 // But not at exactly 0.01
SendRequest request2 = SendRequest.to(OTHER_ADDRESS, CENT); SendRequest request2 = SendRequest.to(OTHER_ADDRESS, CENT);
@ -2290,20 +2283,15 @@ public class WalletTest extends TestWithWallet {
wallet.completeTx(request2); wallet.completeTx(request2);
Transaction spend2 = request2.tx; Transaction spend2 = request2.tx;
assertEquals(2, spend2.getOutputs().size()); assertEquals(2, spend2.getOutputs().size());
// We optimize for priority, so the output selected should be the largest one
assertEquals(Coin.COIN, spend2.getOutput(0).getValue().add(spend2.getOutput(1).getValue()));
// ...but not more fee than what we request // ...but not more fee than what we request
SendRequest request3 = SendRequest.to(OTHER_ADDRESS, CENT.subtract(SATOSHI)); SendRequest request3 = SendRequest.to(OTHER_ADDRESS, CENT.subtract(SATOSHI));
request3.feePerKb = Transaction.REFERENCE_DEFAULT_MIN_TX_FEE.add(SATOSHI); request3.feePerKb = Transaction.REFERENCE_DEFAULT_MIN_TX_FEE.add(SATOSHI);
request3.ensureMinRequiredFee = true; request3.ensureMinRequiredFee = true;
wallet.completeTx(request3); wallet.completeTx(request3);
assertEquals(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE.add(SATOSHI), request3.tx.getFee()); assertEquals(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE, request3.tx.getFee());
Transaction spend3 = request3.tx; Transaction spend3 = request3.tx;
assertEquals(2, spend3.getOutputs().size()); assertEquals(2, spend3.getOutputs().size());
// We optimize for priority, so the output selected should be the largest one.
assertEquals(spend3.getOutput(0).getValue().add(spend3.getOutput(1).getValue()),
Coin.COIN.subtract(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE.add(SATOSHI)));
// ...unless we need it // ...unless we need it
SendRequest request4 = SendRequest.to(OTHER_ADDRESS, CENT.subtract(SATOSHI)); SendRequest request4 = SendRequest.to(OTHER_ADDRESS, CENT.subtract(SATOSHI));
@ -2313,9 +2301,6 @@ public class WalletTest extends TestWithWallet {
assertEquals(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE, request4.tx.getFee()); assertEquals(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE, request4.tx.getFee());
Transaction spend4 = request4.tx; Transaction spend4 = request4.tx;
assertEquals(2, spend4.getOutputs().size()); assertEquals(2, spend4.getOutputs().size());
// We optimize for priority, so the output selected should be the largest one.
assertEquals(spend4.getOutput(0).getValue().add(spend4.getOutput(1).getValue()),
Coin.COIN.subtract(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE));
SendRequest request5 = SendRequest.to(OTHER_ADDRESS, Coin.COIN.subtract(CENT.subtract(SATOSHI))); SendRequest request5 = SendRequest.to(OTHER_ADDRESS, Coin.COIN.subtract(CENT.subtract(SATOSHI)));
request5.ensureMinRequiredFee = true; request5.ensureMinRequiredFee = true;
@ -2324,9 +2309,6 @@ public class WalletTest extends TestWithWallet {
Transaction spend5 = request5.tx; Transaction spend5 = request5.tx;
// If we would have a change output < 0.01, it should add the fee // If we would have a change output < 0.01, it should add the fee
assertEquals(2, spend5.getOutputs().size()); assertEquals(2, spend5.getOutputs().size());
// We optimize for priority, so the output selected should be the largest one.
assertEquals(spend5.getOutput(0).getValue().add(spend5.getOutput(1).getValue()),
Coin.COIN.subtract(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE));
SendRequest request6 = SendRequest.to(OTHER_ADDRESS, Coin.COIN.subtract(CENT)); SendRequest request6 = SendRequest.to(OTHER_ADDRESS, Coin.COIN.subtract(CENT));
request6.ensureMinRequiredFee = true; request6.ensureMinRequiredFee = true;
@ -2335,8 +2317,6 @@ public class WalletTest extends TestWithWallet {
Transaction spend6 = request6.tx; Transaction spend6 = request6.tx;
// ...but not if change output == 0.01 // ...but not if change output == 0.01
assertEquals(2, spend6.getOutputs().size()); assertEquals(2, spend6.getOutputs().size());
// We optimize for priority, so the output selected should be the largest one
assertEquals(Coin.COIN, spend6.getOutput(0).getValue().add(spend6.getOutput(1).getValue()));
SendRequest request7 = SendRequest.to(OTHER_ADDRESS, Coin.COIN.subtract(CENT.subtract(SATOSHI.multiply(2)).multiply(2))); SendRequest request7 = SendRequest.to(OTHER_ADDRESS, Coin.COIN.subtract(CENT.subtract(SATOSHI.multiply(2)).multiply(2)));
request7.ensureMinRequiredFee = true; request7.ensureMinRequiredFee = true;
@ -2346,9 +2326,6 @@ public class WalletTest extends TestWithWallet {
Transaction spend7 = request7.tx; Transaction spend7 = request7.tx;
// If change is 0.1-satoshi and we already have a 0.1-satoshi output, fee should be reference fee // If change is 0.1-satoshi and we already have a 0.1-satoshi output, fee should be reference fee
assertEquals(3, spend7.getOutputs().size()); assertEquals(3, spend7.getOutputs().size());
// We optimize for priority, so the output selected should be the largest one.
assertEquals(spend7.getOutput(0).getValue().add(spend7.getOutput(1).getValue()).add(spend7.getOutput(2).getValue()),
Coin.COIN.subtract(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE));
SendRequest request8 = SendRequest.to(OTHER_ADDRESS, COIN.subtract(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE)); SendRequest request8 = SendRequest.to(OTHER_ADDRESS, COIN.subtract(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE));
request8.ensureMinRequiredFee = true; request8.ensureMinRequiredFee = true;
@ -2357,8 +2334,6 @@ public class WalletTest extends TestWithWallet {
Transaction spend8 = request8.tx; 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 // If we would have a change output == REFERENCE_DEFAULT_MIN_TX_FEE that would cause a fee, throw it away and make it fee
assertEquals(1, spend8.getOutputs().size()); assertEquals(1, spend8.getOutputs().size());
// We optimize for priority, so the output selected should be the largest one
assertEquals(spend8.getOutput(0).getValue(), COIN.subtract(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE));
SendRequest request9 = SendRequest.to(OTHER_ADDRESS, COIN.subtract( SendRequest request9 = SendRequest.to(OTHER_ADDRESS, COIN.subtract(
Transaction.REFERENCE_DEFAULT_MIN_TX_FEE.add(Transaction.MIN_NONDUST_OUTPUT).subtract(SATOSHI))); Transaction.REFERENCE_DEFAULT_MIN_TX_FEE.add(Transaction.MIN_NONDUST_OUTPUT).subtract(SATOSHI)));
@ -2368,9 +2343,6 @@ public class WalletTest extends TestWithWallet {
Transaction spend9 = request9.tx; Transaction spend9 = request9.tx;
// ...in fact, also add fee if we would get back less than MIN_NONDUST_OUTPUT // ...in fact, also add fee if we would get back less than MIN_NONDUST_OUTPUT
assertEquals(1, spend9.getOutputs().size()); assertEquals(1, spend9.getOutputs().size());
// We optimize for priority, so the output selected should be the largest one.
assertEquals(spend9.getOutput(0).getValue(),
COIN.subtract(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE.add(Transaction.MIN_NONDUST_OUTPUT).subtract(SATOSHI)));
SendRequest request10 = SendRequest.to(OTHER_ADDRESS, COIN.subtract( SendRequest request10 = SendRequest.to(OTHER_ADDRESS, COIN.subtract(
Transaction.REFERENCE_DEFAULT_MIN_TX_FEE.add(Transaction.MIN_NONDUST_OUTPUT))); Transaction.REFERENCE_DEFAULT_MIN_TX_FEE.add(Transaction.MIN_NONDUST_OUTPUT)));
@ -2380,22 +2352,16 @@ public class WalletTest extends TestWithWallet {
Transaction spend10 = request10.tx; Transaction spend10 = request10.tx;
// ...but if we get back any more than that, we should get a refund (but still pay fee) // ...but if we get back any more than that, we should get a refund (but still pay fee)
assertEquals(2, spend10.getOutputs().size()); assertEquals(2, spend10.getOutputs().size());
// We optimize for priority, so the output selected should be the largest one
assertEquals(spend10.getOutput(0).getValue().add(spend10.getOutput(1).getValue()),
COIN.subtract(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE));
SendRequest request11 = SendRequest.to(OTHER_ADDRESS, COIN.subtract( SendRequest request11 = SendRequest.to(OTHER_ADDRESS, COIN.subtract(
Transaction.REFERENCE_DEFAULT_MIN_TX_FEE.add(Transaction.MIN_NONDUST_OUTPUT).add(SATOSHI.multiply(2)))); Transaction.REFERENCE_DEFAULT_MIN_TX_FEE.add(Transaction.MIN_NONDUST_OUTPUT).add(SATOSHI.multiply(2))));
request11.feePerKb = Transaction.REFERENCE_DEFAULT_MIN_TX_FEE.add(SATOSHI); request11.feePerKb = Transaction.REFERENCE_DEFAULT_MIN_TX_FEE.add(SATOSHI);
request11.ensureMinRequiredFee = true; request11.ensureMinRequiredFee = true;
wallet.completeTx(request11); wallet.completeTx(request11);
assertEquals(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE.add(SATOSHI), request11.tx.getFee()); assertEquals(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE, request11.tx.getFee());
Transaction spend11 = request11.tx; Transaction spend11 = request11.tx;
// ...of course fee should be min(request.fee, MIN_TX_FEE) so we should get MIN_TX_FEE.add(SATOSHI) here // ...of course fee should be min(request.fee, MIN_TX_FEE) so we should get MIN_TX_FEE.add(SATOSHI) here
assertEquals(2, spend11.getOutputs().size()); assertEquals(2, spend11.getOutputs().size());
// We optimize for priority, so the output selected should be the largest one.
assertEquals(spend11.getOutput(0).getValue().add(spend11.getOutput(1).getValue()),
COIN.subtract(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE.add(SATOSHI)));
// Remove the coin from our wallet // Remove the coin from our wallet
wallet.commitTx(spend11); wallet.commitTx(spend11);
@ -2464,18 +2430,16 @@ public class WalletTest extends TestWithWallet {
for (int i = 0; i < 29; i++) for (int i = 0; i < 29; i++)
request15.tx.addOutput(CENT, OTHER_ADDRESS); request15.tx.addOutput(CENT, OTHER_ADDRESS);
assertTrue(request15.tx.unsafeBitcoinSerialize().length > 1000); assertTrue(request15.tx.unsafeBitcoinSerialize().length > 1000);
request15.feePerKb = SATOSHI; request15.feePerKb = Transaction.DEFAULT_TX_FEE;
request15.ensureMinRequiredFee = true; request15.ensureMinRequiredFee = true;
wallet.completeTx(request15); wallet.completeTx(request15);
assertEquals(SATOSHI.multiply(2), request15.tx.getFee()); assertEquals(Coin.valueOf(60650), request15.tx.getFee());
Transaction spend15 = request15.tx; Transaction spend15 = request15.tx;
// If a transaction is over 1kb, 2 satoshis should be added. // If a transaction is over 1kb, 2 satoshis should be added.
assertEquals(31, spend15.getOutputs().size()); assertEquals(31, spend15.getOutputs().size());
// We optimize for priority, so the output selected should be the largest one // We optimize for priority, so the output selected should be the largest one
Coin outValue15 = ZERO; assertEquals(1, spend15.getInputs().size());
for (TransactionOutput out : spend15.getOutputs()) assertEquals(COIN, spend15.getInput(0).getValue());
outValue15 = outValue15.add(out.getValue());
assertEquals(COIN.subtract(SATOSHI.multiply(2)), outValue15);
SendRequest request16 = SendRequest.to(OTHER_ADDRESS, CENT); SendRequest request16 = SendRequest.to(OTHER_ADDRESS, CENT);
request16.feePerKb = ZERO; request16.feePerKb = ZERO;
@ -2489,20 +2453,18 @@ public class WalletTest extends TestWithWallet {
Transaction spend16 = request16.tx; Transaction spend16 = request16.tx;
assertEquals(31, spend16.getOutputs().size()); assertEquals(31, spend16.getOutputs().size());
// We optimize for priority, so the output selected should be the largest one // We optimize for priority, so the output selected should be the largest one
Coin outValue16 = ZERO; assertEquals(1, spend16.getInputs().size());
for (TransactionOutput out : spend16.getOutputs()) assertEquals(COIN, spend16.getInput(0).getValue());
outValue16 = outValue16.add(out.getValue());
assertEquals(COIN, outValue16);
// Create a transaction whose max size could be up to 999 (if signatures were maximum size) // Create a transaction whose max size could be up to 999 (if signatures were maximum size)
SendRequest request17 = SendRequest.to(OTHER_ADDRESS, CENT); SendRequest request17 = SendRequest.to(OTHER_ADDRESS, CENT);
for (int i = 0; i < 22; i++) for (int i = 0; i < 22; i++)
request17.tx.addOutput(CENT, OTHER_ADDRESS); request17.tx.addOutput(CENT, OTHER_ADDRESS);
request17.tx.addOutput(new TransactionOutput(PARAMS, request17.tx, CENT, new byte[15])); request17.tx.addOutput(new TransactionOutput(PARAMS, request17.tx, CENT, new byte[15]));
request17.feePerKb = SATOSHI; request17.feePerKb = Transaction.DEFAULT_TX_FEE;
request17.ensureMinRequiredFee = true; request17.ensureMinRequiredFee = true;
wallet.completeTx(request17); wallet.completeTx(request17);
assertEquals(SATOSHI, request17.tx.getFee()); assertEquals(Coin.valueOf(49950), request17.tx.getFee());
assertEquals(1, request17.tx.getInputs().size()); assertEquals(1, request17.tx.getInputs().size());
// Calculate its max length to make sure it is indeed 999 // Calculate its max length to make sure it is indeed 999
int theoreticalMaxLength17 = request17.tx.unsafeBitcoinSerialize().length + myKey.getPubKey().length + 75; int theoreticalMaxLength17 = request17.tx.unsafeBitcoinSerialize().length + myKey.getPubKey().length + 75;
@ -2518,20 +2480,18 @@ public class WalletTest extends TestWithWallet {
// Now check that it got a fee of 1 since its max size is 999 (1kb). // Now check that it got a fee of 1 since its max size is 999 (1kb).
assertEquals(25, spend17.getOutputs().size()); assertEquals(25, spend17.getOutputs().size());
// We optimize for priority, so the output selected should be the largest one // We optimize for priority, so the output selected should be the largest one
Coin outValue17 = ZERO; assertEquals(1, spend17.getInputs().size());
for (TransactionOutput out : spend17.getOutputs()) assertEquals(COIN, spend17.getInput(0).getValue());
outValue17 = outValue17.add(out.getValue());
assertEquals(COIN.subtract(SATOSHI), outValue17);
// Create a transaction who's max size could be up to 1001 (if signatures were maximum size) // Create a transaction who's max size could be up to 1001 (if signatures were maximum size)
SendRequest request18 = SendRequest.to(OTHER_ADDRESS, CENT); SendRequest request18 = SendRequest.to(OTHER_ADDRESS, CENT);
for (int i = 0; i < 22; i++) for (int i = 0; i < 22; i++)
request18.tx.addOutput(CENT, OTHER_ADDRESS); request18.tx.addOutput(CENT, OTHER_ADDRESS);
request18.tx.addOutput(new TransactionOutput(PARAMS, request18.tx, CENT, new byte[17])); request18.tx.addOutput(new TransactionOutput(PARAMS, request18.tx, CENT, new byte[17]));
request18.feePerKb = SATOSHI; request18.feePerKb = Transaction.DEFAULT_TX_FEE;
request18.ensureMinRequiredFee = true; request18.ensureMinRequiredFee = true;
wallet.completeTx(request18); wallet.completeTx(request18);
assertEquals(SATOSHI.multiply(2), request18.tx.getFee()); assertEquals(Coin.valueOf(50050), request18.tx.getFee());
assertEquals(1, request18.tx.getInputs().size()); assertEquals(1, request18.tx.getInputs().size());
// Calculate its max length to make sure it is indeed 1001 // Calculate its max length to make sure it is indeed 1001
Transaction spend18 = request18.tx; Transaction spend18 = request18.tx;
@ -2545,10 +2505,8 @@ public class WalletTest extends TestWithWallet {
// Now check that it did get a fee since its max size is 1000 // Now check that it did get a fee since its max size is 1000
assertEquals(25, spend18.getOutputs().size()); assertEquals(25, spend18.getOutputs().size());
// We optimize for priority, so the output selected should be the largest one // We optimize for priority, so the output selected should be the largest one
Coin outValue18 = ZERO; assertEquals(1, spend18.getInputs().size());
for (TransactionOutput out : spend18.getOutputs()) assertEquals(COIN, spend18.getInput(0).getValue());
outValue18 = outValue18.add(out.getValue());
assertEquals(outValue18, COIN.subtract(SATOSHI.multiply(2)));
// Now create a transaction that will spend COIN + fee, which makes it require both inputs // Now create a transaction that will spend COIN + fee, which makes it require both inputs
assertEquals(wallet.getBalance(), CENT.add(COIN)); assertEquals(wallet.getBalance(), CENT.add(COIN));
@ -2566,17 +2524,13 @@ public class WalletTest extends TestWithWallet {
request19.tx.clearInputs(); request19.tx.clearInputs();
request19 = SendRequest.forTx(request19.tx); request19 = SendRequest.forTx(request19.tx);
request19.ensureMinRequiredFee = true; request19.ensureMinRequiredFee = true;
request19.feePerKb = SATOSHI; request19.feePerKb = Transaction.DEFAULT_TX_FEE;
request19.shuffleOutputs = false; request19.shuffleOutputs = false;
wallet.completeTx(request19); wallet.completeTx(request19);
assertEquals(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE, request19.tx.getFee()); assertEquals(Coin.valueOf(187100), request19.tx.getFee());
assertEquals(2, request19.tx.getInputs().size()); assertEquals(2, request19.tx.getInputs().size());
Coin outValue19 = ZERO; assertEquals(COIN, request19.tx.getInput(0).getValue());
for (TransactionOutput out : request19.tx.getOutputs()) assertEquals(CENT, request19.tx.getInput(1).getValue());
outValue19 = outValue19.add(out.getValue());
// But now our change output is CENT-minfee, so we have to pay min fee
assertEquals(request19.tx.getOutput(request19.tx.getOutputs().size() - 1).getValue(), CENT.subtract(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE));
assertEquals(outValue19, COIN.add(CENT).subtract(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE));
// Create another transaction that will spend COIN + fee, which makes it require both inputs // Create another transaction that will spend COIN + fee, which makes it require both inputs
SendRequest request20 = SendRequest.to(OTHER_ADDRESS, CENT); SendRequest request20 = SendRequest.to(OTHER_ADDRESS, CENT);
@ -2592,16 +2546,13 @@ public class WalletTest extends TestWithWallet {
// Now reset request19 and give it a fee per kb // Now reset request19 and give it a fee per kb
request20.tx.clearInputs(); request20.tx.clearInputs();
request20 = SendRequest.forTx(request20.tx); request20 = SendRequest.forTx(request20.tx);
request20.feePerKb = Transaction.REFERENCE_DEFAULT_MIN_TX_FEE; request20.feePerKb = Transaction.DEFAULT_TX_FEE;
wallet.completeTx(request20); wallet.completeTx(request20);
// 4kb tx. // 4kb tx.
assertEquals(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE.multiply(4), request20.tx.getFee()); assertEquals(Coin.valueOf(187100), request20.tx.getFee());
assertEquals(2, request20.tx.getInputs().size()); assertEquals(2, request20.tx.getInputs().size());
Coin outValue20 = ZERO; assertEquals(COIN, request20.tx.getInput(0).getValue());
for (TransactionOutput out : request20.tx.getOutputs()) assertEquals(CENT, request20.tx.getInput(1).getValue());
outValue20 = outValue20.add(out.getValue());
// This time the fee we wanted to pay was more, so that should be what we paid
assertEquals(outValue20, COIN.add(CENT).subtract(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE.multiply(4)));
// Same as request 19, but make the change 0 (so it doesnt force fee) and make us require min fee as a // 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. // result of an output < CENT.
@ -2615,10 +2566,8 @@ public class WalletTest extends TestWithWallet {
wallet.completeTx(request21); wallet.completeTx(request21);
assertEquals(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE, request21.tx.getFee()); assertEquals(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE, request21.tx.getFee());
assertEquals(2, request21.tx.getInputs().size()); assertEquals(2, request21.tx.getInputs().size());
Coin outValue21 = ZERO; assertEquals(COIN, request21.tx.getInput(0).getValue());
for (TransactionOutput out : request21.tx.getOutputs()) assertEquals(CENT, request21.tx.getInput(1).getValue());
outValue21 = outValue21.add(out.getValue());
assertEquals(outValue21, COIN.add(CENT).subtract(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE));
// Test feePerKb when we aren't using ensureMinRequiredFee // Test feePerKb when we aren't using ensureMinRequiredFee
// Same as request 19 // Same as request 19
@ -2632,21 +2581,16 @@ public class WalletTest extends TestWithWallet {
assertEquals(ZERO, request25.tx.getFee()); assertEquals(ZERO, request25.tx.getFee());
assertEquals(1, request25.tx.getInputs().size()); assertEquals(1, request25.tx.getInputs().size());
assertEquals(72, request25.tx.getOutputs().size()); assertEquals(72, request25.tx.getOutputs().size());
// Now reset request19 and give it a fee per kb // Now reset request25 and give it a fee per kb
request25.tx.clearInputs(); request25.tx.clearInputs();
request25 = SendRequest.forTx(request25.tx); request25 = SendRequest.forTx(request25.tx);
request25.feePerKb = CENT.divide(3); request25.feePerKb = Transaction.DEFAULT_TX_FEE;
request25.shuffleOutputs = false; request25.shuffleOutputs = false;
wallet.completeTx(request25); wallet.completeTx(request25);
assertEquals(CENT.subtract(SATOSHI), request25.tx.getFee()); assertEquals(Coin.valueOf(139500), request25.tx.getFee());
assertEquals(2, request25.tx.getInputs().size()); assertEquals(2, request25.tx.getInputs().size());
Coin outValue25 = ZERO; assertEquals(COIN, request25.tx.getInput(0).getValue());
for (TransactionOutput out : request25.tx.getOutputs()) assertEquals(CENT, request25.tx.getInput(1).getValue());
outValue25 = outValue25.add(out.getValue());
// Our change output should be one satoshi
assertEquals(SATOSHI, request25.tx.getOutput(request25.tx.getOutputs().size() - 1).getValue());
// and our fee should be CENT-1 satoshi
assertEquals(outValue25, COIN.add(SATOSHI));
// Spend our CENT output. // Spend our CENT output.
Transaction spendTx5 = new Transaction(PARAMS); Transaction spendTx5 = new Transaction(PARAMS);
@ -2674,11 +2618,8 @@ public class WalletTest extends TestWithWallet {
// If a transaction is over 1kb, the set fee should be added // If a transaction is over 1kb, the set fee should be added
assertEquals(100, spend26.getOutputs().size()); assertEquals(100, spend26.getOutputs().size());
// We optimize for priority, so the output selected should be the largest one // We optimize for priority, so the output selected should be the largest one
Coin outValue26 = ZERO; assertEquals(1, spend26.getInputs().size());
for (TransactionOutput out : spend26.getOutputs()) assertEquals(COIN, spend26.getInput(0).getValue());
outValue26 = outValue26.add(out.getValue());
assertEquals(outValue26, COIN.subtract(
Transaction.REFERENCE_DEFAULT_MIN_TX_FEE.add(Transaction.MIN_NONDUST_OUTPUT).subtract(SATOSHI)));
} }
@Test @Test
@ -2796,17 +2737,18 @@ public class WalletTest extends TestWithWallet {
SendRequest request = SendRequest.to(OTHER_ADDRESS, CENT); SendRequest request = SendRequest.to(OTHER_ADDRESS, CENT);
request.feePerKb = Transaction.DEFAULT_TX_FEE; request.feePerKb = Transaction.DEFAULT_TX_FEE;
wallet.completeTx(request); wallet.completeTx(request);
assertEquals(Transaction.DEFAULT_TX_FEE, request.tx.getFee()); assertEquals(Coin.valueOf(11350), request.tx.getFee());
} }
@Test @Test
public void lowerThanDefaultFee() throws InsufficientMoneyException { public void lowerThanDefaultFee() throws InsufficientMoneyException {
Coin fee = Transaction.DEFAULT_TX_FEE.divide(10); int feeFactor = 10;
Coin fee = Transaction.DEFAULT_TX_FEE.divide(feeFactor);
receiveATransactionAmount(wallet, myAddress, Coin.COIN); receiveATransactionAmount(wallet, myAddress, Coin.COIN);
SendRequest req = SendRequest.to(myAddress, Coin.CENT); SendRequest req = SendRequest.to(myAddress, Coin.CENT);
req.feePerKb = fee; req.feePerKb = fee;
wallet.completeTx(req); wallet.completeTx(req);
assertEquals(fee, req.tx.getFee()); assertEquals(Coin.valueOf(11350).divide(feeFactor), req.tx.getFee());
wallet.commitTx(req.tx); wallet.commitTx(req.tx);
SendRequest emptyReq = SendRequest.emptyWallet(myAddress); SendRequest emptyReq = SendRequest.emptyWallet(myAddress);
emptyReq.feePerKb = fee; emptyReq.feePerKb = fee;
@ -2820,64 +2762,23 @@ public class WalletTest extends TestWithWallet {
@Test @Test
public void higherThanDefaultFee() throws InsufficientMoneyException { public void higherThanDefaultFee() throws InsufficientMoneyException {
Coin fee = Transaction.DEFAULT_TX_FEE.multiply(10); int feeFactor = 10;
Coin fee = Transaction.DEFAULT_TX_FEE.multiply(feeFactor);
receiveATransactionAmount(wallet, myAddress, Coin.COIN); receiveATransactionAmount(wallet, myAddress, Coin.COIN);
SendRequest req = SendRequest.to(myAddress, Coin.CENT); SendRequest req = SendRequest.to(myAddress, Coin.CENT);
req.feePerKb = fee; req.feePerKb = fee;
wallet.completeTx(req); wallet.completeTx(req);
assertEquals(fee, req.tx.getFee()); assertEquals(Coin.valueOf(11350).multiply(feeFactor), req.tx.getFee());
wallet.commitTx(req.tx); wallet.commitTx(req.tx);
SendRequest emptyReq = SendRequest.emptyWallet(myAddress); SendRequest emptyReq = SendRequest.emptyWallet(myAddress);
emptyReq.feePerKb = fee; emptyReq.feePerKb = fee;
emptyReq.emptyWallet = true; emptyReq.emptyWallet = true;
emptyReq.coinSelector = AllowUnconfirmedCoinSelector.get(); emptyReq.coinSelector = AllowUnconfirmedCoinSelector.get();
wallet.completeTx(emptyReq); wallet.completeTx(emptyReq);
assertEquals(Coin.valueOf(34200), emptyReq.tx.getFee()); assertEquals(Coin.valueOf(171000), emptyReq.tx.getFee());
wallet.commitTx(emptyReq.tx); wallet.commitTx(emptyReq.tx);
} }
@Test
public void feePerKbCategoryJumpTest() throws Exception {
// Simple test of boundary condition on fee per kb in category fee solver
// Generate a ton of small outputs
Transaction tx = createFakeTx(PARAMS, COIN, myAddress);
Transaction tx2 = createFakeTx(PARAMS, CENT, myAddress);
Transaction tx3 = createFakeTx(PARAMS, SATOSHI, myAddress);
sendMoneyToWallet(AbstractBlockChain.NewBlockType.BEST_CHAIN, tx, tx2, tx3);
// Create a transaction who's max size could be up to 1000 (if signatures were maximum size)
SendRequest request1 = SendRequest.to(OTHER_ADDRESS, COIN.subtract(CENT.multiply(17)));
for (int i = 0; i < 16; i++)
request1.tx.addOutput(CENT, OTHER_ADDRESS);
request1.tx.addOutput(new TransactionOutput(PARAMS, request1.tx, CENT, new byte[16]));
request1.feePerKb = SATOSHI;
request1.ensureMinRequiredFee = true;
// We get a category 2 using COIN+CENT
// It spends COIN + 1(fee) and because its output is thus < CENT, we have to pay MIN_TX_FEE
// When it tries category 1, its too large and requires COIN + 2 (fee)
// This adds the next input, but still has a < CENT output which means it cant reach category 1
wallet.completeTx(request1);
assertEquals(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE, request1.tx.getFee());
assertEquals(2, request1.tx.getInputs().size());
// We then add one more satoshi output to the wallet
Transaction tx4 = createFakeTx(PARAMS, SATOSHI, myAddress);
sendMoneyToWallet(AbstractBlockChain.NewBlockType.BEST_CHAIN, tx4);
// Create a transaction who's max size could be up to 1000 (if signatures were maximum size)
SendRequest request2 = SendRequest.to(OTHER_ADDRESS, COIN.subtract(CENT.multiply(17)));
for (int i = 0; i < 16; i++)
request2.tx.addOutput(CENT, OTHER_ADDRESS);
request2.tx.addOutput(new TransactionOutput(PARAMS, request2.tx, CENT, new byte[16]));
request2.feePerKb = SATOSHI;
request2.ensureMinRequiredFee = true;
// The process is the same as above, but now we can complete category 1 with one more input, and pay a fee of 2
wallet.completeTx(request2);
assertEquals(SATOSHI.multiply(2), request2.tx.getFee());
assertEquals(4, request2.tx.getInputs().size());
}
@Test @Test
public void testCompleteTxWithExistingInputs() throws Exception { public void testCompleteTxWithExistingInputs() throws Exception {
// Tests calling completeTx with a SendRequest that already has a few inputs in it // Tests calling completeTx with a SendRequest that already has a few inputs in it
@ -3073,8 +2974,9 @@ public class WalletTest extends TestWithWallet {
Transaction tx = broadcaster.waitForTransactionAndSucceed(); Transaction tx = broadcaster.waitForTransactionAndSucceed();
final Coin THREE_CENTS = CENT.add(CENT).add(CENT); final Coin THREE_CENTS = CENT.add(CENT).add(CENT);
assertEquals(Coin.valueOf(24550), tx.getFee());
assertEquals(THREE_CENTS, tx.getValueSentFromMe(wallet)); assertEquals(THREE_CENTS, tx.getValueSentFromMe(wallet));
assertEquals(THREE_CENTS.subtract(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE), tx.getValueSentToMe(wallet)); assertEquals(THREE_CENTS.subtract(tx.getFee()), tx.getValueSentToMe(wallet));
// TX sends to one of our addresses (for now we ignore married wallets). // TX sends to one of our addresses (for now we ignore married wallets).
final Address toAddress = tx.getOutput(0).getScriptPubKey().getToAddress(PARAMS); final Address toAddress = tx.getOutput(0).getScriptPubKey().getToAddress(PARAMS);
final ECKey rotatingToKey = wallet.findKeyFromPubHash(toAddress.getHash160()); final ECKey rotatingToKey = wallet.findKeyFromPubHash(toAddress.getHash160());
@ -3095,9 +2997,11 @@ public class WalletTest extends TestWithWallet {
tx = broadcaster.waitForTransactionAndSucceed(); tx = broadcaster.waitForTransactionAndSucceed();
assertNotNull(wallet.findKeyFromPubHash(tx.getOutput(0).getScriptPubKey().getPubKeyHash())); assertNotNull(wallet.findKeyFromPubHash(tx.getOutput(0).getScriptPubKey().getPubKeyHash()));
log.info("Unexpected thing: {}", tx); log.info("Unexpected thing: {}", tx);
assertEquals(Coin.valueOf(9650), tx.getFee());
assertEquals(1, tx.getInputs().size()); assertEquals(1, tx.getInputs().size());
assertEquals(1, tx.getOutputs().size()); assertEquals(1, tx.getOutputs().size());
assertEquals(CENT.subtract(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE), tx.getOutput(0).getValue()); assertEquals(CENT, tx.getValueSentFromMe(wallet));
assertEquals(CENT.subtract(tx.getFee()), tx.getValueSentToMe(wallet));
assertEquals(Transaction.Purpose.KEY_ROTATION, tx.getPurpose()); assertEquals(Transaction.Purpose.KEY_ROTATION, tx.getPurpose());