Refactor fee handling, add user defined fee to settings, UI fixes

This commit is contained in:
Manfred Karrer 2016-01-21 02:40:49 +01:00
parent 35d6612820
commit f723bf5737
36 changed files with 255 additions and 143 deletions

View file

@ -18,39 +18,67 @@
package io.bitsquare.btc;
import org.bitcoinj.core.Coin;
import org.bitcoinj.core.Wallet;
public class FeePolicy {
// Official min. fee and fee per kiloByte dropped down to 0.00001 BTC / Coin.valueOf(1000) / 1000 satoshis, but as there are reported problems with
// confirmation we use a hgher value.
// The should also help to avoid problems when the tx size is larger as the standard (e.g. if the user does not pay
// in with one transaction but several tx). We don't do a dynamically fee calculation as we need predictable amounts, so that should help to get a larger
// headroom.
// Andreas Schildbach reported problems with confirmation and increased the fee/offered UI side fee setting.
// http://www.cointape.com/
// The fastest and cheapest transaction fee is currently 50 satoshis/byte, shown in green at the top.
// For the average transaction size of 597 bytes, this results in a fee of 298 bits (0.298 mBTC). -> 0.0003 BTC or Coin.valueOf(30000);
// With block getting filled up the needed fee to get fast into a black has become more expensive and less predictable.
// To see current fees check out:
// https://tradeblock.com/blockchain
// http://www.cointape.com
// Average values are 10-100 satoshis/byte in january 2016
//
// Our trade transactions have a fixed set of inputs and outputs making the size very predictable
// (as long the user does not do multiple funding transactions)
//
// trade fee tx: 226 bytes
// deposit tx: 336 bytes
// payout tx: 371 bytes
// disputed payout tx: 408 bytes -> 20400 satoshis with 50 satoshis/byte
// disputed payout tx: 408 bytes
// Other good source is: https://tradeblock.com/blockchain 15-100 satoshis/byte
public static final Coin TX_FEE = Coin.valueOf(30000); // 0.0003 BTC about 1.2 EUR @ 400 EUR/BTC: about 100 satoshi /byte
static {
Wallet.SendRequest.DEFAULT_FEE_PER_KB = TX_FEE;
// We set a fixed fee to make the needed amounts in the trade predictable.
// We use 0.0003 BTC (0.12 EUR @ 400 EUR/BTC) which is for our tx sizes about 75-150 satoshi/byte
// We cannot make that user defined as it need to be the same for both users, so we can only change that in
// software updates
// TODO before Beta we should get a good future proof guess as a change causes incompatible versions
public static Coin getFixedTxFeeForTrades() {
return Coin.valueOf(30_000);
}
public static final Coin CREATE_OFFER_FEE = Coin.valueOf(100000); // 0.001 BTC 0.1% of 1 BTC about 0.4 EUR @ 400 EUR/BTC
public static final Coin TAKE_OFFER_FEE = CREATE_OFFER_FEE;
// For non trade transactions (withdrawal) we use the default fee calculation
// To avoid issues with not getting into full blocks, we increase the fee/kb to 30 satoshi/byte
// The user can change that in the preferences
// The BitcoinJ fee calculation use kb so a tx size < 1kb will still pay the fee for a kb tx.
// Our payout tx has about 370 bytes so we get a fee/kb value of about 90 satoshi/byte making it high priority
// Other payout transactions (E.g. arbitrators many collected transactions) will go with 30 satoshi/byte if > 1kb
private static Coin FEE_PER_KB = Coin.valueOf(30_000); // 0.0003 BTC about 0.12 EUR @ 400 EUR/BTC
// TODO make final again later
public static Coin SECURITY_DEPOSIT = Coin.valueOf(10000000); // 0.1 BTC; about 4 EUR @ 400 EUR/BTC
public static void setFeePerKb(Coin feePerKb) {
FEE_PER_KB = feePerKb;
}
public static Coin getFeePerKb() {
return FEE_PER_KB;
}
// 0.001 BTC 0.1% of 1 BTC about 0.4 EUR @ 400 EUR/BTC
public static Coin getCreateOfferFee() {
return Coin.valueOf(100_000);
}
// Currently we use the same fee for both offerer and taker
public static Coin getTakeOfferFee() {
return getCreateOfferFee();
}
// TODO make final again later 100_000_000
// 0.1 BTC; about 4 EUR @ 400 EUR/BTC
private static Coin SECURITY_DEPOSIT = Coin.valueOf(10_000_000);
public static Coin getSecurityDeposit() {
return SECURITY_DEPOSIT;
}
// Called from WalletService to reduce SECURITY_DEPOSIT for mainnet to 0.01 btc
// TODO remove later when tested enough

View file

@ -34,7 +34,11 @@ public class Restrictions {
MAX_TRADE_AMOUNT = maxTradeAmount;
}
public static boolean isMinSpendableAmount(Coin amount) {
return amount != null && amount.compareTo(FeePolicy.TX_FEE.add(Transaction.MIN_NONDUST_OUTPUT)) > 0;
public static boolean isAboveFixedTxFeeAndDust(Coin amount) {
return amount != null && amount.compareTo(FeePolicy.getFixedTxFeeForTrades().add(Transaction.MIN_NONDUST_OUTPUT)) > 0;
}
public static boolean isAboveDust(Coin amount) {
return amount != null && amount.compareTo(Transaction.MIN_NONDUST_OUTPUT) > 0;
}
}

View file

@ -142,9 +142,9 @@ public class TradeWalletService {
public Transaction createTradingFeeTx(AddressEntry addressEntry, Coin tradingFee, String feeReceiverAddresses)
throws InsufficientMoneyException, AddressFormatException {
Transaction tradingFeeTx = new Transaction(params);
Preconditions.checkArgument(Restrictions.isMinSpendableAmount(tradingFee),
Preconditions.checkArgument(Restrictions.isAboveFixedTxFeeAndDust(tradingFee),
"You cannot send an amount which are smaller than the fee + dust output.");
Coin outPutAmount = tradingFee.subtract(FeePolicy.TX_FEE);
Coin outPutAmount = tradingFee.subtract(FeePolicy.getFixedTxFeeForTrades());
tradingFeeTx.addOutput(outPutAmount, new Address(params, feeReceiverAddresses));
// we allow spending of unconfirmed tx (double spend risk is low and usability would suffer if we need to
@ -154,14 +154,13 @@ public class TradeWalletService {
sendRequest.shuffleOutputs = false;
sendRequest.aesKey = aesKey;
sendRequest.coinSelector = new AddressBasedCoinSelector(params, addressEntry);
// We use a fixed fee
sendRequest.feePerKb = Coin.ZERO;
sendRequest.fee = FeePolicy.getFixedTxFeeForTrades();
// We use always the same address for all transactions in a trade to keep things simple.
// To be discussed if that introduce any privacy issues.
sendRequest.changeAddress = addressEntry.getAddress();
// Wallet.SendRequest.DEFAULT_FEE_PER_KB is set in FeePolicy to our defined amount
// We don't want to risk delayed transactions so we set the fee rather high.
// Delayed tx will lead to a broken chain of the deposit transaction.
wallet.completeTx(sendRequest);
printTxWithInputs("tradingFeeTx", tradingFeeTx);
@ -212,7 +211,7 @@ public class TradeWalletService {
*/
// inputAmount includes the tx fee. So we subtract the fee to get the dummyOutputAmount.
Coin dummyOutputAmount = inputAmount.subtract(FeePolicy.TX_FEE);
Coin dummyOutputAmount = inputAmount.subtract(FeePolicy.getFixedTxFeeForTrades());
Transaction dummyTX = new Transaction(params);
// The output is just used to get the right inputs and change outputs, so we use an anonymous ECKey, as it will never be used for anything.
@ -303,7 +302,7 @@ public class TradeWalletService {
// First we construct a dummy TX to get the inputs and outputs we want to use for the real deposit tx.
// Similar to the way we did in the createTakerDepositTxInputs method.
Transaction dummyTx = new Transaction(params);
Coin dummyOutputAmount = offererInputAmount.subtract(FeePolicy.TX_FEE);
Coin dummyOutputAmount = offererInputAmount.subtract(FeePolicy.getFixedTxFeeForTrades());
TransactionOutput dummyOutput = new TransactionOutput(params, dummyTx, dummyOutputAmount, new ECKey().toAddress(params));
dummyTx.addOutput(dummyOutput);
addAvailableInputsAndChangeOutputs(dummyTx, offererAddressInfo);
@ -640,7 +639,6 @@ public class TradeWalletService {
return payoutTx;
}
///////////////////////////////////////////////////////////////////////////////////////////
// Dispute
///////////////////////////////////////////////////////////////////////////////////////////
@ -694,12 +692,15 @@ public class TradeWalletService {
preparedPayoutTx.addOutput(buyerPayoutAmount, new Address(params, buyerAddressString));
if (sellerPayoutAmount.isGreaterThan(Coin.ZERO))
preparedPayoutTx.addOutput(sellerPayoutAmount, new Address(params, sellerAddressString));
if (arbitratorPayoutAmount.isGreaterThan(Coin.ZERO))
if (arbitratorPayoutAmount.isGreaterThan(Coin.ZERO) && arbitratorAddressEntry.getAddressString() != null)
preparedPayoutTx.addOutput(arbitratorPayoutAmount, new Address(params, arbitratorAddressEntry.getAddressString()));
// take care of sorting!
Script redeemScript = getMultiSigRedeemScript(buyerPubKey, sellerPubKey, arbitratorPubKey);
Sha256Hash sigHash = preparedPayoutTx.hashForSignature(0, redeemScript, Transaction.SigHash.ALL, false);
if (arbitratorAddressEntry.getKeyPair() == null)
throw new RuntimeException("Unexpected null value: arbitratorAddressEntry.getKeyPair() must not be null");
ECKey.ECDSASignature arbitratorSignature = arbitratorAddressEntry.getKeyPair().sign(sigHash, aesKey).toCanonicalised();
verifyTransaction(preparedPayoutTx);
@ -1026,6 +1027,9 @@ public class TradeWalletService {
Wallet.SendRequest sendRequest = Wallet.SendRequest.forTx(transaction);
sendRequest.shuffleOutputs = false;
sendRequest.aesKey = aesKey;
// We use a fixed fee
sendRequest.feePerKb = Coin.ZERO;
sendRequest.fee = FeePolicy.getFixedTxFeeForTrades();
// we allow spending of unconfirmed tx (double spend risk is low and usability would suffer if we need to wait for 1 confirmation)
sendRequest.coinSelector = new AddressBasedCoinSelector(params, addressEntry);
// We use always the same address in a trade for all transactions
@ -1052,4 +1056,5 @@ public class TradeWalletService {
}
return balance;
}*/
}

View file

@ -438,15 +438,29 @@ public class WalletService {
// Withdrawal
///////////////////////////////////////////////////////////////////////////////////////////
public String sendFunds(String fromAddress,
String toAddress,
Coin amount,
KeyParameter aesKey,
FutureCallback<Transaction> callback) throws AddressFormatException, IllegalArgumentException, InsufficientMoneyException {
public Coin getRequiredFee(String fromAddress,
String toAddress,
Coin amount) throws AddressFormatException, IllegalArgumentException, InsufficientMoneyException {
Coin fee;
try {
wallet.completeTx(getSendRequest(fromAddress, toAddress, amount, null));
fee = Coin.ZERO;
} catch (InsufficientMoneyException e) {
log.info("The amount to be transferred is not enough to pay the transaction fees of {}. " +
"We subtract that fee from the receivers amount to make the transaction possible.");
fee = e.missing;
}
return fee;
}
public Wallet.SendRequest getSendRequest(String fromAddress,
String toAddress,
Coin amount,
@Nullable KeyParameter aesKey) throws AddressFormatException, IllegalArgumentException, InsufficientMoneyException {
Transaction tx = new Transaction(params);
Preconditions.checkArgument(Restrictions.isMinSpendableAmount(amount),
"You cannot send an amount which are smaller than the fee + dust output.");
tx.addOutput(amount.subtract(FeePolicy.TX_FEE), new Address(params, toAddress));
Preconditions.checkArgument(Restrictions.isAboveDust(amount),
"You cannot send an amount which are smaller than 546 satoshis.");
tx.addOutput(amount, new Address(params, toAddress));
Wallet.SendRequest sendRequest = Wallet.SendRequest.forTx(tx);
sendRequest.aesKey = aesKey;
@ -457,13 +471,21 @@ public class WalletService {
sendRequest.coinSelector = new AddressBasedCoinSelector(params, addressEntry.get());
sendRequest.changeAddress = addressEntry.get().getAddress();
Wallet.SendResult sendResult = wallet.sendCoins(sendRequest);
sendRequest.feePerKb = FeePolicy.getFeePerKb();
return sendRequest;
}
public String sendFunds(String fromAddress,
String toAddress,
Coin amount,
KeyParameter aesKey,
FutureCallback<Transaction> callback) throws AddressFormatException, IllegalArgumentException, InsufficientMoneyException {
Coin fee = getRequiredFee(fromAddress, toAddress, amount);
Wallet.SendResult sendResult = wallet.sendCoins(getSendRequest(fromAddress, toAddress, amount.subtract(fee), aesKey));
Futures.addCallback(sendResult.broadcastComplete, callback);
printTxWithInputs("sendFunds", tx);
log.debug("tx=" + tx);
return tx.getHashAsString();
printTxWithInputs("sendFunds", sendResult.tx);
return sendResult.tx.getHashAsString();
}
public void emptyWallet(String toAddress, KeyParameter aesKey, ResultHandler resultHandler, ErrorMessageHandler errorMessageHandler)
@ -471,6 +493,7 @@ public class WalletService {
Wallet.SendRequest sendRequest = Wallet.SendRequest.emptyWallet(new Address(params, toAddress));
sendRequest.aesKey = aesKey;
Wallet.SendResult sendResult = wallet.sendCoins(sendRequest);
sendRequest.feePerKb = FeePolicy.getFeePerKb();
Futures.addCallback(sendResult.broadcastComplete, new FutureCallback<Transaction>() {
@Override
public void onSuccess(Transaction result) {

View file

@ -79,7 +79,7 @@ public class BuyerAsOffererTrade extends BuyerTrade implements OffererTrade, Ser
public Coin getPayoutAmount() {
checkNotNull(getTradeAmount(), "Invalid state: getTradeAmount() = null");
return FeePolicy.SECURITY_DEPOSIT.add(getTradeAmount());
return FeePolicy.getSecurityDeposit().add(getTradeAmount());
}
}

View file

@ -80,6 +80,6 @@ public class BuyerAsTakerTrade extends BuyerTrade implements TakerTrade, Seriali
public Coin getPayoutAmount() {
checkNotNull(getTradeAmount(), "Invalid state: getTradeAmount() = null");
return FeePolicy.SECURITY_DEPOSIT.add(getTradeAmount());
return FeePolicy.getSecurityDeposit().add(getTradeAmount());
}
}

View file

@ -368,7 +368,7 @@ abstract public class Trade implements Tradable, Model, Serializable {
}
public Coin getPayoutAmount() {
return FeePolicy.SECURITY_DEPOSIT;
return FeePolicy.getSecurityDeposit();
}
public ProcessModel getProcessModel() {

View file

@ -131,7 +131,6 @@ public class TradeManager {
log.trace("onMailboxMessageAdded senderAddress: " + senderAddress);
Message message = decryptedMsgWithPubKey.message;
if (message instanceof PayDepositRequest) {
//TODO is that used????
PayDepositRequest payDepositRequest = (PayDepositRequest) message;
log.trace("Received payDepositRequest: " + payDepositRequest);
if (payDepositRequest.getSenderAddress().equals(senderAddress))
@ -163,12 +162,6 @@ public class TradeManager {
// Lifecycle
///////////////////////////////////////////////////////////////////////////////////////////
// When all services are initialized we create the protocols for our open offers and persisted pendingTrades
// OffererAsBuyerProtocol listens for take offer requests, so we need to instantiate it early.
public void onAllServicesInitialized() {
log.trace("onAllServicesInitialized");
}
private void initPendingTrades() {
if (firstPeerAuthenticatedListener != null) p2PService.removeP2PServiceListener(firstPeerAuthenticatedListener);

View file

@ -44,7 +44,7 @@ public class BroadcastCreateOfferFeeTx extends Task<PlaceOfferModel> {
protected void run() {
try {
runInterceptHook();
Coin totalsNeeded = FeePolicy.SECURITY_DEPOSIT.add(FeePolicy.CREATE_OFFER_FEE).add(FeePolicy.TX_FEE);
Coin totalsNeeded = FeePolicy.getSecurityDeposit().add(FeePolicy.getCreateOfferFee()).add(FeePolicy.getFixedTxFeeForTrades());
AddressEntry addressEntry = model.walletService.getAddressEntryByOfferId(model.offer.getId());
Coin balance = model.walletService.getBalanceForAddress(addressEntry.getAddress());
if (balance.compareTo(totalsNeeded) >= 0) {

View file

@ -45,7 +45,7 @@ public class CreateOfferFeeTx extends Task<PlaceOfferModel> {
Arbitrator selectedArbitrator = model.user.getAcceptedArbitratorByAddress(selectedArbitratorAddress);
Transaction transaction = model.tradeWalletService.createTradingFeeTx(
model.walletService.getAddressEntryByOfferId(model.offer.getId()),
FeePolicy.CREATE_OFFER_FEE,
FeePolicy.getCreateOfferFee(),
selectedArbitrator.getBtcAddress());
// We assume there will be no tx malleability. We add a check later in case the published offer has a different hash.

View file

@ -41,8 +41,8 @@ public class CreateAndSignDepositTxAsBuyer extends TradeTask {
try {
runInterceptHook();
checkNotNull(trade.getTradeAmount(), "trade.getTradeAmount() must not be null");
Coin buyerInputAmount = FeePolicy.SECURITY_DEPOSIT.add(FeePolicy.TX_FEE);
Coin msOutputAmount = buyerInputAmount.add(FeePolicy.SECURITY_DEPOSIT).add(trade.getTradeAmount());
Coin buyerInputAmount = FeePolicy.getSecurityDeposit().add(FeePolicy.getFixedTxFeeForTrades());
Coin msOutputAmount = buyerInputAmount.add(FeePolicy.getSecurityDeposit()).add(trade.getTradeAmount());
log.info("\n\n------------------------------------------------------------\n"
+ "Contract as json\n"

View file

@ -37,7 +37,7 @@ public class CreateDepositTxInputsAsBuyer extends TradeTask {
protected void run() {
try {
runInterceptHook();
Coin takerInputAmount = FeePolicy.SECURITY_DEPOSIT.add(FeePolicy.TX_FEE);
Coin takerInputAmount = FeePolicy.getSecurityDeposit().add(FeePolicy.getFixedTxFeeForTrades());
InputsAndChangeOutput result = processModel.getTradeWalletService().takerCreatesDepositsTxInputs(takerInputAmount, processModel.getAddressEntry());
processModel.setRawInputs(result.rawInputs);
processModel.setChangeOutputValue(result.changeOutputValue);

View file

@ -40,7 +40,7 @@ public class SignAndFinalizePayoutTx extends TradeTask {
try {
runInterceptHook();
checkNotNull(trade.getTradeAmount(), "trade.getTradeAmount() must not be null");
Coin sellerPayoutAmount = FeePolicy.SECURITY_DEPOSIT;
Coin sellerPayoutAmount = FeePolicy.getSecurityDeposit();
Coin buyerPayoutAmount = sellerPayoutAmount.add(trade.getTradeAmount());
Transaction transaction = processModel.getTradeWalletService().buyerSignsAndFinalizesPayoutTx(

View file

@ -41,8 +41,8 @@ public class CreateAndSignDepositTxAsSeller extends TradeTask {
try {
runInterceptHook();
checkNotNull(trade.getTradeAmount(), "trade.getTradeAmount() must not be null");
Coin sellerInputAmount = FeePolicy.SECURITY_DEPOSIT.add(FeePolicy.TX_FEE).add(trade.getTradeAmount());
Coin msOutputAmount = sellerInputAmount.add(FeePolicy.SECURITY_DEPOSIT);
Coin sellerInputAmount = FeePolicy.getSecurityDeposit().add(FeePolicy.getFixedTxFeeForTrades()).add(trade.getTradeAmount());
Coin msOutputAmount = sellerInputAmount.add(FeePolicy.getSecurityDeposit());
log.info("\n\n------------------------------------------------------------\n"
+ "Contract as json\n"

View file

@ -38,7 +38,7 @@ public class CreateDepositTxInputsAsSeller extends TradeTask {
try {
runInterceptHook();
if (trade.getTradeAmount() != null) {
Coin takerInputAmount = FeePolicy.SECURITY_DEPOSIT.add(FeePolicy.TX_FEE).add(trade.getTradeAmount());
Coin takerInputAmount = FeePolicy.getSecurityDeposit().add(FeePolicy.getFixedTxFeeForTrades()).add(trade.getTradeAmount());
InputsAndChangeOutput result = processModel.getTradeWalletService().takerCreatesDepositsTxInputs(takerInputAmount, processModel
.getAddressEntry());

View file

@ -39,7 +39,7 @@ public class SignPayoutTx extends TradeTask {
try {
runInterceptHook();
checkNotNull(trade.getTradeAmount(), "trade.getTradeAmount() must not be null");
Coin sellerPayoutAmount = FeePolicy.SECURITY_DEPOSIT;
Coin sellerPayoutAmount = FeePolicy.getSecurityDeposit();
Coin buyerPayoutAmount = sellerPayoutAmount.add(trade.getTradeAmount());
// We use the sellers LastBlockSeenHeight, which might be different to the buyers one.

View file

@ -47,7 +47,7 @@ public class CreateTakeOfferFeeTx extends TradeTask {
Arbitrator selectedArbitrator = user.getAcceptedArbitratorByAddress(selectedArbitratorAddress);
Transaction createTakeOfferFeeTx = processModel.getTradeWalletService().createTradingFeeTx(
processModel.getAddressEntry(),
FeePolicy.TAKE_OFFER_FEE,
FeePolicy.getTakeOfferFee(),
selectedArbitrator.getBtcAddress());
processModel.setTakeOfferFeeTx(createTakeOfferFeeTx);

View file

@ -20,6 +20,7 @@ package io.bitsquare.user;
import io.bitsquare.app.BitsquareEnvironment;
import io.bitsquare.app.Version;
import io.bitsquare.btc.BitcoinNetwork;
import io.bitsquare.btc.FeePolicy;
import io.bitsquare.locale.CountryUtil;
import io.bitsquare.locale.CurrencyUtil;
import io.bitsquare.locale.TradeCurrency;
@ -31,6 +32,8 @@ import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import org.bitcoinj.core.Coin;
import org.bitcoinj.core.Transaction;
import org.bitcoinj.utils.MonetaryFormat;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -100,6 +103,7 @@ public class Preferences implements Serializable {
private boolean tacAccepted;
private Locale preferredLocale;
private TradeCurrency preferredTradeCurrency;
private long txFeePerKB = FeePolicy.getFeePerKb().value;
// Observable wrappers
transient private final StringProperty btcDenominationProperty = new SimpleStringProperty(btcDenomination);
@ -146,6 +150,11 @@ public class Preferences implements Serializable {
defaultLocale = preferredLocale;
preferredTradeCurrency = persisted.getPreferredTradeCurrency();
defaultTradeCurrency = preferredTradeCurrency;
try {
setTxFeePerKB(persisted.getTxFeePerKB());
} catch (Exception e) {
// leave default value
}
} else {
setTradeCurrencies(CurrencyUtil.getAllSortedCurrencies());
tradeCurrencies = new ArrayList<>(tradeCurrenciesAsObservable);
@ -162,6 +171,7 @@ public class Preferences implements Serializable {
preferredLocale = getDefaultLocale();
preferredTradeCurrency = getDefaultTradeCurrency();
storage.queueUpForSave();
}
@ -272,6 +282,14 @@ public class Preferences implements Serializable {
storage.queueUpForSave();
}
public void setTxFeePerKB(long txFeePerKB) throws Exception {
if (txFeePerKB < Transaction.REFERENCE_DEFAULT_MIN_TX_FEE.value)
throw new Exception("Transaction fee must be at least 5 satoshi/byte");
this.txFeePerKB = txFeePerKB;
FeePolicy.setFeePerKb(Coin.valueOf(txFeePerKB));
storage.queueUpForSave();
}
///////////////////////////////////////////////////////////////////////////////////////////
// Getter
@ -387,4 +405,8 @@ public class Preferences implements Serializable {
public TradeCurrency getPreferredTradeCurrency() {
return preferredTradeCurrency;
}
public long getTxFeePerKB() {
return Math.max(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE.value, txFeePerKB);
}
}

View file

@ -28,21 +28,21 @@ public class RestrictionsTest {
@Test
public void testIsMinSpendableAmount() {
Coin amount = null;
assertFalse("tx unfunded, pending", Restrictions.isMinSpendableAmount(amount));
assertFalse("tx unfunded, pending", Restrictions.isAboveFixedTxFeeAndDust(amount));
amount = Coin.ZERO;
assertFalse("tx unfunded, pending", Restrictions.isMinSpendableAmount(amount));
assertFalse("tx unfunded, pending", Restrictions.isAboveFixedTxFeeAndDust(amount));
amount = FeePolicy.TX_FEE;
assertFalse("tx unfunded, pending", Restrictions.isMinSpendableAmount(amount));
amount = FeePolicy.getFixedTxFeeForTrades();
assertFalse("tx unfunded, pending", Restrictions.isAboveFixedTxFeeAndDust(amount));
amount = Transaction.MIN_NONDUST_OUTPUT;
assertFalse("tx unfunded, pending", Restrictions.isMinSpendableAmount(amount));
assertFalse("tx unfunded, pending", Restrictions.isAboveFixedTxFeeAndDust(amount));
amount = FeePolicy.TX_FEE.add(Transaction.MIN_NONDUST_OUTPUT);
assertFalse("tx unfunded, pending", Restrictions.isMinSpendableAmount(amount));
amount = FeePolicy.getFixedTxFeeForTrades().add(Transaction.MIN_NONDUST_OUTPUT);
assertFalse("tx unfunded, pending", Restrictions.isAboveFixedTxFeeAndDust(amount));
amount = FeePolicy.TX_FEE.add(Transaction.MIN_NONDUST_OUTPUT).add(Coin.valueOf(1));
assertTrue("tx unfunded, pending", Restrictions.isMinSpendableAmount(amount));
amount = FeePolicy.getFixedTxFeeForTrades().add(Transaction.MIN_NONDUST_OUTPUT).add(Coin.valueOf(1));
assertTrue("tx unfunded, pending", Restrictions.isAboveFixedTxFeeAndDust(amount));
}
}