Make feePerKb apply to the first kilobyte as well.

This commit is contained in:
Mike Hearn 2013-06-12 17:16:07 +02:00
parent c9f0ecae3d
commit 87ab410a2f
7 changed files with 107 additions and 58 deletions

View file

@ -1689,7 +1689,7 @@ public class Wallet implements Serializable, BlockChainListener {
* <p>You might also consider adding a {@link SendRequest#feePerKb} to set the fee per kb of transaction size * <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> * (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; public BigInteger fee = null;
/** /**
* <p>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
@ -1705,7 +1705,13 @@ public class Wallet implements Serializable, BlockChainListener {
* *
* <p>You might also consider using a {@link SendRequest#fee} to set the fee added for the first kb of size.</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; public BigInteger feePerKb = DEFAULT_FEE_PER_KB;
/**
* If you want to modify the default fee for your entire app without having to change each SendRequest you make,
* you can do it here. This is primarily useful for unit tests.
*/
public static BigInteger DEFAULT_FEE_PER_KB = Transaction.REFERENCE_DEFAULT_MIN_TX_FEE;
/** /**
* <p>Requires that there be enough fee for a default reference client to at least relay the transaction. * <p>Requires that there be enough fee for a default reference client to at least relay the transaction.
@ -1954,13 +1960,10 @@ public class Wallet implements Serializable, BlockChainListener {
// Note that output.isMine(this) needs to test the keychain which is currently an array, so it's // 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. // O(candidate outputs ^ keychain.size())! There's lots of low hanging fruit here.
LinkedList<TransactionOutput> candidates = calculateSpendCandidates(true); LinkedList<TransactionOutput> candidates = calculateSpendCandidates(true);
Address changeAddress = req.changeAddress;
int minSize = 0;
// This can throw InsufficientMoneyException. // This can throw InsufficientMoneyException.
FeeCalculation feeCalculation = null; FeeCalculation feeCalculation;
try { try {
feeCalculation = new FeeCalculation(req, value, originalInputs, needAtLeastReferenceFee, feeCalculation = new FeeCalculation(req, value, originalInputs, needAtLeastReferenceFee, candidates);
candidates, changeAddress, minSize);
} catch (InsufficientMoneyException e) { } catch (InsufficientMoneyException e) {
// TODO: Propagate this after 0.9 is released and stop returning a boolean. // TODO: Propagate this after 0.9 is released and stop returning a boolean.
return false; return false;
@ -1979,6 +1982,9 @@ public class Wallet implements Serializable, BlockChainListener {
log.info(" with {} coins change", bitcoinValueToFriendlyString(bestChangeOutput.getValue())); log.info(" with {} coins change", bitcoinValueToFriendlyString(bestChangeOutput.getValue()));
} }
final BigInteger calculatedFee = totalInput.subtract(totalOutput); final BigInteger calculatedFee = totalInput.subtract(totalOutput);
if (calculatedFee.compareTo(BigInteger.ZERO) > 0) {
log.info(" with a fee of {}", bitcoinValueToFriendlyString(calculatedFee));
}
// Now sign the inputs, thus proving that we are entitled to redeem the connected outputs. // Now sign the inputs, thus proving that we are entitled to redeem the connected outputs.
try { try {
@ -3084,8 +3090,7 @@ public class Wallet implements Serializable, BlockChainListener {
private TransactionOutput bestChangeOutput; private TransactionOutput bestChangeOutput;
public FeeCalculation(SendRequest req, BigInteger value, List<TransactionInput> originalInputs, public FeeCalculation(SendRequest req, BigInteger value, List<TransactionInput> originalInputs,
boolean needAtLeastReferenceFee, LinkedList<TransactionOutput> candidates, boolean needAtLeastReferenceFee, LinkedList<TransactionOutput> candidates) throws InsufficientMoneyException {
Address changeAddress, int minSize) throws InsufficientMoneyException {
// There are 3 possibilities for what adding change might do: // There are 3 possibilities for what adding change might do:
// 1) No effect // 1) No effect
// 2) Causes increase in fee (change < 0.01 COINS) // 2) Causes increase in fee (change < 0.01 COINS)
@ -3099,14 +3104,22 @@ public class Wallet implements Serializable, BlockChainListener {
TransactionOutput selection2Change = null; TransactionOutput selection2Change = null;
CoinSelection selection1 = null; CoinSelection selection1 = null;
TransactionOutput selection1Change = null; TransactionOutput selection1Change = null;
// We keep track of the last size of the transaction we calculated but only if the act of adding inputs and
// change resulted in the size crossing a 1000 byte boundary. Otherwise it stays at zero.
int lastCalculatedSize = 0;
BigInteger valueNeeded;
while (true) { while (true) {
resetTxInputs(req, originalInputs); resetTxInputs(req, originalInputs);
BigInteger fees = req.fee.add(BigInteger.valueOf(minSize/1000).multiply(req.feePerKb)); BigInteger fees = req.fee == null ? BigInteger.ZERO : req.fee;
if (lastCalculatedSize > 0)
fees = fees.add(BigInteger.valueOf((lastCalculatedSize / 1000) + 1).multiply(req.feePerKb));
else
fees = fees.add(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;
BigInteger valueNeeded = value.add(fees); valueNeeded = value.add(fees);
if (additionalValueForNextCategory != null) if (additionalValueForNextCategory != null)
valueNeeded = valueNeeded.add(additionalValueForNextCategory); valueNeeded = valueNeeded.add(additionalValueForNextCategory);
BigInteger additionalValueSelected = additionalValueForNextCategory; BigInteger additionalValueSelected = additionalValueForNextCategory;
@ -3146,7 +3159,8 @@ public class Wallet implements Serializable, BlockChainListener {
if (change.compareTo(BigInteger.ZERO) > 0) { 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, // 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 // 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.. // back to us. The address comes either from the request or getChangeAddress() as a default.
Address changeAddress = req.changeAddress;
if (changeAddress == null) if (changeAddress == null)
changeAddress = getChangeAddress(); changeAddress = getChangeAddress();
changeOutput = new TransactionOutput(params, req.tx, change, changeAddress); changeOutput = new TransactionOutput(params, req.tx, change, changeAddress);
@ -3177,11 +3191,12 @@ public class Wallet implements Serializable, BlockChainListener {
checkState(input.getScriptBytes().length == 0); checkState(input.getScriptBytes().length == 0);
} }
// Estimate transaction size and loop again if we need more fee per kb // Estimate transaction size and loop again if we need more fee per kb. The serialized tx doesn't
size += estimateBytesForSpending(selection); // include things we haven't added yet like input signatures/scripts or the change output.
size += req.tx.bitcoinSerialize().length; size += req.tx.bitcoinSerialize().length;
if (size/1000 > minSize/1000 && req.feePerKb.compareTo(BigInteger.ZERO) > 0) { size += estimateBytesForSigning(selection);
minSize = size; if (size/1000 > lastCalculatedSize/1000 && req.feePerKb.compareTo(BigInteger.ZERO) > 0) {
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;
continue; continue;
@ -3216,7 +3231,7 @@ public class Wallet implements Serializable, BlockChainListener {
resetTxInputs(req, originalInputs); resetTxInputs(req, originalInputs);
if (selection3 == null && selection2 == null && selection1 == null) { if (selection3 == null && selection2 == null && selection1 == null) {
log.warn("Insufficient value in wallet for send"); log.warn("Insufficient value in wallet for send: needed {}", bitcoinValueToFriendlyString(valueNeeded));
throw new InsufficientMoneyException(); throw new InsufficientMoneyException();
} }
@ -3249,7 +3264,7 @@ public class Wallet implements Serializable, BlockChainListener {
} }
} }
private int estimateBytesForSpending(CoinSelection selection) { private int estimateBytesForSigning(CoinSelection selection) {
int size = 0; int size = 0;
for (TransactionOutput output : selection.gathered) { for (TransactionOutput output : selection.gathered) {
try { try {

View file

@ -24,6 +24,7 @@ import com.google.bitcoin.store.BlockStore;
import com.google.bitcoin.store.MemoryBlockStore; import com.google.bitcoin.store.MemoryBlockStore;
import com.google.bitcoin.utils.BriefLogFormatter; import com.google.bitcoin.utils.BriefLogFormatter;
import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListenableFuture;
import org.junit.After;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
@ -63,6 +64,7 @@ public class BlockChainTest {
public void setUp() throws Exception { public void setUp() throws Exception {
BriefLogFormatter.initVerbose(); BriefLogFormatter.initVerbose();
testNetChain = new BlockChain(testNet, new Wallet(testNet), new MemoryBlockStore(testNet)); testNetChain = new BlockChain(testNet, new Wallet(testNet), new MemoryBlockStore(testNet));
Wallet.SendRequest.DEFAULT_FEE_PER_KB = BigInteger.ZERO;
unitTestParams = UnitTestParams.get(); unitTestParams = UnitTestParams.get();
wallet = new Wallet(unitTestParams) { wallet = new Wallet(unitTestParams) {
@ -83,6 +85,11 @@ public class BlockChainTest {
coinbaseTo = wallet.getKeys().get(0).toAddress(unitTestParams); coinbaseTo = wallet.getKeys().get(0).toAddress(unitTestParams);
} }
@After
public void tearDown() {
Wallet.SendRequest.DEFAULT_FEE_PER_KB = Transaction.REFERENCE_DEFAULT_MIN_TX_FEE;
}
@Test @Test
public void testBasicChaining() throws Exception { public void testBasicChaining() throws Exception {
// Check that we can plug a few blocks together and the futures work. // Check that we can plug a few blocks together and the futures work.
@ -323,11 +330,8 @@ public class BlockChainTest {
assertNull(coinbaseSpend); assertNull(coinbaseSpend);
} }
// Give it one more block - should now be able to spend coinbase transaction. // Give it one more block - should now be able to spend coinbase transaction. Non relevant tx.
// Non relevant tx. Transaction tx3 = createFakeTx(unitTestParams, Utils.toNanoCoins(1, 0), new ECKey().toAddress(unitTestParams));
Transaction tx3 = createFakeTx(unitTestParams, Utils.toNanoCoins(1, 0),
new ECKey().toAddress(unitTestParams));
Block b3 = createFakeBlock(blockStore, tx3).block; Block b3 = createFakeBlock(blockStore, tx3).block;
chain.add(b3); chain.add(b3);

View file

@ -44,6 +44,7 @@ public class ChainSplitTest {
@Before @Before
public void setUp() throws Exception { public void setUp() throws Exception {
BriefLogFormatter.init(); BriefLogFormatter.init();
Wallet.SendRequest.DEFAULT_FEE_PER_KB = BigInteger.ZERO;
unitTestParams = UnitTestParams.get(); unitTestParams = UnitTestParams.get();
wallet = new Wallet(unitTestParams); wallet = new Wallet(unitTestParams);
wallet.addKey(new ECKey()); wallet.addKey(new ECKey());

View file

@ -46,7 +46,8 @@ public class PeerGroupTest extends TestWithPeerGroup {
} }
@After @After
public void shutDown() throws Exception { public void tearDown() throws Exception {
super.tearDown();
peerGroup.stopAndWait(); peerGroup.stopAndWait();
} }

View file

@ -24,6 +24,7 @@ import org.easymock.EasyMock;
import org.easymock.IMocksControl; import org.easymock.IMocksControl;
import org.jboss.netty.channel.*; import org.jboss.netty.channel.*;
import java.math.BigInteger;
import java.net.InetAddress; import java.net.InetAddress;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.net.SocketAddress; import java.net.SocketAddress;
@ -60,6 +61,7 @@ public class TestWithNetworkConnections {
control.checkOrder(false); control.checkOrder(false);
unitTestParams = UnitTestParams.get(); unitTestParams = UnitTestParams.get();
Wallet.SendRequest.DEFAULT_FEE_PER_KB = BigInteger.ZERO;
this.blockStore = blockStore; this.blockStore = blockStore;
wallet = new Wallet(unitTestParams); wallet = new Wallet(unitTestParams);
key = new ECKey(); key = new ECKey();
@ -74,6 +76,10 @@ public class TestWithNetworkConnections {
pipeline = createPipeline(channel); pipeline = createPipeline(channel);
} }
public void tearDown() throws Exception {
Wallet.SendRequest.DEFAULT_FEE_PER_KB = Transaction.REFERENCE_DEFAULT_MIN_TX_FEE;
}
protected ChannelPipeline createPipeline(Channel channel) { protected ChannelPipeline createPipeline(Channel channel) {
ChannelPipeline pipeline = control.createMock(ChannelPipeline.class); ChannelPipeline pipeline = control.createMock(ChannelPipeline.class);
expect(channel.getPipeline()).andStubReturn(pipeline); expect(channel.getPipeline()).andStubReturn(pipeline);

View file

@ -20,6 +20,7 @@ import com.google.bitcoin.params.UnitTestParams;
import com.google.bitcoin.store.BlockStore; import com.google.bitcoin.store.BlockStore;
import com.google.bitcoin.store.MemoryBlockStore; import com.google.bitcoin.store.MemoryBlockStore;
import com.google.bitcoin.utils.BriefLogFormatter; import com.google.bitcoin.utils.BriefLogFormatter;
import org.junit.After;
import org.junit.Before; import org.junit.Before;
import java.io.IOException; import java.io.IOException;
@ -40,6 +41,7 @@ public class TestWithWallet {
@Before @Before
public void setUp() throws Exception { public void setUp() throws Exception {
BriefLogFormatter.init(); BriefLogFormatter.init();
Wallet.SendRequest.DEFAULT_FEE_PER_KB = BigInteger.ZERO;
myKey = new ECKey(); myKey = new ECKey();
myAddress = myKey.toAddress(params); myAddress = myKey.toAddress(params);
wallet = new Wallet(params); wallet = new Wallet(params);
@ -48,6 +50,11 @@ public class TestWithWallet {
chain = new BlockChain(params, wallet, blockStore); chain = new BlockChain(params, wallet, blockStore);
} }
@After
public void tearDown() throws Exception {
Wallet.SendRequest.DEFAULT_FEE_PER_KB = Transaction.REFERENCE_DEFAULT_MIN_TX_FEE;
}
protected Transaction sendMoneyToWallet(Wallet wallet, Transaction tx, AbstractBlockChain.NewBlockType type) protected Transaction sendMoneyToWallet(Wallet wallet, Transaction tx, AbstractBlockChain.NewBlockType type)
throws IOException, ProtocolException, VerificationException { throws IOException, ProtocolException, VerificationException {
if (type == null) { if (type == null) {

View file

@ -1193,7 +1193,8 @@ public class WalletTest extends TestWithWallet {
StoredBlock block = new StoredBlock(makeSolvedTestBlock(blockStore, notMyAddr), BigInteger.ONE, 1); StoredBlock block = new StoredBlock(makeSolvedTestBlock(blockStore, notMyAddr), BigInteger.ONE, 1);
Transaction tx1 = createFakeTx(params, BigInteger.ONE, myAddress); Transaction tx1 = createFakeTx(params, BigInteger.ONE, myAddress);
wallet.receiveFromBlock(tx1, block, AbstractBlockChain.NewBlockType.BEST_CHAIN); wallet.receiveFromBlock(tx1, block, AbstractBlockChain.NewBlockType.BEST_CHAIN);
Transaction tx2 = createFakeTx(params, BigInteger.ONE, myAddress); assertTrue(!tx1.getHash().equals(tx2.getHash())); Transaction tx2 = createFakeTx(params, BigInteger.ONE, myAddress);
assertTrue(!tx1.getHash().equals(tx2.getHash()));
wallet.receiveFromBlock(tx2, block, AbstractBlockChain.NewBlockType.BEST_CHAIN); wallet.receiveFromBlock(tx2, block, AbstractBlockChain.NewBlockType.BEST_CHAIN);
Transaction tx3 = createFakeTx(params, BigInteger.TEN, myAddress); Transaction tx3 = createFakeTx(params, BigInteger.TEN, myAddress);
wallet.receiveFromBlock(tx3, block, AbstractBlockChain.NewBlockType.BEST_CHAIN); wallet.receiveFromBlock(tx3, block, AbstractBlockChain.NewBlockType.BEST_CHAIN);
@ -1364,24 +1365,25 @@ public class WalletTest extends TestWithWallet {
assertTrue(request15.tx.bitcoinSerialize().length > 1000); assertTrue(request15.tx.bitcoinSerialize().length > 1000);
request15.feePerKb = BigInteger.ONE; request15.feePerKb = BigInteger.ONE;
assertTrue(wallet.completeTx(request15)); assertTrue(wallet.completeTx(request15));
assertEquals(BigInteger.ONE, request15.fee); assertEquals(BigInteger.valueOf(2), request15.fee);
Transaction spend15 = request15.tx; Transaction spend15 = request15.tx;
// If a transaction is over 1kb, the set fee 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
BigInteger outValue15 = BigInteger.ZERO; BigInteger outValue15 = BigInteger.ZERO;
for (TransactionOutput out : spend15.getOutputs()) for (TransactionOutput out : spend15.getOutputs())
outValue15 = outValue15.add(out.getValue()); outValue15 = outValue15.add(out.getValue());
assertEquals(outValue15, Utils.COIN.subtract(BigInteger.ONE)); assertEquals(Utils.COIN.subtract(BigInteger.valueOf(2)), outValue15);
SendRequest request16 = SendRequest.to(notMyAddr, Utils.CENT); SendRequest request16 = SendRequest.to(notMyAddr, Utils.CENT);
request16.feePerKb = BigInteger.ZERO;
for (int i = 0; i < 29; i++) for (int i = 0; i < 29; i++)
request16.tx.addOutput(Utils.CENT, notMyAddr); request16.tx.addOutput(Utils.CENT, notMyAddr);
assertTrue(request16.tx.bitcoinSerialize().length > 1000); assertTrue(request16.tx.bitcoinSerialize().length > 1000);
assertTrue(wallet.completeTx(request16)); assertTrue(wallet.completeTx(request16));
// Of course the fee shouldn't be added if feePerKb == 0
assertEquals(BigInteger.ZERO, request16.fee); assertEquals(BigInteger.ZERO, request16.fee);
Transaction spend16 = request16.tx; Transaction spend16 = request16.tx;
// Of course the fee shouldn't be added if feePerKb == 0
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
BigInteger outValue16 = BigInteger.ZERO; BigInteger outValue16 = BigInteger.ZERO;
@ -1389,14 +1391,14 @@ public class WalletTest extends TestWithWallet {
outValue16 = outValue16.add(out.getValue()); outValue16 = outValue16.add(out.getValue());
assertEquals(Utils.COIN, outValue16); assertEquals(Utils.COIN, outValue16);
// Create a transaction who's 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(notMyAddr, Utils.CENT); SendRequest request17 = SendRequest.to(notMyAddr, Utils.CENT);
for (int i = 0; i < 22; i++) for (int i = 0; i < 22; i++)
request17.tx.addOutput(Utils.CENT, notMyAddr); request17.tx.addOutput(Utils.CENT, notMyAddr);
request17.tx.addOutput(new TransactionOutput(params, request17.tx, Utils.CENT, new byte[15])); request17.tx.addOutput(new TransactionOutput(params, request17.tx, Utils.CENT, new byte[15]));
request17.feePerKb = BigInteger.ONE; request17.feePerKb = BigInteger.ONE;
assertTrue(wallet.completeTx(request17)); assertTrue(wallet.completeTx(request17));
assertEquals(BigInteger.ZERO, request17.fee); assertEquals(BigInteger.ONE, request17.fee);
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.bitcoinSerialize().length + myKey.getPubKey().length + 75; int theoreticalMaxLength17 = request17.tx.bitcoinSerialize().length + myKey.getPubKey().length + 75;
@ -1404,54 +1406,59 @@ public class WalletTest extends TestWithWallet {
theoreticalMaxLength17 -= in.getScriptBytes().length; theoreticalMaxLength17 -= in.getScriptBytes().length;
assertEquals(999, theoreticalMaxLength17); assertEquals(999, theoreticalMaxLength17);
Transaction spend17 = request17.tx; 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); // Its actual size must be between 997 and 999 (inclusive) as signatures have a 3-byte size range (almost always)
// Now check that it didn't get a fee since its max size is 999 final int length = spend17.bitcoinSerialize().length;
assertTrue(Integer.toString(length), length >= 997 && length <= 999);
}
// 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
BigInteger outValue17 = BigInteger.ZERO; BigInteger outValue17 = BigInteger.ZERO;
for (TransactionOutput out : spend17.getOutputs()) for (TransactionOutput out : spend17.getOutputs())
outValue17 = outValue17.add(out.getValue()); outValue17 = outValue17.add(out.getValue());
assertEquals(Utils.COIN, outValue17); assertEquals(Utils.COIN.subtract(BigInteger.ONE), outValue17);
// Create a transaction who's max size could be up to 1000 (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(notMyAddr, Utils.CENT); SendRequest request18 = SendRequest.to(notMyAddr, Utils.CENT);
for (int i = 0; i < 22; i++) for (int i = 0; i < 22; i++)
request18.tx.addOutput(Utils.CENT, notMyAddr); request18.tx.addOutput(Utils.CENT, notMyAddr);
request18.tx.addOutput(new TransactionOutput(params, request18.tx, Utils.CENT, new byte[16])); request18.tx.addOutput(new TransactionOutput(params, request18.tx, Utils.CENT, new byte[17]));
request18.feePerKb = BigInteger.ONE; request18.feePerKb = BigInteger.ONE;
assertTrue(wallet.completeTx(request18)); assertTrue(wallet.completeTx(request18));
assertEquals(BigInteger.ONE, request18.fee); assertEquals(BigInteger.valueOf(2), request18.fee);
assertEquals(1, request18.tx.getInputs().size()); assertEquals(1, request18.tx.getInputs().size());
// Calculate its max length to make sure it is indeed 1000 // Calculate its max length to make sure it is indeed 1001
Transaction spend18 = request18.tx; Transaction spend18 = request18.tx;
int theoreticalMaxLength18 = spend18.bitcoinSerialize().length + myKey.getPubKey().length + 75; int theoreticalMaxLength18 = spend18.bitcoinSerialize().length + myKey.getPubKey().length + 75;
for (TransactionInput in : spend18.getInputs()) for (TransactionInput in : spend18.getInputs())
theoreticalMaxLength18 -= in.getScriptBytes().length; theoreticalMaxLength18 -= in.getScriptBytes().length;
assertEquals(1000, theoreticalMaxLength18); assertEquals(1001, theoreticalMaxLength18);
// Its actual size must be between 998 and 1000 (inclusive) as signatures have a 3-byte size range (almost always) // 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); assertTrue(spend18.bitcoinSerialize().length >= 998);
assertTrue(spend18.bitcoinSerialize().length <= 1000); assertTrue(spend18.bitcoinSerialize().length <= 1001);
// 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
BigInteger outValue18 = BigInteger.ZERO; BigInteger outValue18 = BigInteger.ZERO;
for (TransactionOutput out : spend18.getOutputs()) for (TransactionOutput out : spend18.getOutputs())
outValue18 = outValue18.add(out.getValue()); outValue18 = outValue18.add(out.getValue());
assertEquals(outValue18, Utils.COIN.subtract(BigInteger.ONE)); assertEquals(outValue18, Utils.COIN.subtract(BigInteger.valueOf(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(), Utils.CENT.add(Utils.COIN)); assertEquals(wallet.getBalance(), Utils.CENT.add(Utils.COIN));
SendRequest request19 = SendRequest.to(notMyAddr, Utils.CENT); SendRequest request19 = SendRequest.to(notMyAddr, Utils.CENT);
request19.feePerKb = BigInteger.ZERO;
for (int i = 0; i < 99; i++) for (int i = 0; i < 99; i++)
request19.tx.addOutput(Utils.CENT, notMyAddr); request19.tx.addOutput(Utils.CENT, notMyAddr);
// If we send now, we shouldnt need a fee and should only have to spend our COIN // If we send now, we shouldn't need a fee and should only have to spend our COIN
assertTrue(wallet.completeTx(request19)); assertTrue(wallet.completeTx(request19));
assertEquals(BigInteger.ZERO, request19.fee); assertEquals(BigInteger.ZERO, request19.fee);
assertEquals(1, request19.tx.getInputs().size()); assertEquals(1, request19.tx.getInputs().size());
assertEquals(100, request19.tx.getOutputs().size()); assertEquals(100, request19.tx.getOutputs().size());
// Now reset request19 and give it a fee per kb // Now reset request19 and give it a fee per kb
request19.completed = false; request19.tx.clearInputs(); request19.completed = false;
request19.tx.clearInputs();
request19.feePerKb = BigInteger.ONE; request19.feePerKb = BigInteger.ONE;
assertTrue(wallet.completeTx(request19)); assertTrue(wallet.completeTx(request19));
assertEquals(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE, request19.fee); assertEquals(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE, request19.fee);
@ -1466,9 +1473,10 @@ public class WalletTest extends TestWithWallet {
// 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(notMyAddr, Utils.CENT); SendRequest request20 = SendRequest.to(notMyAddr, Utils.CENT);
request20.feePerKb = BigInteger.ZERO;
for (int i = 0; i < 99; i++) for (int i = 0; i < 99; i++)
request20.tx.addOutput(Utils.CENT, notMyAddr); request20.tx.addOutput(Utils.CENT, notMyAddr);
// If we send now, we shouldnt need a fee and should only have to spend our COIN // If we send now, we shouldn't have a fee and should only have to spend our COIN
assertTrue(wallet.completeTx(request20)); assertTrue(wallet.completeTx(request20));
assertEquals(BigInteger.ZERO, request20.fee); assertEquals(BigInteger.ZERO, request20.fee);
assertEquals(1, request20.tx.getInputs().size()); assertEquals(1, request20.tx.getInputs().size());
@ -1477,16 +1485,19 @@ public class WalletTest extends TestWithWallet {
request20.completed = false; request20.tx.clearInputs(); request20.completed = false; request20.tx.clearInputs();
request20.feePerKb = Transaction.REFERENCE_DEFAULT_MIN_TX_FEE; request20.feePerKb = Transaction.REFERENCE_DEFAULT_MIN_TX_FEE;
assertTrue(wallet.completeTx(request20)); assertTrue(wallet.completeTx(request20));
assertEquals(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE.multiply(BigInteger.valueOf(3)), request20.fee); // 4kb tx.
assertEquals(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE.multiply(BigInteger.valueOf(4)), request20.fee);
assertEquals(2, request20.tx.getInputs().size()); assertEquals(2, request20.tx.getInputs().size());
BigInteger outValue20 = BigInteger.ZERO; BigInteger outValue20 = BigInteger.ZERO;
for (TransactionOutput out : request20.tx.getOutputs()) for (TransactionOutput out : request20.tx.getOutputs())
outValue20 = outValue20.add(out.getValue()); outValue20 = outValue20.add(out.getValue());
// This time the fee we wanted to pay was more, so that should be what we paid // This time the fee we wanted to pay was more, so that should be what we paid
assertEquals(outValue20, Utils.COIN.add(Utils.CENT).subtract(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE.multiply(BigInteger.valueOf(3)))); assertEquals(outValue20, Utils.COIN.add(Utils.CENT).subtract(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE.multiply(BigInteger.valueOf(4))));
// 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 // 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); SendRequest request21 = SendRequest.to(notMyAddr, Utils.CENT);
request21.feePerKb = BigInteger.ZERO;
for (int i = 0; i < 99; i++) for (int i = 0; i < 99; i++)
request21.tx.addOutput(Utils.CENT, notMyAddr); request21.tx.addOutput(Utils.CENT, notMyAddr);
request21.tx.addOutput(Utils.CENT.subtract(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE), notMyAddr); request21.tx.addOutput(Utils.CENT.subtract(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE), notMyAddr);
@ -1502,15 +1513,18 @@ public class WalletTest extends TestWithWallet {
// Test feePerKb when we aren't using ensureMinRequiredFee // Test feePerKb when we aren't using ensureMinRequiredFee
// Same as request 19 // Same as request 19
SendRequest request25 = SendRequest.to(notMyAddr, Utils.CENT); SendRequest request25 = SendRequest.to(notMyAddr, Utils.CENT);
for (int i = 0; i < 99; i++) request25.feePerKb = BigInteger.ZERO;
for (int i = 0; i < 70; i++)
request25.tx.addOutput(Utils.CENT, notMyAddr); request25.tx.addOutput(Utils.CENT, notMyAddr);
// If we send now, we shouldnt need a fee and should only have to spend our COIN // If we send now, we shouldn't need a fee and should only have to spend our COIN
assertTrue(wallet.completeTx(request25)); assertTrue(wallet.completeTx(request25));
assertEquals(BigInteger.ZERO, request25.fee); assertEquals(BigInteger.ZERO, request25.fee);
assertEquals(1, request25.tx.getInputs().size()); assertEquals(1, request25.tx.getInputs().size());
assertEquals(100, request25.tx.getOutputs().size()); assertEquals(72, request25.tx.getOutputs().size());
System.out.println(request25.tx.bitcoinSerialize().length);
// Now reset request19 and give it a fee per kb // Now reset request19 and give it a fee per kb
request25.completed = false; request25.tx.clearInputs(); request25.tx.clearInputs();
request25 = SendRequest.forTx(request25.tx);
request25.feePerKb = Utils.CENT.divide(BigInteger.valueOf(3)); request25.feePerKb = Utils.CENT.divide(BigInteger.valueOf(3));
request25.ensureMinRequiredFee = false; request25.ensureMinRequiredFee = false;
assertTrue(wallet.completeTx(request25)); assertTrue(wallet.completeTx(request25));
@ -1519,13 +1533,13 @@ public class WalletTest extends TestWithWallet {
BigInteger outValue25 = BigInteger.ZERO; BigInteger outValue25 = BigInteger.ZERO;
for (TransactionOutput out : request25.tx.getOutputs()) for (TransactionOutput out : request25.tx.getOutputs())
outValue25 = outValue25.add(out.getValue()); outValue25 = outValue25.add(out.getValue());
// Our change output should be one nanocoin // Our change output should be one satoshi
// Change this assert when we eventually randomize output order // Change this assert when we eventually randomize output order
assertEquals(BigInteger.ONE, request25.tx.getOutput(request25.tx.getOutputs().size() - 1).getValue()); assertEquals(BigInteger.ONE, request25.tx.getOutput(request25.tx.getOutputs().size() - 1).getValue());
// and our fee should be CENT-1 nanocoin // and our fee should be CENT-1 satoshi
assertEquals(outValue25, Utils.COIN.add(BigInteger.ONE)); assertEquals(outValue25, Utils.COIN.add(BigInteger.ONE));
//Spend our CENT output // Spend our CENT output.
Transaction spendTx5 = new Transaction(params); Transaction spendTx5 = new Transaction(params);
spendTx5.addOutput(Utils.CENT, notMyAddr); spendTx5.addOutput(Utils.CENT, notMyAddr);
spendTx5.addInput(tx5.getOutput(0)); spendTx5.addInput(tx5.getOutput(0));
@ -1557,7 +1571,7 @@ public class WalletTest extends TestWithWallet {
@Test @Test
public void basicCategoryStepTest() throws Exception { public void basicCategoryStepTest() throws Exception {
// Creates spends that step through the possible fee solver categories // Creates spends that step through the possible fee solver categories
SendRequest.DEFAULT_FEE_PER_KB = BigInteger.ZERO;
// Make sure TestWithWallet isnt doing anything crazy. // Make sure TestWithWallet isnt doing anything crazy.
assertEquals(0, wallet.getTransactions(true).size()); assertEquals(0, wallet.getTransactions(true).size());
@ -1631,6 +1645,8 @@ public class WalletTest extends TestWithWallet {
assertTrue(wallet.completeTx(request6)); assertTrue(wallet.completeTx(request6));
assertEquals(BigInteger.ZERO, request6.fee); assertEquals(BigInteger.ZERO, request6.fee);
assertEquals(2, request6.tx.getOutputs().size()); // We should have a change output assertEquals(2, request6.tx.getOutputs().size()); // We should have a change output
SendRequest.DEFAULT_FEE_PER_KB = Transaction.REFERENCE_DEFAULT_MIN_TX_FEE;
} }
@Test @Test
@ -1701,11 +1717,10 @@ public class WalletTest extends TestWithWallet {
for (int i = 0; i < 16; i++) for (int i = 0; i < 16; i++)
request2.tx.addOutput(Utils.CENT, notMyAddr); request2.tx.addOutput(Utils.CENT, notMyAddr);
request2.tx.addOutput(new TransactionOutput(params, request2.tx, Utils.CENT, new byte[16])); request2.tx.addOutput(new TransactionOutput(params, request2.tx, Utils.CENT, new byte[16]));
request2.fee = BigInteger.ONE;
request2.feePerKb = BigInteger.ONE; request2.feePerKb = BigInteger.ONE;
// The process is the same as above, but now we can complete category 1 with one more input, and pay a fee of 2 // The process is the same as above, but now we can complete category 1 with one more input, and pay a fee of 2
assertTrue(wallet.completeTx(request2)); assertTrue(wallet.completeTx(request2));
assertEquals(BigInteger.ONE.shiftLeft(1), request2.fee); assertEquals(BigInteger.valueOf(2), request2.fee);
assertEquals(4, request2.tx.getInputs().size()); assertEquals(4, request2.tx.getInputs().size());
} }