Wallet: throw more appropriate exception types during completion.

Resolves issue 560.
This commit is contained in:
Mike Hearn 2014-05-21 19:38:12 +02:00
parent 028a1cca69
commit b47995ed97
3 changed files with 19 additions and 14 deletions

View file

@ -1884,6 +1884,11 @@ public class Wallet extends BaseTaggableObject implements Serializable, BlockCha
return tx; return tx;
} }
public static class CompletionException extends RuntimeException {}
public static class DustySendRequested extends CompletionException {}
public static class CouldNotAdjustDownwards extends CompletionException {}
public static class ExceededMaxTransactionSize extends CompletionException {}
/** /**
* Given a spend request containing an incomplete transaction, makes it valid by adding outputs and signed inputs * Given a spend request containing an incomplete transaction, makes it valid by adding outputs and signed inputs
* according to the instructions in the request. The transaction in the request is modified by this method, as is * according to the instructions in the request. The transaction in the request is modified by this method, as is
@ -1891,8 +1896,10 @@ public class Wallet extends BaseTaggableObject implements Serializable, BlockCha
* *
* @param req a SendRequest that contains the incomplete transaction and details for how to make it valid. * @param req a SendRequest that contains the incomplete transaction and details for how to make it valid.
* @throws InsufficientMoneyException if the request could not be completed due to not enough balance. * @throws InsufficientMoneyException if the request could not be completed due to not enough balance.
* @throws IllegalArgumentException if you try and complete the same SendRequest twice, or if the given send request * @throws IllegalArgumentException if you try and complete the same SendRequest twice
* cannot be completed without violating the protocol rules. * @throws DustySendRequested if the resultant transaction would violate the dust rules (an output that's too small to be worthwhile)
* @throws CouldNotAdjustDownwards if emptying the wallet was requested and the output can't be shrunk for fees without violating a protocol rule.
* @throws ExceededMaxTransactionSize if the resultant transaction is too big for Bitcoin to process (try breaking up the amounts of value)
*/ */
public void completeTx(SendRequest req) throws InsufficientMoneyException { public void completeTx(SendRequest req) throws InsufficientMoneyException {
lock.lock(); lock.lock();
@ -1925,7 +1932,7 @@ public class Wallet extends BaseTaggableObject implements Serializable, BlockCha
for (TransactionOutput output : req.tx.getOutputs()) for (TransactionOutput output : req.tx.getOutputs())
if (output.getValue().compareTo(Utils.CENT) < 0) { if (output.getValue().compareTo(Utils.CENT) < 0) {
if (output.getValue().compareTo(output.getMinNonDustValue()) < 0) if (output.getValue().compareTo(output.getMinNonDustValue()) < 0)
throw new IllegalArgumentException("Tried to send dust with ensureMinRequiredFee set - no way to complete this"); throw new DustySendRequested();
needAtLeastReferenceFee = true; needAtLeastReferenceFee = true;
break; break;
} }
@ -1967,7 +1974,7 @@ public class Wallet extends BaseTaggableObject implements Serializable, BlockCha
final BigInteger feePerKb = req.feePerKb == null ? BigInteger.ZERO : req.feePerKb; final BigInteger feePerKb = req.feePerKb == null ? BigInteger.ZERO : req.feePerKb;
Transaction tx = req.tx; Transaction tx = req.tx;
if (!adjustOutputDownwardsForFee(tx, bestCoinSelection, baseFee, feePerKb)) if (!adjustOutputDownwardsForFee(tx, bestCoinSelection, baseFee, feePerKb))
throw new InsufficientMoneyException.CouldNotAdjustDownwards(); throw new CouldNotAdjustDownwards();
} }
totalInput = totalInput.add(bestCoinSelection.valueGathered); totalInput = totalInput.add(bestCoinSelection.valueGathered);
@ -1991,11 +1998,8 @@ public class Wallet extends BaseTaggableObject implements Serializable, BlockCha
// Check size. // Check size.
int size = req.tx.bitcoinSerialize().length; int size = req.tx.bitcoinSerialize().length;
if (size > Transaction.MAX_STANDARD_TX_SIZE) { if (size > Transaction.MAX_STANDARD_TX_SIZE)
throw new IllegalArgumentException( throw new ExceededMaxTransactionSize();
String.format("Transaction could not be created without exceeding max size: %d vs %d", size,
Transaction.MAX_STANDARD_TX_SIZE));
}
// Label the transaction as being self created. We can use this later to spend its change output even before // Label the transaction as being self created. We can use this later to spend its change output even before
// the transaction is confirmed. We deliberately won't bother notifying listeners here as there's not much // the transaction is confirmed. We deliberately won't bother notifying listeners here as there's not much

View file

@ -1463,7 +1463,7 @@ public class WalletTest extends TestWithWallet {
} }
} }
@Test(expected = IllegalArgumentException.class) @Test(expected = Wallet.ExceededMaxTransactionSize.class)
public void respectMaxStandardSize() throws Exception { public void respectMaxStandardSize() throws Exception {
// Check that we won't create txns > 100kb. Average tx size is ~220 bytes so this would have to be enormous. // Check that we won't create txns > 100kb. Average tx size is ~220 bytes so this would have to be enormous.
sendMoneyToWallet(Utils.toNanoCoins(100, 0), AbstractBlockChain.NewBlockType.BEST_CHAIN); sendMoneyToWallet(Utils.toNanoCoins(100, 0), AbstractBlockChain.NewBlockType.BEST_CHAIN);
@ -1498,11 +1498,12 @@ public class WalletTest extends TestWithWallet {
Transaction tx3 = createFakeTx(params, BigInteger.TEN, myAddress); Transaction tx3 = createFakeTx(params, BigInteger.TEN, myAddress);
wallet.receiveFromBlock(tx3, block, AbstractBlockChain.NewBlockType.BEST_CHAIN, 2); wallet.receiveFromBlock(tx3, block, AbstractBlockChain.NewBlockType.BEST_CHAIN, 2);
// No way we can add nearly enough fee // Not allowed to send dust.
try { try {
wallet.createSend(notMyAddr, BigInteger.ONE); wallet.createSend(notMyAddr, BigInteger.ONE);
fail(); fail();
} catch (IllegalArgumentException e) { } catch (Wallet.DustySendRequested e) {
// Expected.
} }
// Spend it all without fee enforcement // Spend it all without fee enforcement
SendRequest req = SendRequest.to(notMyAddr, BigInteger.TEN.add(BigInteger.ONE.add(BigInteger.ONE))); SendRequest req = SendRequest.to(notMyAddr, BigInteger.TEN.add(BigInteger.ONE.add(BigInteger.ONE)));
@ -2179,7 +2180,7 @@ public class WalletTest extends TestWithWallet {
try { try {
wallet.completeTx(request); wallet.completeTx(request);
fail(); fail();
} catch (InsufficientMoneyException.CouldNotAdjustDownwards e) {} } catch (Wallet.CouldNotAdjustDownwards e) {}
request.ensureMinRequiredFee = false; request.ensureMinRequiredFee = false;
wallet.completeTx(request); wallet.completeTx(request);
wallet.commitTx(request.tx); wallet.commitTx(request.tx);

View file

@ -87,7 +87,7 @@ public class Main extends Application {
// last months worth or more (takes a few seconds). // last months worth or more (takes a few seconds).
bitcoin.setCheckpoints(getClass().getResourceAsStream("checkpoints")); bitcoin.setCheckpoints(getClass().getResourceAsStream("checkpoints"));
// As an example! // As an example!
bitcoin.useTor(); // bitcoin.useTor();
} }
// Now configure and start the appkit. This will take a second or two - we could show a temporary splash screen // Now configure and start the appkit. This will take a second or two - we could show a temporary splash screen