Commit e48ced6d authored by Andreas Schildbach's avatar Andreas Schildbach

Remove cent rule from fee solving in Wallet and payment channels.

This commit sadly disables WalletTest.basicCategoryStepTest() because that test is not maintainable and
doesn't work any more. Hopefully we will rewrite fee solving together with a better set of unit tests.
parent 53d2d562
......@@ -4132,26 +4132,18 @@ public class Wallet extends BaseTaggableObject
value = value.subtract(totalInput);
List<TransactionInput> originalInputs = new ArrayList<TransactionInput>(req.tx.getInputs());
int opReturnCount = 0;
// 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;
// Check for dusty sends and the OP_RETURN limit.
if (req.ensureMinRequiredFee && !req.emptyWallet) { // Min fee checking is handled later for emptyWallet.
int opReturnCount = 0;
for (TransactionOutput output : req.tx.getOutputs()) {
if (output.getValue().compareTo(Coin.CENT) < 0) {
needAtLeastReferenceFee = true;
if (output.isDust())
throw new DustySendRequested();
if (output.getScriptPubKey().isOpReturn())
++opReturnCount;
else
break;
}
if (output.isDust())
throw new DustySendRequested();
if (output.getScriptPubKey().isOpReturn())
++opReturnCount;
}
}
if (opReturnCount > 1) { // Only 1 OP_RETURN per transaction allowed.
throw new MultipleOpReturnRequested();
if (opReturnCount > 1) // Only 1 OP_RETURN per transaction allowed.
throw new MultipleOpReturnRequested();
}
// Calculate a list of ALL potential candidates for spending and then ask a coin selector to provide us
......@@ -4164,7 +4156,7 @@ public class Wallet extends BaseTaggableObject
TransactionOutput bestChangeOutput = null;
if (!req.emptyWallet) {
// This can throw InsufficientMoneyException.
FeeCalculation feeCalculation = calculateFee(req, value, originalInputs, needAtLeastReferenceFee, candidates);
FeeCalculation feeCalculation = calculateFee(req, value, originalInputs, req.ensureMinRequiredFee, candidates);
bestCoinSelection = feeCalculation.bestCoinSelection;
bestChangeOutput = feeCalculation.bestChangeOutput;
} else {
......
......@@ -132,7 +132,6 @@ public abstract class PaymentChannelClientState {
* @param myKey a freshly generated private key for this channel.
* @param serverKey a public key retrieved from the server used for the initial multisig contract
* @param value how many satoshis to put into this contract. If the channel reaches this limit, it must be closed.
* It is suggested you use at least {@link Coin#CENT} to avoid paying fees if you need to spend the refund transaction
* @param expiryTimeInSeconds At what point (UNIX timestamp +/- a few hours) the channel will expire
*
* @throws VerificationException If either myKey's pubkey or serverKey's pubkey are non-canonical (ie invalid)
......
......@@ -77,7 +77,6 @@ public class PaymentChannelV1ClientState extends PaymentChannelClientState {
* @param myKey a freshly generated private key for this channel.
* @param serverMultisigKey a public key retrieved from the server used for the initial multisig contract
* @param value how many satoshis to put into this contract. If the channel reaches this limit, it must be closed.
* It is suggested you use at least {@link Coin#CENT} to avoid paying fees if you need to spend the refund transaction
* @param expiryTimeInSeconds At what point (UNIX timestamp +/- a few hours) the channel will expire
*
* @throws VerificationException If either myKey's pubkey or serverKey's pubkey are non-canonical (ie invalid)
......@@ -155,7 +154,7 @@ public class PaymentChannelV1ClientState extends PaymentChannelClientState {
// by using this sequence value, we avoid extra full replace-by-fee and relative lock time processing.
refundTx.addInput(multisigOutput).setSequenceNumber(TransactionInput.NO_SEQUENCE - 1L);
refundTx.setLockTime(expiryTime);
if (totalValue.compareTo(Coin.CENT) < 0 && Context.get().isEnsureMinRequiredFee()) {
if (Context.get().isEnsureMinRequiredFee()) {
// Must pay min fee.
final Coin valueAfterFee = totalValue.subtract(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE);
if (Transaction.MIN_NONDUST_OUTPUT.compareTo(valueAfterFee) > 0)
......
......@@ -126,7 +126,7 @@ public class PaymentChannelV2ClientState extends PaymentChannelClientState {
// by using this sequence value, we avoid extra full replace-by-fee and relative lock time processing.
refundTx.addInput(contract.getOutput(0)).setSequenceNumber(TransactionInput.NO_SEQUENCE - 1L);
refundTx.setLockTime(expiryTime);
if (totalValue.compareTo(Coin.CENT) < 0 && Context.get().isEnsureMinRequiredFee()) {
if (Context.get().isEnsureMinRequiredFee()) {
// Must pay min fee.
final Coin valueAfterFee = totalValue.subtract(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE);
if (Transaction.MIN_NONDUST_OUTPUT.compareTo(valueAfterFee) > 0)
......
......@@ -17,6 +17,7 @@
package org.bitcoinj.protocols.channels;
import org.bitcoinj.core.*;
import org.bitcoinj.core.Wallet.SendRequest;
import org.bitcoinj.script.Script;
import org.bitcoinj.script.ScriptBuilder;
import org.bitcoinj.testing.TestWithWallet;
......@@ -88,7 +89,7 @@ public class PaymentChannelStateTest extends TestWithWallet {
public void setUp() throws Exception {
Utils.setMockClock(); // Use mock clock
super.setUp();
Context.propagate(new Context(PARAMS, 100, Coin.ZERO, true));
Context.propagate(new Context(PARAMS, 100, Coin.ZERO, false));
wallet.addExtension(new StoredPaymentChannelClientStates(wallet, new TransactionBroadcaster() {
@Override
public TransactionBroadcast broadcastTransaction(Transaction tx) {
......@@ -357,8 +358,6 @@ public class PaymentChannelStateTest extends TestWithWallet {
assertEquals(PaymentChannelClientState.State.NEW, clientState.getState());
assertEquals(CENT.divide(2), clientState.getTotalValue());
clientState.initiate();
// We will have to pay min_tx_fee twice - both the multisig contract and the refund tx
assertEquals(clientState.getRefundTxFees(), Transaction.REFERENCE_DEFAULT_MIN_TX_FEE.multiply(2));
assertEquals(getInitialClientState(), clientState.getState());
if (useRefunds()) {
......@@ -442,7 +441,7 @@ public class PaymentChannelStateTest extends TestWithWallet {
chain.add(makeSolvedTestBlock(blockStore.getChainHead().getHeader(), multisigContract,clientBroadcastedRefund));
// Make sure we actually had to pay what initialize() told us we would
assertEquals(wallet.getBalance(), CENT.subtract(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE.multiply(2)));
assertEquals(CENT, wallet.getBalance());
try {
// After its expired, we cant still increment payment
......@@ -687,13 +686,17 @@ public class PaymentChannelStateTest extends TestWithWallet {
@Test
public void feesTest() throws Exception {
// Test that transactions are getting the necessary fees
Context.propagate(new Context(PARAMS, 100, Coin.ZERO, true));
// Spend the client wallet's one coin
wallet.sendCoinsOffline(Wallet.SendRequest.to(new ECKey().toAddress(PARAMS), COIN));
final SendRequest request = Wallet.SendRequest.to(new ECKey().toAddress(PARAMS), COIN);
request.ensureMinRequiredFee = false;
wallet.sendCoinsOffline(request);
assertEquals(Coin.ZERO, wallet.getBalance());
chain.add(makeSolvedTestBlock(blockStore.getChainHead().getHeader(), createFakeTx(PARAMS, CENT, myAddress)));
assertEquals(CENT, wallet.getBalance());
chain.add(makeSolvedTestBlock(blockStore.getChainHead().getHeader(),
createFakeTx(PARAMS, CENT.add(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE), myAddress)));
assertEquals(CENT.add(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE), wallet.getBalance());
Utils.setMockClock(); // Use mock clock
final long EXPIRE_TIME = Utils.currentTimeMillis()/1000 + 60*60*24;
......@@ -724,14 +727,14 @@ public class PaymentChannelStateTest extends TestWithWallet {
assertEquals(PaymentChannelClientState.State.NEW, clientState.getState());
// We'll have to pay REFERENCE_DEFAULT_MIN_TX_FEE twice (multisig+refund), and we'll end up getting back nearly nothing...
clientState.initiate();
assertEquals(clientState.getRefundTxFees(), Transaction.REFERENCE_DEFAULT_MIN_TX_FEE.multiply(2));
assertEquals(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE.multiply(2), clientState.getRefundTxFees());
assertEquals(getInitialClientState(), clientState.getState());
// Now actually use a more useful CENT
clientState = makeClientState(wallet, myKey, ECKey.fromPublicOnly(serverKey.getPubKey()), CENT, EXPIRE_TIME);
assertEquals(PaymentChannelClientState.State.NEW, clientState.getState());
clientState.initiate();
assertEquals(clientState.getRefundTxFees(), Coin.ZERO);
assertEquals(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE.multiply(2), clientState.getRefundTxFees());
assertEquals(getInitialClientState(), clientState.getState());
if (useRefunds()) {
......@@ -802,6 +805,7 @@ public class PaymentChannelStateTest extends TestWithWallet {
@Test
public void serverAddsFeeTest() throws Exception {
// Test that the server properly adds the necessary fee at the end (or just drops the payment if its not worth it)
Context.propagate(new Context(PARAMS, 100, Coin.ZERO, true));
Utils.setMockClock(); // Use mock clock
final long EXPIRE_TIME = Utils.currentTimeMillis()/1000 + 60*60*24;
......@@ -882,9 +886,7 @@ public class PaymentChannelStateTest extends TestWithWallet {
}
// Now give the server enough coins to pay the fee
StoredBlock block = new StoredBlock(makeSolvedTestBlock(blockStore, new ECKey().toAddress(PARAMS)), BigInteger.ONE, 1);
Transaction tx1 = createFakeTx(PARAMS, COIN, serverKey.toAddress(PARAMS));
serverWallet.receiveFromBlock(tx1, block, AbstractBlockChain.NewBlockType.BEST_CHAIN, 0);
sendMoneyToWallet(serverWallet, AbstractBlockChain.NewBlockType.BEST_CHAIN, COIN, serverKey.toAddress(PARAMS));
// The contract is still not worth redeeming - its worth less than we pay in fee
try {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment